Strata

API referencefor Strata 0.9.

Everything the interface does goes through this HTTP API, so a script can do it too: feed the map from an inventory, open proposals from a scanner, back up the history, run connectors on a schedule. This page lists every route. What stays stable from 1.0 on is in Compatibility.

Basics

Authentication

Every route but /healthz needs credentials, the same as the interface. Which ones work depends on how the server is set up (Accounts and roles).

MethodHowvia
Local accountHTTP Basic: Authorization: Basic base64(name:password), as curl -u name:password. Refused when LOCAL_LOGIN=off, and for non-admins when LOCAL_LOGIN=admins.local
API tokenAuthorization: Bearer stk_…. Created by an admin (below) with its own role and expiry. Not available on a server with no accounts.token
Single sign-on sessionThe strata_session cookie (__Host-strata_session over HTTPS) set by /auth/oidc/callback. Meant for browsers.sso
Trusted proxy headerSSO_HEADER_USER (and SSO_HEADER_GROUPS), believed only from the addresses in SSO_HEADER_TRUST.header
No accountsWhen no account and no single sign-on is set up, every request is an admin (or a viewer with READ_ONLY). Use this on a laptop only.open

For scripts, use an API token with the lowest role that does the job: a contributor token for a tool that should only propose, an approver token for one that writes. Repeated failed sign-ins answer 429 with a Retry-After header.

Answers and errors

A write that changes the map answers {"ok": true, "rev": N}, the map’s new revision. Other writes answer {"ok": true, …} with what they made. A refused request answers a JSON object with an error text meant for people, sometimes with more keys:

HTTP/1.1 409 Conflict
{"error": "Some items changed on the map since this proposal was prepared.", "conflicts": [...]}
StatusMeans
400Bad body or id, missing write headers, invalid JSON
401No credentials, or wrong ones
403Signed in, but the role is too low, or the action is someone else’s (the four-eyes rule)
404Unknown item, or {"error": "Unknown API route"}; connector routes when connectors are off
409The item is not in a state that allows it (a proposal not submitted, a connector already running), or a conflict
413Body too large
429Too many failed sign-ins: wait for Retry-After seconds

Rely on the status and the keys, not on the wording of error, which can change.

Account

RouteRoleRequest → answer
GET /api/meany→ {user, role, auth, via, signOut, sso, readOnly, allowSelfApproval, canWrite, canPropose, canReview, canAdmin, connectors, sourceUrl}. can… say what the interface may offer; connectors: whether connectors are on.
PUT /api/me/passwordany, local account{current, next} (10 characters at least) → {ok}. 403 if current is wrong; 400 for a single sign-on account.
GET /healthznone→ ok (text). For health checks.

The map

The map is four parts: the IS documents (si, by id), the assets (by id), the links between IS (wan) and the shared Internet services (services). Their content is in Data format. Every change makes a new revision and an entry in the history.

RouteRoleRequest → answer
GET /api/stateviewer→ {si, assets, wan, services, rev, updatedAt}
GET /api/state?at=REVviewer→ the same, as it was right after revision REV
GET /api/exportviewer→ an export file (Content-Disposition: attachment): {format: "strata", version: 3, exportedAt, si, assets, wan, services}
GET /api/si/{id}, GET /api/assets/{id}viewer→ the document, or 404
PUT /api/si/{id}approverthe IS document → {ok, rev}. Creates or replaces it whole.
DELETE /api/si/{id}approver→ {ok, rev}. Its assets stay.
PUT /api/assets/{id}approverthe asset document → {ok, rev}. Creates or replaces it whole.
DELETE /api/assets/{id}approver→ {ok, rev}
PUT /api/wanapprover{links: [...]} → {ok, rev}. Replaces all the links.
PUT /api/servicesapprover{services: [...]} → {ok, rev}. Replaces the shared catalogue.
POST /api/importapprover{si, assets, wan?, services?} (an export file works as is) → {ok, rev}. Replaces the whole map; a backup is taken first.

Documents are replaced whole: to change one field, read the document, change it, send it back. To add a flow or a VLAN, change the IS document (si.flows, si.vlans).

curl -H "Authorization: Bearer $STRATA_TOKEN" \
  -H 'Content-Type: application/json' -H 'X-Requested-With: strata' \
  -X PUT https://strata.example.lan/api/assets/srv-erp-01 \
  -d '{"site":"lyon","name":"srv-erp-01","type":"server","vlan":"v-lyo-20","ip":"10.20.0.20",
       "os":"RHEL 8","role":"ERP","crit":3,
       "vulns":[{"id":"CVE-2021-44228","title":"Log4Shell","cvss":10,"sev":"crit"}]}'

History

