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
- All routes are under the server’s own address, for example
https://strata.example.lan/api/…. Bodies and answers are JSON (UTF-8). - Writes (every method but
GETandHEAD) must send both headersContent-Type: application/jsonandX-Requested-With: strata, or they are refused with400. This is what stops another site from writing through a signed-in browser. - Roles, from least to most:
viewer(reads),contributor(proposes),approver(changes the map, reviews proposals, runs connectors),admin(accounts, tokens, connector settings, modules). A role can do everything the roles below it can. WithREAD_ONLY=trueeverybody is a viewer. - Ids (IS, assets, proposals) are 1 to 120 characters among
A-Z a-z 0-9 _ . : @ + ~ -. - Batches: send
X-Batch: <any id>on several writes to show them as one entry in the history, withX-Batch-Label: <URL-encoded label>as its title. - Sizes: one IS or asset document is 512 KB at most; an import, a proposal or a file sent to a connector is
MAX_IMPORT_MB(20 MB) at most. Bigger bodies answer413.
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).
| Method | How | via |
|---|---|---|
| Local account | HTTP 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 token | Authorization: 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 session | The strata_session cookie (__Host-strata_session over HTTPS) set by /auth/oidc/callback. Meant for browsers. | sso |
| Trusted proxy header | SSO_HEADER_USER (and SSO_HEADER_GROUPS), believed only from the addresses in SSO_HEADER_TRUST. | header |
| No accounts | When 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": [...]}| Status | Means |
|---|---|
400 | Bad body or id, missing write headers, invalid JSON |
401 | No credentials, or wrong ones |
403 | Signed in, but the role is too low, or the action is someone else’s (the four-eyes rule) |
404 | Unknown item, or {"error": "Unknown API route"}; connector routes when connectors are off |
409 | The item is not in a state that allows it (a proposal not submitted, a connector already running), or a conflict |
413 | Body too large |
429 | Too 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
| Route | Role | Request → answer |
|---|---|---|
GET /api/me | any | → {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/password | any, local account | {current, next} (10 characters at least) → {ok}. 403 if current is wrong; 400 for a single sign-on account. |
GET /healthz | none | → 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.
| Route | Role | Request → answer |
|---|---|---|
GET /api/state | viewer | → {si, assets, wan, services, rev, updatedAt} |
GET /api/state?at=REV | viewer | → the same, as it was right after revision REV |
GET /api/export | viewer | → 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} | approver | the IS document → {ok, rev}. Creates or replaces it whole. |
DELETE /api/si/{id} | approver | → {ok, rev}. Its assets stay. |
PUT /api/assets/{id} | approver | the asset document → {ok, rev}. Creates or replaces it whole. |
DELETE /api/assets/{id} | approver | → {ok, rev} |
PUT /api/wan | approver | {links: [...]} → {ok, rev}. Replaces all the links. |
PUT /api/services | approver | {services: [...]} → {ok, rev}. Replaces the shared catalogue. |
POST /api/import | approver | {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
| Route | Role | Request → answer |
|---|---|---|
GET /api/history?before=REV&limit=N | viewer | → {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/rollback | approver | {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.
| Route | Role | Request → answer |
|---|---|---|
GET /api/proposals | viewer | → {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/proposals | contributor | {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}/comment | contributor | {text} → {ok, proposal} |
POST /api/proposals/{id}/submit | contributor, author | {text?} → {ok, proposal}. 400 if it changes nothing. |
POST /api/proposals/{id}/withdraw | author or admin | → {ok, proposal} |
POST /api/proposals/{id}/reject | approver | {text?} → {ok, proposal} |
POST /api/proposals/{id}/approve | approver, 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.
| Route | Role | Request → answer |
|---|---|---|
GET /api/connectors | contributor | → {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} | admin | the settings → {ok, connector}. A masked secret sent back unchanged keeps the stored one. |
POST /api/connectors/{id}/test | approver | → {ok, message}. A failed test is 200 with ok: false and what to check. |
POST /api/connectors/{id}/run | approver; 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
| Route | Role | Request → answer |
|---|---|---|
GET /api/modules?fresh=1 | admin | → {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}/install | admin | → {ok, module}. Installs or updates from the store; the package checksum is checked. |
POST /api/modules/upload | admin | {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
| Route | Role | Request → answer |
|---|---|---|
GET /api/users | admin | → {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/tokens | admin | → {tokens: [{id, name, role, createdBy, createdAt, expiresAt, lastUsedAt}]} |
POST /api/tokens | admin | {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/sessions | admin | → {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:
| Event | Data | When |
|---|---|---|
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/eventsTreat 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.
| Route | Does |
|---|---|
GET /auth/login | The sign-in page (with single sign-on) |
GET /auth/oidc/start | Starts an OpenID Connect sign-in |
GET /auth/oidc/callback | Where the provider sends the browser back; sets the session cookie |
POST /auth/logout | Ends the session → {ok, location}: where to go next (the provider’s sign-out) |
GET /?local | Asks 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
| Field | Content |
|---|---|
name, kind | Name, and a short description (Head office) |
order | Position 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 |
services | Internet services used by this IS only, as below |
Asset document
| Field | Content |
|---|---|
site | The IS id |
name, ip, os, role, notes | Text |
type | server, dc, db, hypervisor, firewall, gateway, router, switch, workstation, printer, phone, ap, camera, plc, iot |
vlan | A VLAN id of its IS |
crit | Criticality, 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 |
contacts | As for the IS |
ext | Ids 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
| Item | Fields |
|---|---|
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:
- The manifest,
strata-module.json, with"strataModule": 1: id, name, version,main,minStrata,permissions,fields,live,file. - The code exports
test(cfg, ctx)andrun(cfg, ctx);ctxholdsstate,file,logandsdk;runresolves with{proposals, summary, report}. - The SDK,
ctx.sdk:request,expectOk,assetIndex,siteFor,vlanForIp, the IP, XML and text helpers,ConnectorError. - The store index,
index.jsonwith"strataRegistry": 1: each module’s manifest summary, its packageurlandsha256.
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
- The routes on this page under
/api/, their methods, the roles they need, the request fields they accept and the answer fields they return. - Authentication: Basic for local accounts,
Bearer stk_…tokens, the write headersContent-Type: application/jsonandX-Requested-With: strata,X-BatchandX-Batch-Label. - Status codes and the
errorkey of refusals (not its wording). - The data format: the fields listed in Data format keep their meaning. Every export file written by an earlier version (
version3 and below) keeps importing; a 1.x version may write a higherversionnumber, and still reads the lower ones. - The module API: a module written for
"strataModule": 1keeps running, with the samectxand SDK helpers; a store index with"strataRegistry": 1keeps being read. - The event names
change,proposals,connectors, andrevinchange.
What may change in a minor version
- New routes, optional request fields, answer fields, document fields, event names, roles’ abilities, SDK helpers. Write clients that ignore fields they do not know, and send back the fields they did not change: the server keeps them.
- The interface, the sign-in pages, the texts of
error, log lines, files inDATA_DIR(use the API, not the files), anything not on this page. - Security fixes: if a behaviour is a security hole, it is closed in a patch version even if a script relied on it, and the changelog says so.
Deprecation
- Something to be removed is first marked Deprecated in the changelog, with what to use instead.
- 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.
- 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.