REST API
Everything lives under /v1 on your instance’s own address, for example https://infrainbox.example.com/v1.
The spec itself
Section titled “The spec itself”The full OpenAPI document is served live, no credential needed, at GET /v1/openapi.json. It’s always in sync with the running server — generate a client from it, or just read it.
Authentication
Section titled “Authentication”| Credential | Who uses it | Form |
|---|---|---|
| Session cookie | The web dashboard | Set by sign-in; sent automatically by the browser. |
| Device bearer token | A paired phone app | Authorization: Bearer <token>, opaque, refreshed automatically. |
| Source key | Something sending events | Authorization: Bearer iik_src_…, on POST /v1/events, a tool’s own webhook path, and the watcher’s own calls (enrolling its heartbeat, POST /v1/watchers/check-in). |
| Heartbeat token | A heartbeat ping | Authorization: Bearer iik_hb_…, or as the ping URL’s path segment. |
Signing in through an identity provider uses three endpoints: GET /v1/auth/providers lists the configured providers, POST /v1/auth/oidc/{id}/start begins a sign-in, and GET /v1/auth/oidc/{id}/callback finishes it. The start answers with the provider’s authorization URL (authorizeUrl) and sets a short-lived cookie that ties the sign-in to the browser, which the dashboard then sends to that URL; the callback always answers with a redirect (303) back into the dashboard. They are steps of a browser flow, not calls for a script. {id} is the provider’s id from the configuration.
The account links a server mails out (see Your account) each have a public endpoint that takes the link’s token in the JSON body: POST /v1/auth/password-reset and /password-reset/confirm, POST /v1/auth/email-change/confirm, and on InfraInbox Cloud POST /v1/auth/signup and /verify-email. They exist only where GET /v1/info lists the matching capability (auth.password_reset, mail, auth.signup), and answer 404 elsewhere. A refused link is always the same 403 token_invalid, whatever was wrong with it.
Workspaces and their people sit under /v1/workspaces: POST /v1/workspaces creates one with you as its owner, GET|PATCH /v1/workspaces/{id} reads and changes its settings, GET /v1/workspaces/{id}/members lists who is in it, and PATCH|DELETE /v1/workspaces/{id}/members/{userId} changes a role or removes somebody. Invitations are GET|POST /v1/workspaces/{id}/invites and DELETE /v1/workspaces/{id}/invites/{inviteId}; the reply to a new invitation carries its link once and never again. The invited person accepts with POST /v1/invites/accept, which takes the link’s token in the body. Reading is open to every member of the workspace; everything that changes a membership is for owners only, behind a recent step-up, and a refused invitation is always the same 403 invite_invalid. See Workspaces and members.
Errors
Section titled “Errors”Every error response is RFC 9457 problem details, application/problem+json:
{ "type": "https://infrainbox.app/problems/validation_failed", "title": "Validation failed", "status": 422, "detail": "1 field is invalid.", "code": "validation_failed", "requestId": "b6f1c2d4e8a94f3b", "errors": [{"pointer": "/severity", "detail": "must be one of DEBUG, INFO, SUCCESS, WARNING, ERROR, CRITICAL"}]}code is the stable, machine-readable reason; type is always https://infrainbox.app/problems/<code>; requestId matches what the server logged, for support. A validation failure (422 validation_failed) lists one errors entry per bad field, each with a JSON pointer and a plain-language reason.
Deleting and exporting
Section titled “Deleting and exporting”Three operations are destructive or export everything, so they all need a recent re-authentication (a 403 with code: reauth_required says so, and lists the factors the account can prove) and all three are written to the audit log:
| Operation | What it does |
|---|---|
DELETE /v1/me |
Closes the caller’s own account, and with it every workspace the account owned alone. 409 workspace_needs_owner names a workspace that has other members; 409 last_instance_admin refuses the only administrator. |
DELETE /v1/workspaces/{id} |
Closes one workspace, owners only. 409 last_workspace refuses the account’s only one. |
GET /v1/workspaces/{id}/export |
Streams the workspace as one JSON document (owners only), as a file attachment. It holds no secret — no key, key hash or sealed credential. One export per workspace per minute; a second answers 429. |
Both deletions end access with the answer and leave the rows to the background purge (see Security and privacy).
Pagination and sorting
Section titled “Pagination and sorting”List endpoints support two paging styles:
limit(1–200, default 50) withcursor— keyset paging: pass the previous page’snextCursorto get the next one. Best for a live feed.limitwithoffset— numbered paging (pagenisoffset = (n-1) * limit); the response carriestotal, so a table can say “1–50 of 312”.cursorandoffsetare mutually exclusive.
Most list endpoints also take sort and order (asc/desc, default desc) — the exact sort columns vary by endpoint (events sort by received, occurred, severity, source, title or disposition, for example).
The live stream
Section titled “The live stream”GET /v1/stream (session cookie or device bearer) is a Server-Sent Events stream of what changed in your workspaces — a data: line per change, so a client refetches the specific resource that changed within about a second instead of polling. Every event carries a reference only, never state. On connect, and whenever the server’s own change feed breaks, it sends resync, meaning “reload everything you show.” A comment heartbeat (: ping) arrives every 25 seconds so an idle connection isn’t mistaken for a dead one; a client that falls behind is disconnected and reconnects to a fresh resync.