RouteRoleRequest → answer
GET /api/history?before=REV&limit=Nviewer→ {rev, oldest, entries}, newest first, older than before if given; limit 120 by default, 500 at most. Each entry: {rev, at, user, batch, label, kind, id, op, name, site}; kind: si, assets, wan, services or all; op: created, updated or deleted. oldest: the oldest revision the history still holds.
GET /api/history/{rev}viewer→ {rev, entries}: the changes of that revision, each with its before and after document
POST /api/rollbackapprover{rev} → {ok, rev}. Puts the map back as it was at rev, as a new revision (nothing is erased); a backup is taken first.

Proposals

A proposal is a set of changes that an approver merges into the map, or not. Its changes are {si: {id: doc}, assets: {id: doc}, wan?: [...], services?: [...]}, where a document set to null deletes it. Its status goes draft → submitted → approved or rejected; a submitted proposal can be withdrawn back to draft, and editing a rejected one makes it a draft again.

RouteRoleRequest → answer
GET /api/proposalsviewer→ {proposals}, summaries, last changed first: {id, title, description, author, siteId, status, createdAt, updatedAt, submittedAt, reviewedBy, reviewedAt, mergedRev, changes (a count), comments (a count), source, connector}
POST /api/proposalscontributor{title, description?, siteId?, changes, source?} → {ok, proposal}, a draft
GET /api/proposals/{id}viewer→ {proposal, conflicts}: the whole proposal (with changes, base, the documents as they were when it was prepared, and comments), and the items changed on the map since
PUT /api/proposals/{id}contributor, author or admin{title?, description?, changes?} → {ok, proposal}. Drafts and rejected proposals only (409 otherwise).
DELETE /api/proposals/{id}author (draft, rejected, withdrawn) or admin→ {ok}
POST /api/proposals/{id}/commentcontributor{text} → {ok, proposal}
POST /api/proposals/{id}/submitcontributor, author{text?} → {ok, proposal}. 400 if it changes nothing.
POST /api/proposals/{id}/withdrawauthor or admin→ {ok, proposal}
POST /api/proposals/{id}/rejectapprover{text?} → {ok, proposal}
POST /api/proposals/{id}/approveapprover, not the author{text?, force?} → {ok, rev, proposal}. 409 with {conflicts} when the map changed under it, unless force: true. 403 for the author’s own proposal unless ALLOW_SELF_APPROVAL=true.
# a scanner that proposes instead of writing (contributor token)
curl -H "Authorization: Bearer $STRATA_TOKEN" \
  -H 'Content-Type: application/json' -H 'X-Requested-With: strata' \
  -X POST https://strata.example.lan/api/proposals \
  -d '{"title":"Weekly scan, Lyon","siteId":"lyon",
       "changes":{"assets":{"lyo-old-ftp":null}}}'

Connectors

Connector routes answer 404 when connectors are off (CONNECTORS=false). A connector’s id is cyberwatch, glpi, phpipam, paloalto or the id of an installed module.

RouteRoleRequest → answer
GET /api/connectorscontributor→ {connectors, canConfigure, canRun, store, problems?}: each connector with its fields, settings (secrets masked) and last run. A contributor sees only the file imports open to contributors.
PUT /api/connectors/{id}adminthe settings → {ok, connector}. A masked secret sent back unchanged keeps the stored one.
POST /api/connectors/{id}/testapprover→ {ok, message}. A failed test is 200 with ok: false and what to check.
POST /api/connectors/{id}/runapprover; contributor for a file import that allows it{wait?, file?: {name, text}, overrides?} → 202 {ok, started}, or with wait: true 200 {ok, run}. file imports a file instead of calling the tool; overrides: the settings the module lets one import change. 409 if already running. The older Palo Alto form {xml, fileName} still works.

A run never writes the map: it opens proposals (one per IS), which an approver reviews. run is {id, at, by, file, ok, summary, error, proposals: [{id, title, siteId}], ms}.

Module store

RouteRoleRequest → answer
GET /api/modules?fresh=1admin→ {store, uploads, registry, modules, error}: whether the store and uploads are on, the registry address, what it offers and what is installed. fresh=1 reads the registry again.
POST /api/modules/{id}/installadmin→ {ok, module}. Installs or updates from the store; the package checksum is checked.
POST /api/modules/uploadadmin{data: "<.tgz in base64>"} → {ok, module}. Only with MODULE_UPLOAD=true.
DELETE /api/modules/{id}admin→ {ok}. Removes a module installed from the store, and its settings.

Accounts, tokens, sessions

