The SOAR profile of the API
The full API has more than two hundred operations. A connector exposing all of
them is unusable: nobody scrolls a playbook menu looking for
natProposalsDismiss. So fourteen operations carry the SOAR tag and the
x-soar: true extension, each with a typed response schema and a response
example, and they are published as their own document:
GET /api/openapi-soar.yaml— the same document, served by your own instance. Take it from there when you can: it describes the operations that daemon actually serves, and an air-gapped instance can hand it over.openapi-soar.yaml— the fourteen. This is the file to import into your SOAR.openapi.yaml— the complete reference.
The first is derived from the second automatically and committed; it is never edited by hand, so it cannot drift.
The fourteen
Authentication is a Bearer token throughout: Authorization: Bearer obs_….
| # | Operation | Call | Permission | Use it to |
|---|---|---|---|---|
| 1 | lookupEnrichment | GET /api/enrichment/lookup?ip=… | sessions:read | Enrich up to 50 addresses in one call |
| 2 | getHostContext | GET /api/context/host?ip=…&window=… | sessions:read | See everything one host did over a window |
| 3 | getPeers | POST /api/context/peers | sessions:read | The same, filtered by direction, scope and instant |
| 4 | checkPolicy | POST /api/policy/check | rules:read | Ask whether one flow was already allowed |
| 5 | exportIndicators | GET /api/indicators | indicators:read | Pull the observed-address feed |
| 6 | listAlerts | GET /api/alerts | alerts:read | Poll or reconcile alerts |
| 7 | getAlert | GET /api/alerts/{id} | alerts:read | Resume from an alert id, with its evidence |
| 8 | setAlertStatus | POST /api/alerts/{id}/status | alerts:ack | Take ownership once a ticket exists |
| 9 | resolveIp | GET /api/query/resolve-ip?ip=… | sessions:read | Turn an address into a catalogued asset |
| 10 | getHost | GET /api/carto/hosts/{name} | cartography:read | Read that asset’s interfaces, services, groups |
| 11 | searchCartography | GET /api/carto/search?q=… | cartography:read | Find an entity by name |
| 12 | findCoveringRule | POST /api/rules/find-covering | rules:read | The raw covering-rule lookup |
| 13 | getStatus | GET /api/status | monitoring:read | Confirm which instance answered, and that it is alive |
| 14 | executeQuery | POST /api/query | nfql:execute | The escape hatch: raw NFQL |
The five that carry a playbook are worth spelling out.
1. lookupEnrichment
curl -sG https://obserae.corp.example/api/enrichment/lookup \
-H "Authorization: Bearer obs_…" \
--data-urlencode "ip=203.0.113.45" --data-urlencode "ip=10.0.0.50"
Returns {"items": [...]}, one entry per address in request order, each with
scope, asn, as_org, country, cloud, rdns, threat_intel[] and
carto{}. Repeat ip up to 50 times; a 51st is a 400 — the batch is refused,
never silently truncated, because a playbook handed half its evidence decides on
half its evidence.
2. getHostContext
curl -sG https://obserae.corp.example/api/context/host \
-H "Authorization: Bearer obs_…" \
--data-urlencode "ip=10.0.0.50" \
--data-urlencode "window=24h" \
--data-urlencode "as_of=2026-06-04T12:00:00Z"
Returns services_exposed[], talks_to[], talked_to_by[],
volume{in_bytes,out_bytes} and new_peers_in_window[]. Every peer carries
policy_covered — the flow matrix’s verdict on that exact conversation — which
is what makes the answer a segmentation report rather than a traffic dump.
window is 1h, 24h or 7d, and nothing else: it is what bounds the scan, so
an unrecognised value is refused rather than widened. as_of moves that
window without widening it. Pass the alert’s fired_at and you see what the host
was doing when it fired, not what it is doing now.
3. getPeers
{"ip": "10.0.0.50", "window": "24h", "direction": "out",
"scope": "external", "limit": 100, "as_of": "2026-06-04T12:00:00Z"}
The same question, narrowed. direction is in, out or both; scope is
internal, external or any. Peers come back largest first. limit above
1000 is clamped and truncated says so.
4. checkPolicy
{"src": "10.0.0.50", "dst": "10.20.4.11", "port": 5432, "protocol": "tcp"}
{"covered": false,
"matched_rule": null,
"nearest_rules": [{"name": "build-to-db", "why_not": "port not in rule"}],
"src_entity": {"carto": {"host": "ci-runner-prod", "group": "build"}},
"dst_entity": {"carto": {"host": "db-01", "network": "db"}}}
why_not is the field that turns a boolean into a ticket somebody can act on:
“the rule build→db exists, but 5432 is not in it” is a sentence a network owner
can answer. Both src and dst are required — a verdict about half a pair would
answer a question nobody asked — and a reference that resolves to nothing comes
back covered: false with a note, never silently allowed.
5. exportIndicators
curl -sD- https://obserae.corp.example/api/indicators?since=24h\&min_severity=high \
-H "Authorization: Bearer obs_…" \
-H "If-None-Match: \"$(cat last.etag)\""
format is plain (one address per line, # comments — what a firewall or an
RPZ ingests directly), json, csv or misp.
Always send
If-None-Match. The response carriesETagandLast-Modified, and an unchanged feed is answered304with no body. A consumer polling every five minutes without a conditional request forces a full recomputation twelve times an hour, forever, and the day someone points at the load it will be your recipe they find. Store theETag, send it back, and act only on a200.
A token for a playbook
Grant only what your playbooks call. sessions:read, alerts:read,
alerts:ack, cartography:read and rules:read cover eleven of the fourteen.
Add nfql:execute only if you use executeQuery, and monitoring:read only if
you check instance health. None of those permits any change to the cartography,
the rules or the configuration.
indicators:read is the exception, and it does not belong on the playbook
token. Put it on the enforcement device’s own credential — the firewall, the RPZ,
the blocklist that pulls the feed — so that revoking that device’s access does
not revoke your playbooks’. It is in no built-in group but admin; create a
group holding just it. See Authentication.
Every authenticated call to these operations is written to the audit log with its token, permission, operation, timestamp and status code — reads included, which is unusual and deliberate: an auditor asking who queried what about an incident gets an answer.