Drive Zedmos from anywhere.
Every Zedmos capability the console UI exposes is a REST endpoint. This guide covers the two secure ways a remote system can reach them — a direct API key, or the zedmos-console WebSocket proxy — plus a full, categorized endpoint reference.
Direct API, or the WebSocket proxy
Both paths ultimately hit the same /api/zedmos/* controllers — only the transport and credentials differ. Pick one per integration.
- Exposes
- The OPNsense firewall itself (its web server).
- Transport
- HTTPS straight to the firewall's management interface.
- Credential
- OPNsense API key + secret (HTTP Basic auth).
- Scoping
- A single OPNsense ACL — page-services-zedmos — gates every endpoint (read AND write). No built-in read-only scope.
- Best for
- A partner on a trusted segment / site-to-site VPN, or one you can IP-restrict.
- Exposes
- The Zedmos cloud hub (zedmos-backend). The firewall dials out to it.
- Transport
- Firewall opens an outbound wss:// tunnel to the hub; the partner calls the hub's REST API.
- Credential
- Hub console session token (Bearer). Firewall is enrolled with tenant_id + node_id + agent_secret (HMAC).
- Scoping
- Per-path allowlist preset on the agent, PLUS the local API-key ACL, PLUS the hub's tenant isolation.
- Best for
- SaaS / multi-tenant management, NAT'd firewalls, or anything you must not expose inbound.
Quick start
Direct API uses an OPNsense API key + secret over HTTP Basic. Unauthenticated calls get HTTP 302 (redirect to login), not 401. API-key calls are exempt from CSRF.
# 1) Create an API key in OPNsense: System > Access > Users > API keys
# The user must hold the "Services: Zedmos" privilege (page-services-zedmos).
KEY='....' # OPNsense API key
SECRET='....' # OPNsense API secret
# Read — list policy groups
curl -s -u "$KEY:$SECRET" \
https://fw.example.com/api/zedmos/policies/groups
# -> {"groups":[{"name":"Default","status":true}, ...]}
# Write — block a domain globally (POST, JSON body)
curl -s -u "$KEY:$SECRET" -H 'Content-Type: application/json' \
-d '{"type":"host","value":"badsite.com","global":true}' \
https://fw.example.com/api/zedmos/policies/block
# -> {"status":"ok"}const base = "https://fw.example.com";
const auth =
"Basic " + Buffer.from(`${process.env.ZED_KEY}:${process.env.ZED_SECRET}`).toString("base64");
async function zed(path, { method = "GET", body } = {}) {
const r = await fetch(`${base}/api/zedmos${path}`, {
method,
headers: { Authorization: auth, ...(body ? { "Content-Type": "application/json" } : {}) },
body: body ? JSON.stringify(body) : undefined,
});
if (r.status === 302) throw new Error("auth failed (redirect to login)");
return r.json();
}
const groups = await zed("/policies/groups");
await zed("/policies/block", {
method: "POST",
body: { type: "category", value: "AdultContent", group: "Strict" },
});# The partner never talks to the firewall directly — it calls the hub,
# which routes the call down the firewall's outbound socket.
curl -s -X POST https://www.zedmos.com/api/agent/proxy/http \
-H "Authorization: Bearer $CONSOLE_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"tenant_id": "69fb...",
"node_id": "6a0d...",
"method": "GET",
"url": "/api/zedmos/policies/groups"
}'
# -> {"status":"ok","response":{"status":200,"body":"{\"groups\":[...]}","error":null}}The most secure way to expose this
For an outside company you do not fully control, prefer the WebSocket proxy: no inbound port, per-path allowlist, tenant isolation, central revocation. Use the direct API only on a trusted segment / VPN, always hardened.
One ACL gates everything
page-services-zedmos covers every /api/zedmos/* endpoint, read and write. There is no per-endpoint or read-only Zedmos privilege. Scope a partner with network rules or the WS-proxy allowlist instead.
API keys bypass CSRF
API-key calls are exempt from the anti-CSRF token (only browser sessions need it on non-GET). Some endpoints also mutate on GET with no method guard — fine for keys, but lock down methods at a reverse proxy if you expose them.
Always HTTPS, always IP-restricted
The API binds to all interfaces (*:80 on the lab). For remote use, switch the WebGUI to HTTPS with a valid cert and restrict the management port by firewall rule to the partner's source IPs.
Prefer the WS proxy for third parties
It opens no inbound port, restricts exactly which paths the partner may call (allowlist), isolates tenants, and is centrally revocable (signed tombstone). The recommended channel for untrusted integrators.
Recommendation
WS proxy for untrusted third parties. Direct API only behind HTTPS + a management-port firewall rule restricted to the partner's source IPs, with a dedicated service user holding only page-services-zedmos.
The policies.json schema
Every key the partner can read via /policies/get and write via /policies/groupsave or /policies/save. Schema 1.1.0. Allowed values and defaults are taken from the document's own built-in docs.
Document structure
eval — evaluation order
evalgroups[] — the policy group object
Identity
groups[]Selectors — who the group applies to
groups[].selectorsA flow joins this group when its selectors match. Empty arrays = no constraint on that axis.
Overrides
groups[].overridesExclusions
groups[].exclusionsAllow-list overrides that exempt matching traffic from this group's blocks.
Security catalogs (threat categories)
groups[].securityEach boolean enables blocking of one threat-intel catalog. basic_mode/advanced_mode are convenience presets; the `essential{}` and `advanced{}` objects mirror the flat booleans.
Application control (L7 app-ID)
groups[].appsEncrypted-transport control
groups[].transportFirst-install default: all 'allow'. Switch to 'block' to enforce.
TLS inspection (MITM)
groups[].tlsNetwork (L3/L4) blocks
groups[].networkHard early-drops via fast-reject before rules[] runs. Use a rule with is_exception+action:allow to punch a hole.
Web filtering
groups[].webDLP & AI Gateway
groups[].web.dlpPer-group only — globals.web.dlp is a TEMPLATE the engine ignores. First-install default: OFF.
File / AV scanning
groups[].file.scanDNS control
groups[].dnsETA · Identity · Geo · Risk · TI · IDS
groups[].{eta,identity,geo,risk,ti,ids}Action handlers
groups[].actionsConfigured at group level; a rule references them via its `action`. Most fire only on a DROP-class decision (pair with action:drop). Placeholders $src, $dst, $rule_id.
SD-WAN routing
groups[].routingRules (ordered, fine-grained)
groups[].rules[]Optional per-group ordered rule list evaluated after early-drops. Each rule matches on fields and fires one action.
// POST /api/zedmos/policies/groupsave — Content-Type: application/json
// Upsert one group (only the keys you send are changed; rest is preserved).
{
"name": "Strict",
"status": true,
"description": "Locked-down VLAN",
"selectors": { "vlans": [30], "direction": "any" },
"overrides": { "block_all": false, "block_untrusted": true, "schedule": "work-hours" },
"security": { "basic_mode": "high", "malware_virus": true, "phishing": true },
"apps": { "categories_block": ["adultcontent", "gaming"] },
"transport":{ "quic_strategy": "block", "doh_strategy": "block" },
"tls": { "enable_inspection": true, "bump_mode": "force", "min_version": "1.2" },
"dns": { "block_domains": ["coin-hive.com"], "dga": "block", "tunnel": "block" },
"web": { "dlp": { "enable": true, "mode": "regex_only", "action": "block",
"presets": ["cc_luhn","iban","email_pii"] } },
"file": { "scan": { "mode": "block", "action": "block", "engines": ["clamav"] } },
"ids": { "mode": "prevent", "block_max_priority": 2 },
"rules": [
{ "is_exception": true, "action": "allow", "mode": "any",
"dst_domain": ["intranet.corp"], "comment": "always allow intranet" }
]
}globals — defaults, catalogs, engine knobs
Engine knob registry
globals.engineOperator-tunable hot-path knobs. Precedence: value here > env DG_* > hardcoded default. Atomic publish on policy reload.
Global catalogs & defaults
globals.{security,ti,quarantine,schedules,exclusions}// POST /api/zedmos/policies/globals — set global blacklists / actions
{
"exclusions": { "domains": ["windowsupdate.com"], "src_cidrs": ["10.0.0.0/8"] },
"ti": { "domain_block": ["evil.example"], "ip_block": ["203.0.113.7"] },
"schedules": [
{ "name": "work-hours", "start": "08:00", "end": "18:00",
"days": ["Mon","Tue","Wed","Thu","Fri"] }
]
}Every category
Grouped exactly like the console: Policies first, then Settings, Notifications, Reports, Live and the rest. Paths are relative to each category base.
/api/zedmos/policiesThe flagship surface. Drives policies.json — groups, globals, security catalog, app categories, DLP presets and threat-intel.
/api/zedmos/settingsThe largest surface (~62 actions): TLS/CA, interfaces & workers, IDS/ETA/TI, writerd & storage, AD/identity, cloud console, device recognition and traffic control.
/api/zedmos/notificationsAlert feed + dispatcher. The only controller with its own authorization gate (canModify: session user, or loopback/same-host). Config in notifications.json.
/api/zedmos/reportsRead-only chart endpoints over the flow DB. All force POST in dispatch() so the UI can XHR-GET them; with an API key call either method. Common params: hours(1-168), since/until(ms), mode(session/packet/volume). Most return {labels, values}.
/api/zedmos/liveNear-real-time feeds. Poll with a since cursor (the UI uses polling — there is no WebSocket/SSE on the box). Common params: hours(6), since(ms), limit(1000, 10-2000), plus smart filters (filters_<ep>, sf_*).
/api/zedmos/dashboardAggregate KPIs, system telemetry (CPU/temp/disk), feature status, and service / update control.
/api/zedmos/deviceDevice inventory + lifecycle. Uses positional path args for <id> actions, e.g. /device/trust/42.
/api/zedmosAdditional controllers, summarized. Each follows the same /api/zedmos/<slug>/<command> convention and the same auth.
Path B, in depth
The firewall dials out to the hub; the partner calls the hub's REST API, which relays the call down the socket. The agent enforces a per-request allowlist before touching the local API.
Hub REST API — what the partner calls
Allowlist presets (the per-request boundary)
core-read-only~364 read-only GET endpoints across 24 modules. Safe default.firewall-adminFull read+write firewall/* + read-only core/diag/interfaces.network-adminFull interfaces/*, routes/*, routing/* + read-only core/diag.vpn-adminFull ipsec/*, openvpn/*, wireguard/* (on disk; select via custom).ids-monitorFull ids/* + read-only core/diag (on disk; select via custom).full-adminEverything, every module. "Equivalent to bypass." Do not ship to partners.None of the shipped presets include /api/zedmos/*. For a policies integration, use a custom allowlist:
# Custom allowlist for a policies integration (config.json patterns / preset=custom)
/api/zedmos/policies/*
/api/zedmos/settings/*
# optional read-only telemetry
/api/zedmos/dashboard/*
/api/zedmos/live/*
/api/zedmos/reports/*Message protocol (reference)
Hub → agent: {"type":"http","requestId","method","url","body","timeoutMs"}
Agent → hub: {"type":"httpResponse","requestId","status","body","error"}
Handshake: hub sends a challenge nonce; agent replies with HMAC-SHA256(agent_secret, "tenant|node|ts|nonce"); hub replies hello_ack. Revocation via a signed tombstone.Full prose reference with every endpoint and parameter: docs/ZEDMOS_API_REFERENCE.md in the repo.