API Reference
Everything is HTTPS + JSON under /api/v1. The full machine-readable contract lives in our OpenAPI 3.1 spec.
Download the OpenAPI 3.1 spec → — import it into Postman, Insomnia, or your codegen.
Authentication
Send your gateway API key as a bearer token. Keys are created in the dashboard or via POST /api/v1/keys, shown once, stored hashed, and revocable. Every request is scoped to the key's tenant — there is no way to read another tenant's data with a valid key.
header
Authorization: Bearer sk_live_...Endpoints
| Endpoint | Purpose |
|---|---|
POST /v1/actions/preflight | The core decision: should this action proceed? |
POST /v1/actions/execute | Preflight + forward to the upstream with the bound credential injected. |
POST /v1/passports/issue · verify · revoke | Manage Action Passports. GET /v1/passports/jwks serves public keys. |
GET /v1/approvals · POST /v1/approvals/:id/decide | List the queue; apply approve/deny/modify/escalate (FSM-gated, RBAC). |
GET/POST/PUT/DELETE /v1/policies | Policy CRUD — validated, versioned, hashed. |
POST /v1/tools/ingest · /v1/tools/diff | Register tool manifests; classify drift between versions. |
GET /v1/evidence/events · verify · export | Read the ledger, verify the hash chain, export signed bundles. |
POST/GET /v1/credentials | Vault credentials (AES-256-GCM). Secrets are write-only. |
POST/GET/DELETE /v1/keys | API-key lifecycle. Keys are returned once, stored hashed. |
GET /v1/compliance · /v1/integrations/siem | Live control evaluation; SIEM-formatted event streams. |
The preflight request
POST /api/v1/actions/preflight
{
"tool": "stripe.refund.create", // required — namespaced tool id
"resource": "stripe:charge:ch_123", // what it acts on
"args": { "amount": 4900, "currency": "usd" },
"agent_id": "support_agent", // required
"user_id": "user_456", // the delegating human
"goal": "resolve_refund_request",
"mode": "enforce", // monitor | warn | enforce | strict
"passport": "eyJhbGciOiJFZERTQSJ9...", // optional JWS
"idempotency_key": "req_8841" // safe retries
}response — 200
{
"decision": "require_approval",
"reason_code": "refund.medium_needs_approval",
"risk_tier": "high",
"approval_request_id": "apr_312",
"tool_manifest_hash": "sha256:9f2c...",
"policy_hash": "sha256:77ab...",
"evidence_event_id": "ev_5520",
"explain": {
"summary": "Policy refund_policy v3: require_approval.",
"matched_rules": ["medium_refund_needs_human"],
"next_steps": ["A reviewer must decide approval apr_312."]
}
}Note:Preflight never executes anything. Denies in
enforce/strict return HTTP 403 for hard security failures (replay, revocation, tenant mismatch) and 200 with decision: denyfor policy outcomes — so your client can distinguish "attack" from "not allowed".Reason codes
Every decision carries a stable, typed reason_code. The families:
| Family | Examples |
|---|---|
passport.* | missing, expired, revoked, replay_detected, tool_not_allowed, audience_mismatch |
tool.* | manifest_changed, reapproval_required, read_to_write_conversion, oauth_scope_broadened |
args.* | schema_invalid, amount_exceeds_limit, resource_out_of_scope, external_recipient |
approval.* | pending, denied, expired, invalid (forged hash), satisfied (verified approval) |
policy.* / domain packs | update_required; refund.small_in_scope, refund.out_of_policy, … |
Errors & rate limits
error shape
{ "error": "human-readable message", "reason_code": "passport.replay_detected" }| Status | Meaning |
|---|---|
| 401 | Missing/invalid API key, or a passport that fails verification |
| 403 | Authenticated but forbidden — RBAC, tenant scope, or a hard security deny |
| 409 | Approval state transition not allowed by the FSM |
| 429 | Per-tenant request budget exceeded (fixed window per minute) |
Use idempotency_key on preflight/execute: retrying the same key with the same payload is safe and returns the original decision instead of burning a passport use.