Opnsense MCP Server — 343 tools
contains code
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
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
How do you configure the Opnsense DADL?
- Log in to OPNsense web UI as admin
- Navigate to System → Access → Users
- Edit the user you want to create API credentials for (or create a dedicated API user)
- Scroll down to 'API keys' section and click the '+' (add) button
- A key/secret pair is generated and downloaded as an apikey.txt file
- The file contains: key=<your-api-key> and secret=<your-api-secret>
- IMPORTANT: The secret is NOT stored on the system — save it securely
- 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
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:
- 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? ⚠ contains code
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}.