# Opnsense MCP Server — 343 tools via DADL

The Opnsense DADL turns Opnsense's API into an MCP server that Claude, GPT or any MCP-compatible agent can consume directly. One YAML file declares all 343 tools — firewall, ipsec, unbound, haproxy, system, wireguard, and more — and ToolMesh serves them at runtime. No Python boilerplate, no per-endpoint code, no separate MCP server process.

Below: the endpoint coverage matrix, a two-block ToolMesh setup, the full tool reference grouped by Opnsense feature area, required credential scopes.

**Source:** [OPNsense REST API](https://docs.opnsense.org/development/api.html)

**Updated:** 2026-08-02

**Tags:** firewall, crud, user-management, networking, monitoring, security, automation, logging, auth:basic

## Which Opnsense endpoints are covered?

**23%** (343 of ~1500 endpoints).

**Focus:** firewall (aliases, filter rules, NAT, outbound NAT mode, NPTv6), diagnostics (ARP, states, system, ping jobs, packet capture), interfaces (overview, VLANs, VIPs, global settings, reload), Unbound DNS, WireGuard, OpenVPN, IPsec, Kea DHCP, routes/gateways/gateway groups, firmware, system/backup, HAProxy, IDS/IPS, traffic shaper, certificates, syslog, cron, HA (hasync syncitems, config sync), IPv6 RA (radvd), NDP proxy, services (per-instance start/stop/restart)

**Missing:** per-interface IPv4/IPv6 address config (configuration type, addresses, MTU/MSS — legacy config.xml, no API as of 26.7), firewall normalization/scrub rules (firewall_scrub.php legacy, no API as of 26.7), firewall savepoint/rollback (no such endpoints on 26.7), captive portal vouchers, BIND DNS, Caddy, Nginx, FRR routing, ACME client, Monit, collectd, CrowdSec, FreeRADIUS, most third-party plugins

*Last reviewed: 2026-08-02*

## How do you configure the Opnsense DADL?

1. Log in to OPNsense web UI as admin
2. Navigate to System → Access → Users
3. Edit the user you want to create API credentials for (or create a dedicated API user)
4. Scroll down to 'API keys' section and click the '+' (add) button
5. A key/secret pair is generated and downloaded as an apikey.txt file
6. The file contains: key=<your-api-key> and secret=<your-api-secret>
7. IMPORTANT: The secret is NOT stored on the system — save it securely
8. Set env vars: CREDENTIAL_OPNSENSE_API_KEY=<key> and CREDENTIAL_OPNSENSE_API_SECRET=<secret>

**Environment variable:** `CREDENTIAL_OPNSENSE_API_KEY and CREDENTIAL_OPNSENSE_API_SECRET`

[Authentication docs](https://docs.opnsense.org/development/how-tos/api.html)

*OPNsense uses page-based permissions. The API user needs GUI access to the same pages they want to access via API. For full API access, assign the user to the 'admins' group. API key + secret are used as HTTP Basic Auth (key=username, secret=password).*

## How do you install the Opnsense MCP server with ToolMesh?

Add to your `backends.yaml`:

```yaml
- name: opnsense
  transport: rest
  dadl: opnsense.dadl
  url: "https://your-opnsense-host/api"

```

Set the credential:

```
CREDENTIAL_OPNSENSE_API_KEY and CREDENTIAL_OPNSENSE_API_SECRET=your-token-here
```

## What 343 tools does the Opnsense DADL expose?

- **POST** `search_firewall_aliases` — Search firewall aliases (IP lists, port groups, URL tables, GeoIP, etc.)
- **GET** `get_firewall_alias` — Get a single firewall alias by UUID
- **POST** `add_firewall_alias` — Create a new firewall alias
- **POST** `set_firewall_alias` — Update an existing firewall alias
- **POST** `del_firewall_alias` — Delete a firewall alias by UUID
- **POST** `toggle_firewall_alias` — Toggle a firewall alias on or off. Omit 'enabled' to flip the current state.
- **POST** `reconfigure_firewall_aliases` — Apply alias changes to the running firewall
- **GET** `get_alias_uuid_by_name` — Look up alias UUID by name
- **GET** `list_alias_content` — List the resolved content (IPs/networks) of an alias
- **POST** `add_alias_entry` — Add an entry to an alias at runtime (without modifying config)
- **POST** `delete_alias_entry` — Remove an entry from an alias at runtime
- **POST** `flush_alias` — Flush (clear) all entries from an alias at runtime
- **GET** `list_network_aliases` — List all aliases usable as network references
- **GET** `list_alias_categories` — List all alias categories
- **GET** `list_geoip` — Get GeoIP configuration and available countries
- **POST** `search_firewall_rules` — Search firewall filter rules. Returns {total, returned, truncated, rows} — check `truncated` before claiming a complete picture (rowCount defaults to 500 here, the appliance's own default of 50 silently cut a 53-rule ruleset short). The result MIXES user rules (MVC model) with OPNsense's automatic/internal rules (anti-lockout, default deny, pfsync, …): `is_automatic: true` marks the latter and is now always present as a real boolean — the raw API omits the key entirely on user rules. All boolean-ish fields are normalised to real booleans (raw API returns `true` on automatic rules but the string "1"/"0" on user rules, so `if (rule.log)` was true for log "0"): enabled, log, quick, source_not, destination_not, interfacenot, disablereplyto, nosync, nopfsync, allowopts, tcpflags_any. NEGATION: the added comfort fields %source_net / %destination_net / %interface render the negation flags inline ("NOT <alias>"), so a policy-routing rule can no longer be misread as its own opposite. Always read those, or read the *_not flags — the bare source_net/destination_net/interface fields say nothing about negation. Filters: `interface` (comma list of friendly keys; pass "" for floating rules only, omit for ALL rules), `category` (comma list of category UUIDs — automatic rules carry their own category), `show_all: 1` additionally merges live pf hit counters (evaluations/states/packets/bytes) into each row.

- **GET** `get_firewall_rule` — Get a single firewall filter rule by UUID
- **POST** `add_firewall_rule` — Create a new firewall filter rule. Covers the full Filter.xml model — fields not listed here cannot be sent. Apply with apply_firewall afterwards. Key gotchas: icmptype/icmp6type are ignored unless protocol is ICMP/IPv6-ICMP, and leaving them empty means ALL ICMP types; disablereplyto is regularly required on WAN-side rules; statetype defaults to "keep".

- **POST** `set_firewall_rule` — Update an existing firewall filter rule. Same full field set as add_firewall_rule; the endpoint MERGES, so omitted fields keep their stored value. A field that is not in this list cannot be written and is dropped without any error — check the list before assuming a restriction was applied. Apply with apply_firewall afterwards.

- **POST** `del_firewall_rule` — Delete a firewall filter rule
- **POST** `toggle_firewall_rule` — Toggle a firewall filter rule on or off. Omit 'enabled' to flip the current state.
- **POST** `move_firewall_rule_before` — Move a firewall rule before another rule (reorder)
- **GET** `get_firewall_interface_list` — List interfaces available for firewall rules
- **GET** `get_firewall_rule_stats` — Get firewall rule hit statistics
- **POST** `apply_firewall` — Apply the stored firewall ruleset to the running pf (runs configd "filter reload skip_alias"). Takes NO parameters — the old {rollback_revision} path segment does not exist on 26.7 and made this tool uncallable. Returns {"status":"OK"} plus the reload output. NOTE: model writes via add_/set_/del_/toggle_ endpoints already trigger a config save that reloads the filter on most installations, so an explicit apply is usually a no-op confirmation rather than the step that activates the change. NPT/DNAT/ SNAT rules share this same pf reload; there is no separate per-family apply.

- **POST** `search_firewall_categories` — Search firewall rule categories
- **POST** `add_firewall_category` — Create a new firewall rule category
- **POST** `set_firewall_category` — Update a firewall rule category
- **POST** `del_firewall_category` — Delete a firewall rule category
- **POST** `search_dnat_rules` — Search destination NAT (port forwarding) rules. The NAT families use different field conventions than the filter rules — inverted polarity and dot instead of underscore notation — which reads as "everything is disabled" if taken at face value: raw rows carry `disabled: "0"` (NOT `enabled`), `descr` (not `description`) and `source.network` / `destination.network` / `destination.not` (not source_net / destination_net / destination_not). This tool therefore ADDS filter-rule-compatible fields: `enabled` (real boolean, inverse of disabled), `description`, and the negation- aware %source_net / %destination_net ("NOT <alias>" when the .not flag is set). disabled/log/is_automatic/nordr are normalised to real booleans.

- **GET** `get_dnat_rule` — Get a single DNAT rule by UUID
- **POST** `add_dnat_rule` — Create a new destination NAT (port forwarding) rule
- **POST** `set_dnat_rule` — Update an existing DNAT rule
- **POST** `del_dnat_rule` — Delete a destination NAT (port forwarding) rule by UUID
- **POST** `toggle_dnat_rule` — Toggle a destination NAT rule on or off. Omit 'disabled' to flip the current state.
- **GET** `get_outbound_nat_mode` — Get the outbound (source) NAT generation mode — the Firewall→NAT→Outbound radio button. Returns {filter:{general:{snat_mode:{<key>:{value,selected}}}}} as an OPNsense SELECT map; the selected key is one of automatic (rules generated from the interface config), hybrid (automatic PLUS the manual rules), advanced (manual rules ONLY) or disabled (no source NAT at all). Without this, an empty search_snat_rules cannot be interpreted.

- **POST** `set_outbound_nat_mode` — Set the outbound (source) NAT generation mode. Pass the object param `filter` as {general: {snat_mode: "advanced"}} — a BARE option key, not the {value,selected} map from get_outbound_nat_mode. Valid: automatic, hybrid, advanced, disabled. Switching from automatic/hybrid to advanced with no manual rules present DROPS ALL OUTBOUND NAT and will cut every masqueraded client off the internet — read search_snat_rules first. Apply with apply_firewall.

- **POST** `search_snat_rules` — Search source NAT (outbound NAT) rules. An EMPTY rows list is ambiguous on its own — call get_outbound_nat_mode: in mode "automatic" no manual rules exist by design while NAT is still active; in mode "advanced" an empty list means no outbound NAT is applied. Same field-convention normalisation as search_dnat_rules: raw rows use `disabled`, `descr` and dotted source.network/destination.network, so `enabled` (boolean), `description` and negation-aware %source_net / %destination_net are added.

- **POST** `add_snat_rule` — Create a new source NAT rule
- **POST** `set_snat_rule` — Update a source NAT rule
- **POST** `del_snat_rule` — Delete a source NAT rule
- **POST** `search_npt_rules` — Search NPTv6 (IPv6 network prefix translation) rules. Rows include uuid, sequence, interface, source_net (internal prefix), destination_net (external prefix), trackif, enabled.
- **GET** `get_npt_rule` — Get a single NPTv6 rule by UUID. SELECT fields (interface, trackif, categories) come back as {opt:{value,selected}} maps — flatten to the bare selected key before posting back via set_npt_rule.
- **POST** `add_npt_rule` — Create an NPTv6 rule translating an internal IPv6 prefix (source_net) to an external one (destination_net). For dynamically assigned (tracked) external prefixes set trackif to the upstream interface INSTEAD of destination_net. Booleans are "0"/"1" strings; interface is the bare friendly key. The rule is stored only — call apply_firewall to activate it in pf. Returns {"result":"saved","uuid":...} or {"result":"failed","validations":{...}}.

- **POST** `set_npt_rule` — Update an NPTv6 rule. FULL-REPLACE (setBase): any field you omit is reset to its model default — read with get_npt_rule, flatten SELECT maps to bare values, change what you need, then post the COMPLETE rule. Call apply_firewall afterwards to activate.

- **POST** `del_npt_rule` — Delete an NPTv6 rule by UUID. Call apply_firewall afterwards to deactivate it in pf.
- **POST** `toggle_npt_rule` — Enable/disable an NPTv6 rule. enabled=1 to enable, 0 to disable. Call apply_firewall afterwards.
- **POST** `query_firewall_states` — Query active pf firewall states (connections)
- **GET** `get_pf_statistics` — Get pf firewall statistics. Available sections: info, memory, timeouts, interfaces, rules. Pass a section name or 'all' for everything.
- **GET** `get_firewall_stats` — Get firewall statistics summary
- **GET** `get_firewall_log` — Get recent firewall log entries. Use limit to control result size (default 1000, can be large). Use digest for pagination (value from previous response). No content filtering — filter client-side by action, src, dst, etc.
- **GET** `get_firewall_log_filters` — Get available firewall log filter options
- **POST** `kill_firewall_states` — Kill pf states selected by address filter and/or rule label. WORKS ONLY FOR BARE IPv4 ADDRESSES — this is an appliance-side limitation, not a tool bug, and it fails SILENTLY with {"result":"ok","dropped_states":0}. OPNsense runs `filter` through SanitizeFilter::filter_query, which keeps only [0-9 a-z A-Z , space * - _ . #] and therefore STRIPS ":" and "/": an IPv6 address "2001:db8::1" arrives as "2001db81", and a CIDR "198.51.100.0/24" arrives as "198.51.100.024" — neither parses as a network, so both degrade to a plain substring match that matches nothing. Verified against 26.7.1_1 (kill_states.py + lib/states.py + SanitizeFilter.php). WHAT WORKS: one or more space-separated bare IPv4 addresses (e.g. "192.0.2.10 192.0.2.11"); each is matched against a state's src/dst/nat address and gateway, and ALL address clauses must match. Tokens that are not parseable as an address become substring filters over the state record. `ruleid` is matched as a lowercase substring of the pf rule label and is sanitised to alphanumerics only. FOR IPv6 OR CIDR use query_firewall_states to get stateid/creatorid and then del_firewall_state per state, or flush_firewall_states to drop everything.

- **POST** `flush_firewall_states` — Flush ALL firewall states
- **POST** `del_firewall_state` — Delete a specific firewall state
- **GET** `list_firewall_rule_ids` — List all active pf rule IDs with descriptions
- **POST** `query_pf_top` — Query top firewall connections by various criteria
- **GET** `get_arp_table` — Get the ARP table (IPv4 neighbor cache). Returns ALL entries (no server-side filtering). Fields are reduced to ip, mac, hostname, intf_description to keep response compact.
- **GET** `get_ndp_table` — Get the NDP table (IPv6 neighbor cache). Returns ALL entries (no server-side filtering). Fields are reduced to keep response compact.
- **POST** `flush_arp_table` — Flush the entire ARP table
- **GET** `get_routes` — Get the system routing table
- **GET** `get_interface_config` — Get configuration of all network interfaces
- **GET** `get_interface_names` — Get interface name mappings (physical ↔ friendly)
- **GET** `get_interface_statistics` — Get traffic statistics for all interfaces
- **GET** `get_vip_status` — Get CARP/VIP status for high availability
- **GET** `get_pfsync_nodes` — Get pfsync HA cluster node status
- **POST** `del_route` — Delete a route from the system routing table
- **GET** `get_protocol_statistics` — Get protocol-level traffic statistics (TCP, UDP, ICMP, etc.)
- **GET** `get_socket_statistics` — Get active socket/connection statistics
- **GET** `get_memory_statistics` — Get network memory (mbuf) allocation statistics
- **GET** `get_bpf_statistics` — Get BPF (Berkeley Packet Filter) statistics
- **GET** `get_system_information` — Hostname and OPNsense/kernel version strings. Much thinner than the name suggests — no uptime, CPU or memory here (use get_system_resources / get_system_status). The raw response also carries an `updates` field whose value is the UI label "Click to check for updates." rather than any update state; it is stripped here. Real update state: check_firmware_updates or get_firmware_status.

- **GET** `get_system_resources` — Get system resource usage (CPU, memory, processes)
- **GET** `get_system_memory` — RAW kernel allocator dump: the complete vmstat malloc-statistics and memory-zone statistics (~45 KB of per-bucket counters). This is NOT a memory usage summary and is almost never what you want — for total/used/ARC in a few hundred bytes use get_system_resources. Reach for this only when you need per-zone allocator detail.

- **GET** `get_system_disk` — Get disk usage for all filesystems
- **GET** `get_system_temperature` — Get system temperature sensors
- **GET** `get_system_time` — Get current system time and uptime
- **GET** `get_system_swap` — Get swap space usage statistics (total, used, free)
- **GET** `get_system_mbuf` — Get mbuf (network memory buffer) statistics
- **GET** `get_activity` — Get running processes and system activity
- **GET** `reverse_dns_lookup` — Perform a reverse DNS lookup
- **GET** `get_traffic_interface` — Get current traffic rates per interface
- **GET** `get_traffic_top` — Get top traffic connections for specified interfaces
- **POST** `set_ping_job` — Create a ping diagnostics job (does NOT start it). Pass the single object param `ping` with a nested settings object; returns {"result":"ok","uuid":"…"} — feed the uuid to start_ping_job. Settings fields: hostname (required — target host/IP), fam (address family: "ip"=IPv4, "ip6"=IPv6; default ip), source_address (source IP to ping from — controls source-address selection, e.g. to test a specific GUA/ULA), packetsize (bytes 1-65535), disable_frag ("0"/"1"), interval (seconds between packets 1-120), description.

- **POST** `start_ping_job` — Start a ping job created with set_ping_job. It pings continuously until stop_ping_job.
- **POST** `stop_ping_job` — Stop a running ping job. Final statistics stay readable via search_ping_jobs until the job is removed.
- **POST** `remove_ping_job` — Remove a stopped ping job and its result files
- **POST** `search_ping_jobs` — List ping jobs with live status and statistics per job: id (uuid), status, hostname, send, received, loss, min, avg, max (ms)
- **POST** `set_capture_job` — Create a packet capture job (does NOT start it). Pass the single object param `packetcapture` with a nested settings object; returns {"result":"ok","uuid":"…"}. Settings fields: interface (required — comma-separated PHYSICAL device names as listed in get_interfaces_overview `device`, e.g. "em0" or "em0,vlan01" — NOT friendly keys like lan/wan, those fail with "Option not in list"), fam (required address-family filter: any|ip|ip6|arp), promiscuous ("0"/"1", default 0), protocol (tcpdump protocol filter, default any — e.g. icmp, tcp, udp, esp), protocol_not ("1" inverts the protocol match), host (host/network filter), port (1-65535), port_not ("1" inverts the port match), snaplen (bytes per packet 1-262144), count (packet limit, default 100 — capture auto-stops when reached), description.

- **POST** `start_capture_job` — Start a packet capture job created with set_capture_job
- **POST** `stop_capture_job` — Stop a running packet capture job (it also auto-stops after `count` packets)
- **GET** `view_capture_job` — View decoded packets of a capture job (tcpdump text rows per interface, with interface name map). detail: normal, medium (-v) or high (-vv).
- **GET** `download_capture` — Download the raw pcap archive of a capture job as a file (open in Wireshark/tcpdump)
- **POST** `remove_capture_job` — Remove a stopped capture job and its pcap files
- **POST** `search_capture_jobs` — List packet capture jobs with status per job: id (uuid), status (stopped/running), interface, description
- **GET** `get_capture_macinfo` — Look up the vendor (OUI) of a MAC address seen in a capture
- **GET** `get_interfaces_overview` — Detailed live state of all configured interfaces. The prefix length is only embedded in the address strings (addr4 "198.51.100.2/24", addr6 "2001:db8:0:a::2/64") — this tool adds `subnetbits4` / `subnetbits6` as separate fields so no string splitting is needed (the stored config values are also in config.subnet / config.subnetv6). Per row: device, identifier (friendly key), description, addr4/addr6 plus full ipv4/ipv6 lists incl. CARP VIPs, carp (vhid → MASTER/BACKUP), macaddr, mtu, media, flags, routes.

- **GET** `get_interface_detail` — Get details for a specific interface
- **GET** `export_interfaces` — Export interface configuration
- **POST** `reload_interface` — Re-apply the stored configuration of ONE interface (configd "interface reconfigure"): re-runs address assignment and restarts dhclient/dhcp6c on it — the API-side way to bounce/flush an interface after upstream or config changes (e.g. to re-trigger SLAAC/DHCPv6). Expect a brief connectivity loss on that interface.

- **GET** `get_interface_settings` — Get global interface settings: hardware offloading flags (disablechecksumoffloading, disablesegmentationoffloading, disablelargereceiveoffloading, disablevlanhwfilter 0|1|2), the global IPv6 kill switch (disableipv6), and DHCPv6 client behavior (dhcp6_norelease, dhcp6_debug, dhcp6_duid, dhcp6_ratimeout). Also returns suggested DUID values under `duids`.

- **POST** `set_interface_settings` — Update GLOBAL interface settings — PARTIAL MERGE (setNodes): only posted fields change, everything else is preserved. Pass the single object param `settings`. Fields: disablechecksumoffloading, disablesegmentationoffloading, disablelargereceiveoffloading ("0"/"1"), disablevlanhwfilter (bare "0"=enable HW filtering, "1"=disable, "2"=leave default), disableipv6 ("1" disables IPv6 on ALL interfaces — dangerous), dhcp6_norelease ("1" = do not send DHCPv6 release on exit), dhcp6_debug, dhcp6_duid (DUID string, see get response `duids` for suggestions), dhcp6_ratimeout (seconds). Call reconfigure_interface_settings to apply.

- **POST** `reconfigure_interface_settings` — Apply global interface settings changes to the running system
- **POST** `search_vlans` — Search VLAN interfaces
- **GET** `get_vlan` — Get a VLAN interface by UUID
- **POST** `add_vlan` — Create a new VLAN interface
- **POST** `set_vlan` — Update an existing VLAN interface
- **POST** `del_vlan` — Delete a VLAN interface
- **POST** `reconfigure_vlans` — Apply VLAN interface changes to the running system configuration
- **POST** `search_vips` — Search virtual IP addresses (CARP, IP alias, proxy ARP)
- **POST** `add_vip` — Create a virtual IP address
- **POST** `set_vip` — Update a virtual IP address
- **POST** `del_vip` — Delete a virtual IP address
- **POST** `reconfigure_vips` — Apply virtual IP changes
- **GET** `get_unbound_settings` — Get Unbound DNS resolver settings
- **POST** `set_unbound_settings` — Update Unbound DNS resolver settings
- **POST** `search_unbound_host_overrides` — Search DNS host overrides (local DNS records)
- **GET** `get_unbound_host_override` — Get a DNS host override by UUID
- **POST** `add_unbound_host_override` — Create a DNS host override (local A/AAAA/MX record)
- **POST** `set_unbound_host_override` — Update a DNS host override
- **POST** `del_unbound_host_override` — Delete a DNS host override
- **POST** `search_unbound_forwards` — Search DNS forwarding domains
- **POST** `add_unbound_forward` — Create a DNS forwarding entry (forward queries for a domain to specific servers)
- **POST** `set_unbound_forward` — Update a DNS forwarding entry
- **POST** `del_unbound_forward` — Delete a DNS forwarding entry
- **POST** `search_unbound_dnsbl` — Search DNS blocklist entries
- **POST** `update_unbound_blocklist` — Trigger DNS blocklist update
- **GET** `get_unbound_service_status` — Get Unbound DNS resolver service status
- **POST** `reconfigure_unbound` — Apply Unbound DNS changes and restart
- **POST** `restart_unbound` — Restart the Unbound DNS resolver
- **GET** `get_unbound_cache` — Dump the Unbound DNS cache
- **GET** `get_unbound_stats` — Get Unbound DNS resolver statistics
- **GET** `get_unbound_local_data` — List Unbound local data entries
- **GET** `get_unbound_local_zones` — List Unbound local zones
- **POST** `search_wireguard_servers` — Search WireGuard server (tunnel) instances
- **GET** `get_wireguard_server` — Get a WireGuard server instance by UUID
- **POST** `add_wireguard_server` — Create a new WireGuard server (tunnel interface)
- **POST** `set_wireguard_server` — Update a WireGuard server instance
- **POST** `del_wireguard_server` — Delete a WireGuard server instance
- **POST** `toggle_wireguard_server` — Toggle a WireGuard server instance on/off
- **GET** `wireguard_key_pair` — Generate a new WireGuard key pair (public + private)
- **POST** `search_wireguard_clients` — Search WireGuard client (peer) configurations
- **GET** `get_wireguard_client` — Get a WireGuard client (peer) by UUID
- **POST** `add_wireguard_client` — Create a new WireGuard client (peer) — returns a new peer with generated keys
- **POST** `set_wireguard_client` — Update a WireGuard client (peer)
- **POST** `del_wireguard_client` — Delete a WireGuard client (peer)
- **POST** `toggle_wireguard_client` — Toggle a WireGuard client (peer) on/off
- **GET** `wireguard_psk` — Generate a new WireGuard pre-shared key
- **GET** `get_wireguard_status` — Get WireGuard service status
- **POST** `reconfigure_wireguard` — Apply WireGuard configuration changes
- **GET** `wireguard_show` — Show WireGuard tunnel status (equivalent to 'wg show')
- **POST** `search_openvpn_instances` — Search OpenVPN server/client instances
- **GET** `get_openvpn_instance` — Get an OpenVPN instance by UUID
- **POST** `add_openvpn_instance` — Create a new OpenVPN instance
- **POST** `set_openvpn_instance` — Update an OpenVPN instance
- **POST** `del_openvpn_instance` — Delete an OpenVPN instance
- **POST** `toggle_openvpn_instance` — Toggle an OpenVPN server or client instance on or off. Omit 'enabled' to flip current state.
- **POST** `reconfigure_openvpn` — Apply OpenVPN configuration changes
- **GET** `search_openvpn_sessions` — List active OpenVPN sessions/connections
- **GET** `search_openvpn_routes` — List OpenVPN routing table entries
- **POST** `kill_openvpn_session` — Kill an active OpenVPN session
- **POST** `search_ipsec_connections` — Search IPsec connections (phase 1)
- **GET** `get_ipsec_connection` — Get an IPsec connection by UUID
- **POST** `add_ipsec_connection` — Create a new IPsec connection
- **POST** `set_ipsec_connection` — Update an IPsec connection
- **POST** `del_ipsec_connection` — Delete an IPsec connection
- **POST** `toggle_ipsec_connection` — Toggle an IPsec connection on or off. Omit 'enabled' to flip the current state.
- **POST** `search_ipsec_children` — Search IPsec child SAs (phase 2 / traffic selectors)
- **POST** `add_ipsec_child` — Create a new IPsec child SA
- **POST** `set_ipsec_child` — Update an IPsec child SA
- **POST** `del_ipsec_child` — Delete an IPsec child SA
- **GET** `search_ipsec_phase1` — List active IPsec phase 1 (IKE) sessions
- **GET** `search_ipsec_phase2` — List active IPsec phase 2 (child SA) sessions
- **POST** `connect_ipsec` — Initiate an IPsec connection
- **POST** `disconnect_ipsec` — Disconnect an IPsec connection
- **GET** `get_ipsec_status` — Get IPsec service status
- **POST** `reconfigure_ipsec` — Apply IPsec configuration changes
- **POST** `search_ipsec_psks` — Search IPsec pre-shared keys
- **POST** `add_ipsec_psk` — Create an IPsec pre-shared key
- **POST** `set_ipsec_psk` — Update an IPsec pre-shared key
- **POST** `del_ipsec_psk` — Delete an IPsec pre-shared key
- **POST** `search_kea_dhcp4_subnets` — Search Kea DHCPv4 subnets
- **GET** `get_kea_dhcp4_subnet` — Get a Kea DHCPv4 subnet by UUID
- **POST** `add_kea_dhcp4_subnet` — Create a new Kea DHCPv4 subnet
- **POST** `set_kea_dhcp4_subnet` — Update a Kea DHCPv4 subnet
- **POST** `del_kea_dhcp4_subnet` — Delete a Kea DHCPv4 subnet
- **POST** `search_kea_dhcp4_reservations` — Search Kea DHCPv4 static reservations
- **POST** `add_kea_dhcp4_reservation` — Create a Kea DHCPv4 static reservation
- **POST** `set_kea_dhcp4_reservation` — Update a Kea DHCPv4 static reservation
- **POST** `del_kea_dhcp4_reservation` — Delete a Kea DHCPv4 static reservation
- **GET** `search_kea_leases` — Search active DHCP leases
- **GET** `get_kea_status` — Get Kea DHCP service status
- **POST** `reconfigure_kea` — Apply Kea DHCP configuration changes
- **POST** `restart_kea` — Restart Kea DHCP service
- **POST** `search_static_routes` — Search static routes
- **GET** `get_static_route` — Get a static route by UUID
- **POST** `add_static_route` — Create a new static route
- **POST** `set_static_route` — Update a static route
- **POST** `del_static_route` — Delete a static route
- **POST** `reconfigure_routes` — Apply static route changes
- **GET** `get_gateway_status` — Get gateway status (up/down, latency, loss)
- **POST** `search_gateways` — Search configured gateways. WATCH OUT for `defaultgw`: the API reports it COMPUTED, not from the config — it says whether this gateway is the CURRENTLY ACTIVE default route, not whether the "Upstream Gateway" checkbox is ticked. A gateway with defaultgw=1 in config.xml is reported as false whenever a higher-priority gateway holds the default route (lower priority number wins). That looks like data loss but is not; to read the stored flag, use get_gateway (or the config backup).

- **GET** `get_gateway` — Get a gateway by UUID
- **POST** `add_gateway` — Create a new gateway. Provide the whole item as the object param `gateway_item` (posted as gateway_item[<field>]=... form fields). SELECT fields must be BARE option values: interface = friendly key ("wan","opt1"), ipprotocol = "inet"/"inet6". Booleans are "0"/"1" strings. Required: name, interface, ipprotocol, gateway. Returns {"result":"saved"} or {"result":"failed","validations":{...}} — inspect "validations". Fields: name, descr, disabled, interface (bare), ipprotocol (bare), gateway, defaultgw, fargw, nosync, monitor, monitor_disable, monitor_noroute, monitor_killstates, monitor_killstates_priority, force_down, priority, weight, latencylow, latencyhigh, losslow, losshigh, interval, time_period, loss_interval, data_length.

- **POST** `set_gateway` — Low-level update of a gateway definition. PREFER the update_gateway composite — it does a safe read-modify-write. This primitive REPLACES the whole item: OPNsense's setBase('gateway_item','gateway_item',uuid) rebuilds the model from exactly what you post, so any field you omit is reset to its model default (monitor cleared, defaultgw/nosync/priority lost). Pass the COMPLETE item as the object param `gateway_item` (form-encoded as gateway_item[<field>]=...): use get_gateway, flatten the SELECT maps, change the fields you want, post it all back. SELECT fields must be BARE values: interface = friendly key ("wan","opt1"), ipprotocol = "inet"/"inet6". Booleans are "0"/"1" strings. Returns {"result":"saved"} on success or {"result":"failed","validations":{...}} on error. RENAMING IS IMPOSSIBLE: Gateways::validateNameChange() rejects any `name` that differs from the stored one with "Changing name on a gateway is not allowed" — no API call can rename a gateway. Create the new gateway, repoint every reference (rules, gateway groups, static routes), then delete the old one.

- **POST** `del_gateway` — Delete a gateway definition by UUID
- **POST** `reconfigure_gateways` — Apply gateway changes
- **POST** `search_gateway_groups` — Search gateway groups (failover / load-balancing groups). Each row carries the raw item/item2..item5 tier fields PLUS a `gateways` array indexed by tier that the API enriches with the live dpinger status of every member. Remember: `item` is TIER 1.

- **GET** `get_gateway_group` — Get one gateway group by UUID. Returns {gateway_group:{…}} with the tier fields as OPNsense SELECT maps ({gatewayname:{value,selected}}) — `item` is TIER 1, item2..item5 are tiers 2..5. Post them back as comma-joined BARE gateway names (see set_gateway_group), or use the update_gateway_group composite which does that flattening for you.

- **POST** `add_gateway_group` — Create a gateway group. Pass the whole item as the object param `gateway_group` (form-encoded as gateway_group[<field>]=…). Fields: name (required, [a-zA-Z0-9_-]{1,32}), item (TIER 1 — comma-separated gateway NAMES), item2/item3/item4/item5 (tiers 2..5, same format), trigger (required: down | downloss | downlatency | downlosslatency, default down), poolopts ("" = default, "round-robin", "round-robin sticky-address"), descr. Gateway names must exist (search_gateways). Apply with reconfigure_gateway_groups.

- **POST** `set_gateway_group` — Low-level update of a gateway group. PREFER the update_gateway_group composite. Pass the object param `gateway_group` with BARE, comma-joined gateway names in item (TIER 1) and item2..item5 — never the {value,selected} maps returned by get_gateway_group. The endpoint merges (omitted fields keep their value), but the tier fields are all-or-nothing per field: posting item="A" replaces the entire tier-1 membership. Apply with reconfigure_gateway_groups.

- **POST** `del_gateway_group` — Delete a gateway group by UUID. Refuses with a UserException when the group is still referenced by a firewall rule or another gateway consumer — the message names the referring object and its UUID.

- **POST** `reconfigure_gateway_groups` — Apply gateway-group changes (runs configd 'interface routes configure')
- **POST** `get_firmware_status` — Get firmware update status
- **POST** `check_firmware_updates` — Check for available firmware updates
- **GET** `get_firmware_info` — Firmware/product identity plus the list of INSTALLED plugins. The raw endpoint answers ~330 KB (900+ base packages, all 100+ known plugins and the full changelog) with no section parameter, which does not fit in a model context — so this tool projects it down to: product identity, os_version, last_check, needs_reboot, pending upgrade count, packages_installed (count only), plugins_available (count) and plugins_installed (the actual list with name/version/tier/locked/automatic/repository/comment). For one specific package use get_firmware_details; for update state use check_firmware_updates / get_firmware_status; for the changelog get_firmware_changelog.

- **GET** `get_firmware_running` — Check if a firmware operation is currently running
- **POST** `update_firmware` — Start firmware update (non-blocking, check status with get_firmware_running)
- **POST** `get_firmware_changelog` — Get changelog for a specific firmware version
- **POST** `install_firmware_plugin` — Install a plugin package
- **POST** `remove_firmware_plugin` — Remove a plugin package
- **POST** `get_firmware_details` — Get details about an installed package
- **GET** `get_system_status` — Get system status and pending notifications
- **POST** `reboot_system` — Reboot the OPNsense appliance
- **POST** `halt_system` — Shut down the OPNsense appliance
- **GET** `list_backups` — List the LOCAL configuration backup history — one entry per saved config revision with timestamp, the user who caused it and the change description. Takes no arguments: the host is pinned to "this" in the path because ToolMesh does not apply parameter defaults to path segments, so the previous {host} form failed with "missing required path parameter" whenever it was called without an explicit host. For a CARP peer's history use list_peer_backups.

- **GET** `list_peer_backups` — List the configuration backup history of a CARP peer (hostname as configured in HA sync). For the local appliance use list_backups.
- **GET** `download_backup` — Download a configuration backup as XML (returned as a broker file URL, not inline). HANDLE WITH CARE: the config XML is the one place where EVERY secret this backend otherwise redacts is present in full — certificate and CA private keys, the HA sync password, user password hashes, PSKs, RADIUS secrets. The broker URL is unauthenticated for its lifetime, so do not pass it on. Use it for restores/diffs, not to work around the redaction on the read tools.

- **POST** `revert_backup` — Revert to a previous configuration backup
- **POST** `delete_backup` — Delete a configuration backup
- **GET** `diff_backups` — Show the diff between two configuration backups (filenames from list_backups). Useful for "what changed at 12:13?" — but note the diff is over the raw config XML and can therefore expose secrets when a key or password changed between the two revisions.

- **GET** `search_services` — List all services and their status
- **POST** `start_service` — Start a system service by name (e.g. 'unbound', 'openvpn'). Use search_services to list available service names. For one instance of a multi-instance service use start_service_instance.
- **POST** `stop_service` — Stop a system service by name. Use search_services to list running services. For one instance of a multi-instance service use stop_service_instance.
- **POST** `restart_service` — Restart a system service by name. Useful after configuration changes or to recover a stalled service. For one instance of a multi-instance service use restart_service_instance.
- **POST** `start_service_instance` — Start ONE instance of a multi-instance service. The instance id is the part after the slash in search_services ids — e.g. a per-gateway dpinger monitor is name "dpinger", id "<gateway name>". The id MUST travel as a path segment; a ?id= query param is ignored by the API router.

- **POST** `stop_service_instance` — Stop ONE instance of a multi-instance service (see start_service_instance for the name/id convention).
- **POST** `restart_service_instance` — Restart ONE instance of a multi-instance service — e.g. restart a single gateway's dpinger monitor after source-address or IPv6 changes without touching the other gateway monitors (name "dpinger", id = gateway name from search_services "dpinger/<gateway>").

- **GET** `get_ids_status` — Get IDS/IPS (Suricata) service status
- **POST** `query_ids_alerts` — Query IDS/IPS alert log
- **GET** `get_ids_alert_info` — Get detailed information for a specific IDS/IPS alert by alert ID. Returns rule metadata, classification, and affected traffic details.
- **GET** `get_ids_alert_logs` — List available IDS alert log files
- **POST** `reconfigure_ids` — Apply IDS/IPS configuration changes
- **POST** `update_ids_rules` — Download and update IDS/IPS rulesets
- **POST** `drop_ids_alert_log` — Clear the IDS alert log
- **GET** `get_ids_settings` — Get IDS/IPS settings
- **POST** `set_ids_settings` — Update IDS/IPS settings
- **POST** `search_ids_rules` — Search installed IDS/IPS rules (signatures)
- **POST** `toggle_ids_rule` — Enable or disable IDS/IPS rules by Suricata SID. Accepts comma-separated SIDs for bulk toggle.
- **POST** `search_shaper_pipes` — Search traffic shaper pipes (bandwidth limiters)
- **POST** `add_shaper_pipe` — Create a traffic shaper pipe (bandwidth limit)
- **POST** `set_shaper_pipe` — Update a traffic shaper pipe (bandwidth limit)
- **POST** `del_shaper_pipe` — Delete a traffic shaper pipe
- **POST** `search_shaper_queues` — Search traffic shaper queues (within pipes)
- **POST** `search_shaper_rules` — Search traffic shaper rules (match traffic to pipes/queues)
- **POST** `add_shaper_rule` — Create a traffic shaper rule
- **POST** `set_shaper_rule` — Update a traffic shaper rule
- **POST** `del_shaper_rule` — Delete a traffic shaper rule
- **POST** `reconfigure_shaper` — Apply traffic shaper changes
- **GET** `get_shaper_statistics` — Get traffic shaper statistics
- **POST** `search_haproxy_servers` — Search HAProxy backend servers
- **POST** `add_haproxy_server` — Create an HAProxy backend server
- **POST** `set_haproxy_server` — Update an HAProxy backend server
- **POST** `del_haproxy_server` — Delete an HAProxy backend server
- **POST** `search_haproxy_backends` — Search HAProxy backends
- **POST** `add_haproxy_backend` — Create an HAProxy backend
- **POST** `set_haproxy_backend` — Update an HAProxy backend
- **POST** `del_haproxy_backend` — Delete an HAProxy backend
- **POST** `search_haproxy_frontends` — Search HAProxy frontends (public listeners)
- **POST** `add_haproxy_frontend` — Create an HAProxy frontend
- **POST** `set_haproxy_frontend` — Update an HAProxy frontend
- **POST** `del_haproxy_frontend` — Delete an HAProxy frontend
- **GET** `get_haproxy_status` — Get HAProxy service status
- **POST** `reconfigure_haproxy` — Apply HAProxy configuration changes
- **GET** `test_haproxy_config` — Test HAProxy configuration for syntax errors
- **GET** `get_haproxy_statistics` — Get HAProxy statistics counters
- **GET** `export_haproxy_config` — Export the generated HAProxy configuration file
- **GET** `diff_haproxy_config` — Show diff between staged and active HAProxy config
- **POST** `search_certificates` — Search TLS/SSL certificates (inventory). PRIVATE KEY MATERIAL IS NOT RETURNED: the raw endpoint ships `prv` (base64) and `prv_payload` (the full "-----BEGIN PRIVATE KEY-----" PEM) with every row and has no opt-out parameter — a two-certificate inventory was 23 KB of mostly RSA key. Those fields, plus csr/csr_payload, arrive redacted; an empty value still shows as empty so "does this cert have a stored key?" remains answerable, and private_key_location tells you where it lives. Keys stay SETTABLE via add_certificate / set_certificate. The certificate itself (crt/crt_payload) is public and passes through. Expiry: valid_from/valid_to are raw epoch-second strings; readable %valid_from / %valid_to are added — an expired GUI certificate is easy to miss otherwise.

- **GET** `get_certificate` — Get a certificate by UUID. Private key material (prv / prv_payload) and CSR fields are redacted on the way out — settable via set_certificate, never returned. crt/crt_payload (the public certificate) pass through; %valid_from / %valid_to are added.

- **POST** `add_certificate` — Create/import a certificate
- **POST** `set_certificate` — Update a certificate
- **POST** `del_certificate` — Delete a certificate
- **POST** `search_cas` — Search Certificate Authorities. Like the certificate search, the raw endpoint returns the CA's PRIVATE KEY (prv / prv_payload) on a plain listing call — arguably worse, since a CA key can mint new certificates. Those fields are redacted here; the CA certificate itself is public and passes through, and %valid_from / %valid_to are added.

- **GET** `get_ca` — Get a Certificate Authority by UUID
- **POST** `add_ca` — Create/import a Certificate Authority
- **POST** `set_ca` — Update a Certificate Authority
- **POST** `del_ca` — Delete a Certificate Authority
- **GET** `get_syslog_settings` — Get syslog service settings including log retention, remote destinations, and format options
- **POST** `search_syslog_destinations` — Search remote syslog destinations
- **POST** `add_syslog_destination` — Create a remote syslog destination
- **POST** `set_syslog_destination` — Update a remote syslog destination
- **POST** `del_syslog_destination` — Delete a remote syslog destination
- **POST** `reconfigure_syslog` — Apply syslog configuration changes
- **GET** `get_syslog_stats` — Get syslog service statistics
- **POST** `search_cron_jobs` — Search scheduled cron jobs
- **GET** `get_cron_job` — Get a cron job by UUID
- **POST** `add_cron_job` — Create a new cron job
- **POST** `set_cron_job` — Update a scheduled cron job
- **POST** `del_cron_job` — Delete a scheduled cron job by UUID
- **POST** `toggle_cron_job` — Toggle a scheduled cron job on or off. Omit 'enabled' to flip the current state.
- **GET** `get_hasync_settings` — Get HA synchronization settings. THE SYNC PASSWORD IS NOT RETURNED: /core/hasync/get hands out `password` in CLEAR TEXT (it is the XMLRPC credential of the peer's admin account); it is redacted here and is write-only — settable through set_hasync_settings / update_hasync_settings, never readable. An unset password still shows as empty. The multi-select fields are flattened from OPNsense's full option maps (36 entries for syncitems alone) to the comma-joined BARE selection you would post back: `syncitems` = the config sections XMLRPC-synced to the peer, `pfsyncinterface`, `pfsyncversion`. Added: "%pfsync_configured" — the model has NO `pfsyncenabled` field on 26.7 (asking for it yields null and reading that as "pfsync is off" is wrong), so this flag reports whether a pfsync interface is configured. For the live picture use get_pfsync_nodes (both creator IDs present = pfsync is actually exchanging states).

- **POST** `set_hasync_settings` — Low-level update of HA sync settings. PREFER the update_hasync_settings composite (safe read-modify-write, and the only sane way to change syncitems). Provide the fields under the object param `hasync` (posted as hasync[<field>]=...). Fields: disablepreempt, disconnectppps, synchronizetoip (peer IP), username, password, verifypeer, pfsyncdefer, pfsyncpeerip, pfsyncinterface (bare key, e.g. "lan"), pfsyncversion ("1301"/"1400"), syncitems (comma-joined section keys, e.g. "aliases,ipsec,radvd,ndpproxy" — REPLACES the whole selection). Booleans are "0"/"1" strings. Returns {"result":"saved"} or {"result":"failed","validations":{...}}.

- **POST** `reconfigure_hasync` — Apply local HA/pfsync changes (runs 'interface pfsync configure'). NOTE: this does NOT push config to the peer — for the XMLRPC config sync use synchronize_ha_services.
- **GET** `get_ha_status_version` — Check the HA peer link: returns the backup node's version info (empty/❌ when the peer is unreachable or credentials are wrong).
- **POST** `get_ha_status_services` — List HA-managed services on the peer with their sync/running state (the rows behind the HA status grid).
- **POST** `synchronize_ha_services` — HA "Synchronize and reconfigure all services": pushes the current config to the backup via XMLRPC (exec_sync), reloads templates, then restarts every HA-managed service on the peer. This is the manual config-sync trigger. Returns {"status":"ok","count":N}. Heavy — it bounces services on the backup; for a routine change, a normal save already auto-syncs.

- **POST** `search_radvd` — Search Router Advertisement (radvd) per-interface configurations
- **GET** `get_radvd` — Get a radvd interface entry by UUID. SELECT fields (interface, mode, Adv* option fields) come back as {opt:{value,selected}} maps — flatten to the bare selected key before posting back.
- **POST** `add_radvd` — Create a Router Advertisement config for an interface. Provide the whole entry as the object param `entries` (posted as entries[<field>]=...). SELECT fields are BARE values: interface = friendly key ("lan","opt1"), mode = router|unmanaged|managed|assist|stateless, AdvDefaultPreference = low|medium|high, DeprecatePrefix/RemoveAdvOnExit/RemoveRoute = ""(auto)|on|off. Booleans (enabled, dns) are "0"/"1". List fields (routes, RDNSS, DNSSL) are comma-separated. Returns {"result":"saved","uuid":...} or {"result":"failed","validations":{...}}. Then call reconfigure_radvd.

- **POST** `set_radvd` — Low-level replace of a radvd entry — PREFER the update_radvd composite (read-modify-write). setBase rebuilds the entry from exactly what you post, so any omitted field resets to its model default. Pass the COMPLETE entry as the object param `entries`: use get_radvd, flatten the SELECT maps to bare values, change what you need, post it all back. Booleans "0"/"1"; interface/mode/AdvDefaultPreference bare; routes/RDNSS/DNSSL comma-separated. Returns {"result":"saved"} or {"result":"failed","validations":{...}}.

- **POST** `toggle_radvd` — Enable/disable a radvd interface entry. enabled=1 to enable, 0 to disable.
- **POST** `del_radvd` — Delete a radvd interface entry by UUID
- **POST** `reconfigure_radvd` — Apply radvd changes (regenerates radvd.conf and reloads the daemon)
- **GET** `get_ndpproxy_settings` — Get NDP Proxy settings. Returns {ndpproxy:{general:{...}, aliases:{alias:{...}}}}. `general.upstream`/`downstream` come back as interface SELECT maps.
- **POST** `set_ndpproxy_settings` — Update NDP Proxy `general` settings (partial-merge — omitted fields keep their value). Fields (as ndpproxy.general.*): enabled, upstream (bare interface key), downstream (bare, comma list for multiple), ra ("0"/"1"), routes ("0"/"1"), carp_depend_on ("0"/"1", HA/CARP failover), cache_ttl, cache_max, cache_file, route_qps, pf_qps, pcap_timeout, debug. Booleans are "0"/"1". Then call reconfigure_ndpproxy. Returns {"result":"saved"} or {"result":"failed","validations":{...}}.

- **POST** `search_ndpproxy_aliases` — Search NDP Proxy alias entries (proxied external firewall aliases per interface)
- **GET** `get_ndpproxy_alias` — Get an NDP Proxy alias entry by UUID
- **POST** `add_ndpproxy_alias` — Add an NDP Proxy alias entry (fields as alias.*): alias.interface (bare key, blank=any), alias.alias (UUID of an EXTERNAL-type firewall alias — use search_firewall_aliases to find it), alias.description. Then call reconfigure_ndpproxy.

- **POST** `set_ndpproxy_alias` — Update an NDP Proxy alias entry by UUID (full-replace — post interface, alias and description). Then reconfigure_ndpproxy.
- **POST** `del_ndpproxy_alias` — Delete an NDP Proxy alias entry by UUID
- **POST** `reconfigure_ndpproxy` — Apply NDP Proxy changes and reload the ndp-proxy-go service
- **GET** `get_ndpproxy_status` — Get NDP Proxy service run state (running/stopped)
- **POST** `search_users` — Search OPNsense system users with optional filtering by name or email
- **GET** `get_user` — Get a system user account by UUID including group memberships and permissions
- **POST** `add_user` — Create a system user
- **POST** `set_user` — Update a system user account
- **POST** `del_user` — Delete a system user
- **POST** `search_groups` — Search system groups
- **POST** `add_user_api_key` — Generate a new API key+secret pair for a user. The key is returned as a downloadable file. IMPORTANT: the secret is not stored on the system — save it immediately.
- **POST** `search_api_keys` — Search API keys across all users — returns key ID, associated username, and creation date. Keys are managed per-user, not as standalone resources.
- **POST** `del_user_api_key` — Delete an API key by its ID — revokes API access for this key immediately
- **GET** `get_rrd_list` — List available RRD graphs (system health metrics)
- **GET** `get_system_health` — Get system health time-series data for a specific RRD metric. Use get_rrd_list to discover available metric names (e.g. cpu-usage, traffic-wan).
- **GET** `get_health_interfaces` — List interfaces available for health monitoring
- **POST** `search_tunables` — Search system tunables (sysctl)
- **POST** `add_tunable` — Create a system tunable
- **POST** `set_tunable` — Update a system tunable
- **POST** `del_tunable` — Delete a system tunable
- **POST** `reconfigure_tunables` — Apply tunable changes
- **GET** `get_dashboard` — Get dashboard widget configuration
- **GET** `get_product_info` — Get product info and update feed
- **GET** `get_menu_tree` — Get the full OPNsense menu structure

## What composite workflows does the Opnsense DADL provide?

- **FN** `update_gateway` — Safely update a gateway by UUID using read-modify-write. Pass `uuid` and a `changes` object with only the fields you want to change (e.g. {monitor:"8.8.8.8", losslow:"10", losshigh:"20"}). The composite GETs the current gateway_item, flattens its SELECT maps (interface, ipprotocol) to bare values, merges your changes, and POSTs the COMPLETE item back under the correct "gateway_item" node — so untouched fields (defaultgw, nosync, priority, weight, …) are preserved instead of being reset by OPNsense's full-replace setBase. Does NOT apply by default: call reconfigure_gateways yourself, or pass apply:true. On failure returns {ok:false, validations, raw} with the full OPNsense response so errors are visible instead of a bare {"result":"failed"}. Field names for `changes`: name, descr, disabled, interface (bare key, e.g. "wan"/"opt1"), ipprotocol ("inet"/"inet6"), gateway, defaultgw, fargw, nosync, monitor, monitor_disable, monitor_noroute, monitor_killstates, monitor_killstates_priority, force_down, priority, weight, latencylow, latencyhigh, losslow, losshigh, interval, time_period, loss_interval, data_length.

- **FN** `update_gateway_group` — Safely edit a gateway group (failover/load-balancing tiers) by UUID via read-modify-write. Pass `uuid` plus either `changes` (raw field map) or the friendlier `tiers` map {"1": ["PRIMARY_GWv6"], "2": ["BACKUP_GWv6"]} — tier numbers 1..5, values are arrays of gateway NAMES; only the tiers you name are touched. WHY THIS EXISTS: tier 1 is stored in the field `item`, NOT `item1` (tiers 2..5 are item2..item5), and every tier is a multi-select. Hand-written code that loops item1..item5, or that assumes `item` is a scalar, skips tier 1 silently — which is how a gateway rename once left a group pointing at a gateway that no longer existed. This composite reads the group, flattens the SELECT maps to bare comma-joined names, applies your changes and posts the complete item back, then reports the resulting tiers so the result can be counted against what you asked for. Does NOT apply by default — pass apply:true or call reconfigure_gateway_groups. Other writable fields for `changes`: name, trigger (down | downloss | downlatency | downlosslatency), poolopts ("" | "round-robin" | "round-robin sticky-address"), descr.

- **FN** `update_hasync_settings` — Safely update HA sync settings — especially the syncitems multi-select — via read-modify-write. Pass `changes` (scalar fields: synchronizetoip, username, password, disablepreempt, disconnectppps, verifypeer, pfsyncinterface (bare), pfsyncversion, pfsyncpeerip, pfsyncdefer) and/or `enable_syncitems` / `disable_syncitems` (arrays of section keys, e.g. ["radvd","ndpproxy"]). The composite GETs the current hasync model, derives the new syncitems selection FROM the current one (so untouched sections are preserved, never clobbered by a bare set), merges your scalar changes, and POSTs only what changed (setNodes merges). Does NOT apply pfsync (call reconfigure_hasync if you changed a pfsync field). Valid syncitems keys are whatever the appliance reports — commonly aliases, authservers, captiveportal, categories, certs, cron, dhcpd, dhcpdv6, dhcrelay, dnsforwarder, dnsresolver, hostwatch, ifgroups, ipsec, kea, lvtemplate, monit, nat, ndpproxy, ntpd, opendns, openvpn, radvd, rules, schedules, shaper, ssh, staticroutes, suricata, sysctl, syslog, syslog-ng, users, virtualip, webgui, wireguard. On failure returns {ok:false, validations, raw}. PASSWORD: `changes.password` is accepted and written normally — the sync password is write-only, not unwritable. It is never read back (get_hasync_settings redacts it), and this composite never copies it from the current model, so a plain update_hasync_settings({changes:{synchronizetoip:"…"}}) leaves the stored password untouched. A `changes` value that is itself the redaction marker is rejected rather than written.

- **FN** `update_radvd` — Safely update a radvd interface entry by UUID via read-modify-write. Pass `uuid` and a `changes` object with only the fields to change (e.g. {mode:"managed", dns:"1"}). The composite GETs the current entry, flattens its SELECT maps (interface, mode, Adv* option fields, AdvRASrcAddress) to bare values, merges your changes, and POSTs the COMPLETE entry back under the `entries` node — so untouched fields are preserved instead of being reset by setBase's full-replace. Does NOT apply by default: call reconfigure_radvd, or pass apply:true. Field names for `changes`: enabled, interface (bare), Base6Interface (bare), mode (router|unmanaged|managed|assist|stateless), dns, DeprecatePrefix/RemoveAdvOnExit/RemoveRoute (""|on|off), routes/RDNSS/DNSSL (comma lists), MinRtrAdvInterval, MaxRtrAdvInterval, AdvDefaultPreference (low|medium|high), AdvCurHopLimit, AdvDefaultLifetime, AdvLinkMTU, AdvPreferredLifetime, AdvValidLifetime, AdvRDNSSLifetime, AdvRouteLifetime, AdvDNSSLLifetime, AdvRASrcAddress, nat64prefix. On failure returns {ok:false, validations, raw}.


## Which DADLs are related to Opnsense?

- [Graylog](https://www.dadl.ai/d/graylog/) — Graylog REST API -- log search (Views/Search + legacy universal), streams, pipelines, inputs, alerts, events, dashboards, users, roles, sidecars, index management, and cluster administration. Targets Graylog 6.x.
- [Mikrotik](https://www.dadl.ai/d/mikrotik/) — MikroTik RouterOS REST API -- manage interfaces, IP addresses, routing, firewall, DHCP, DNS, PPP, queues, wireless, system configuration, users, certificates, files, logs, and diagnostics on RouterOS v7.1+ devices
- [PeeringDB](https://www.dadl.ai/d/peeringdb/) — PeeringDB v2 API -- the public peering database of networks (ASNs), Internet Exchanges, and colocation facilities: full CRUD over net, ix, fac, org, carrier, campus, point-of-contact records, and the netixlan/netfac/ixfac/carrierfac/ixlan/ixpfx relationships that map who peers where
- [Xen Orchestra](https://www.dadl.ai/d/xen-orchestra/) — Xen Orchestra REST API (XO 6.4+, current through 6.7) -- complete coverage: VMs (incl. PATCH update + snapshot revert), VM controllers, hosts (incl. full power/lifecycle actions + maintenance mode), pools (incl. add_host), storage (SR create/delete, VDI incl. PATCH update, VBD), networks (VIF incl. PATCH update + PIF/PBD), VM/VDI snapshots, VM templates, hardware (PCI/PGPU/SM), tasks, backups (jobs/logs/repositories incl. health + benchmark/restore), schedules, messages, alarms, events (SSE), RBAC v2 (users/groups/acl-roles/acl-privileges incl. assignment introspection), proxies, servers, dashboards, auth tokens, SDN traffic rules (add/delete/update), health check
- [Zammad](https://www.dadl.ai/d/zammad/) — Zammad helpdesk REST API -- tickets, articles, users, organizations, groups, roles, knowledge base, SLAs, calendars, object manager (custom fields), macros, triggers, overviews, reports, time accounting, and full admin surface. Supports X-On-Behalf-Of impersonation on every endpoint.
- [Alertmanager](https://www.dadl.ai/d/alertmanager/) — Prometheus Alertmanager API v2 -- alerts, silences, receivers, alert groups, status, and operational health

---

**Canonical URL:** https://www.dadl.ai/d/opnsense/
**Raw DADL:** https://github.com/DunkelCloud/dadl-registry/blob/main/opnsense.dadl