RouteRoleRequest → answer
GET /api/usersadmin→ {users: [{name, role, disabled, createdAt, passwordChangedAt}]}, local accounts
PUT /api/users/{name}admin{role?, password?, disabled?} → {ok}. Creates (a password is needed) or changes an account. You cannot remove your own admin access.
DELETE /api/users/{name}admin→ {ok}
GET /api/tokensadmin→ {tokens: [{id, name, role, createdBy, createdAt, expiresAt, lastUsedAt}]}
POST /api/tokensadmin{name, role, days} (1 to 730, 90 by default) → {ok, token, id, …}. token is shown this once only; the server keeps a hash of it.
DELETE /api/tokens/{id}admin→ {ok}. The token stops working at once.
DELETE /api/sessionsadmin→ {ok, ended}. Ends every single sign-on session.

Live events

GET /api/events (viewer) is a Server-Sent Events stream. It sends the current revision on connection, then:

EventDataWhen
change{rev}The map changed: read /api/state again
proposals{at}A proposal was created, changed or reviewed: read /api/proposals again
connectors{id, running?}A connector or module changed, or a run started or ended
curl -N -H "Authorization: Bearer $STRATA_TOKEN" https://strata.example.lan/api/events

Treat the events as a signal to read again, not as the data itself: their content may grow.

Sign-in pages

These are for browsers, not scripts.

RouteDoes
GET /auth/loginThe sign-in page (with single sign-on)
GET /auth/oidc/startStarts an OpenID Connect sign-in
GET /auth/oidc/callbackWhere the provider sends the browser back; sets the session cookie
POST /auth/logoutEnds the session → {ok, location}: where to go next (the provider’s sign-out)
GET /?localAsks for a local account’s password when single sign-on is on

Data format

An export file, GET /api/export and the body of POST /api/import share one format. Only si and assets are required; format and version say what wrote the file.

{
  "format": "strata", "version": 3, "exportedAt": "2026-10-26T09:00:00.000Z",
  "si":       { "<IS id>": { IS document } },
  "assets":   { "<asset id>": { asset document } },
  "wan":      [ { link } ],
  "services": [ { Internet service } ]
}

The export of a single IS (Export this IS (JSON)) adds "scope": "si" and holds that IS, its assets and the shared services. The interface imports it by replacing that IS only; POST /api/import always replaces the whole map, so to load one IS from a script, send its documents with PUT /api/si/{id} and PUT /api/assets/{id}.

IS document

FieldContent
name, kindName, and a short description (Head office)
orderPosition in the overview
zones[{id, name}], the plates, from the bottom up
vlans[{id, num, name, cidr, zone, pos?, place?, layout?, ext?}]. zone: a zone id; pos: order in the zone; place: [x, z] on the plate; layout: {assetId: [x, z]} set by hand in the 2D diagram
flows[{id, src, dst, port, vol, risk, note, via?, pan?}]. src, dst: an asset id, internet or svc:<service id>; port: tcp/443; vol: a relative volume; risk: flagged as risky; via: the devices crossed, in order ([]: direct; missing: guessed); pan: {rules, scope} from Palo Alto
contacts[{name, role, phone, email, dispo, src?}], who to call for the IS
business[{id, name, crit, owner, notes, assets}]: business services and the assets they run on
servicesInternet services used by this IS only, as below

Asset document

FieldContent
siteThe IS id
name, ip, os, role, notesText
typeserver, dc, db, hypervisor, firewall, gateway, router, switch, workstation, printer, phone, ap, camera, plc, iot
vlanA VLAN id of its IS
critCriticality, 1 (low) to 3 (high)
vulns[{id, title, cvss, sev, epss?, kev?, src?}]; sev: crit, high, med, low, none; epss: 0 to 1; kev: on the CISA list
apps[{name, version, ports, src?}], software and the ports it listens on
contactsAs for the IS
extIds in connected tools: {"glpi": "Computer/12", "cyberwatch": 101}

src on a vulnerability, application or contact names the connector it came from; entries without one were typed by hand.

Links and Internet services

ItemFields
Link between IS (wan){id, a, b, tech, vol, risk, note}; a, b: IS ids; tech: MPLS, VPN…
Internet service{id, name, category, hosts, ports, notes}; hosts: names, wildcards allowed; ports: ["tcp/443"]

The quickest way to see real documents is an Export JSON of the demo.

Module API

Connector modules have their own contract, described in full in MODULES.md:

Compatibility

Strata follows Semantic Versioning. Before 1.0, a minor version (0.9 → 0.10) may still break things; the changelog says how to upgrade each time. From 1.0 on, every 1.x version keeps the following working.

What stays stable in 1.x

What may change in a minor version

Deprecation

  1. Something to be removed is first marked Deprecated in the changelog, with what to use instead.
  2. It keeps working for at least one more minor version after that (deprecated in 1.3: still there in 1.4, removed in 1.5 at the earliest). Where it can, the server logs a warning when it is used.
  3. Removal happens in a minor version, under Removed and Upgrading in the changelog. Only a major version (2.0) may remove something without deprecating it first.