MAQPNADocs

Admin API

Every /v1 route of the MAQPNA gateway's admin API - approvals, audit, sessions, costs, policies, servers, kill switch, taint, memory, posture and more - with roles, parameters, examples and errors.

The admin API is the /v1/* HTTP API of the MAQPNA gateway. The CLI, the console and MAQPNA Desk use it to list and decide approvals, read and verify the audit ledger, inspect sessions, costs and budgets, test policies, revoke agents and more. This page lists every route the gateway serves, with the role it needs, its parameters, an example and its errors.

Use it when you script against the gateway directly, build your own dashboard, or need to know exactly what a CLI command calls. Data-plane calls from agents (/mcp, /a2a, /llm) are on Gateway data-plane API.

Source: cmd/maqpna-gateway (adminauth.go holds the role table; each register*Routes function adds its routes).

Contents#

Group Routes
Authentication GET /v1/whoami, GET /v1/auth/config
Approvals GET /v1/approvals, GET /v1/approvals/{id}, POST /v1/approvals/{id}/approve, POST /v1/approvals/{id}/deny, notifier callbacks
Audit ledger GET /v1/audit/verify, /records, /export, /jwks, /checkpoints, /sinks, /anchors, POST /v1/audit/anchors:anchor, /v1/audit/forward*
Evidence GET /v1/evidence/register
Sessions GET /v1/sessions, GET /v1/sessions/{id}/timeline, /cost, /taint, POST .../taint:clear, POST /v1/sessions/self/result, POST .../llamay:checkpoint, POST .../llamay:restore
Session-scoped endpoints /v1/sessions/{id}/exec, /files, /evidence, /liveview, /tools:authorize, /infra:*
Costs and budgets GET /v1/costs, GET /v1/costs/focus, GET /v1/budgets, POST /v1/usage/sandbox
Policies and taint GET /v1/policies, POST /v1/policies:evaluate, GET /v1/taints
MCP servers and A2A routes GET /v1/servers, GET /v1/servers/{ns}/{name}/tools, POST /v1/servers/{ns}/{name}/pins:export, GET /v1/a2a
Kill switch GET /v1/revocations, POST /v1/revocations, DELETE /v1/revocations/{id}
Connected accounts /v1/connect*, GET /v1/connected-accounts, DELETE /v1/connected-accounts/{user}/{provider}, GET /v1/vault/keys, POST /v1/vault:rekey
Memory, tenants and captures GET /v1/memory/stores, DELETE /v1/memory/users/{user}, GET /v1/tenants, GET /v1/captures:export
Operations GET /v1/posture, GET /v1/license, GET/POST /v1/admin/config:bundle, resource API /apis/maqpna.com/v1alpha1/..., /debug/pprof/

Conventions#

Base URL and listener#

The admin API is served on the gateway's data listener (default :8080). When adminListen is set, every admin route answers 404 on the public listener and is served only on the admin listener, which should be bound to a private address. Examples on this page use $MAQPNA_GATEWAY_URL and an access token in $TOKEN.

Authentication#

Send Authorization: Bearer <access token>. What the gateway accepts depends on adminAuth.mode:

Mode Accepted tokens
token (default) The static MAQPNA_ADMIN_TOKEN. It holds every role. Without it set, the admin API answers 503 admin API disabled
oidc Access tokens from your identity provider (adminAuth.oidc). Roles come from adminAuth.roles and adminAuth.namespaceRoles (groups, or user:<name> entries)
both OIDC, plus the static token as break-glass (logged as a warning, recorded with issuer urn:maqpna:break-glass)

Tenant admin tokens (multi-tenant installations) hold namespace roles in the tenant's namespaces only. See Write policies and Human approvals for how roles are mapped.

Roles#

Role Grants
viewer Read approvals and sessions
approver Decide approvals (in its namespaces)
auditor Audit ledger, evidence, costs, budgets, policies, servers, taint, memory, posture, licence
admin Everything except deciding approvals
killswitch Revocations, revoking connected accounts
audit-forwarder Push ledger segments to an audit collector

A role can be global or per namespace. Routes marked ns-filtered accept namespace roles and return only what those namespaces may see. Routes marked ns-query accept namespace roles when you pass ?namespace=<one of your namespaces>. Any /v1/* route not listed with other roles needs admin (fail closed).

Errors#

Admin errors are {"error": "<message>"} with an HTTP status. (The structured envelope of the data plane is Planned for the admin API's v1beta1.)

Status Meaning
400 Bad parameter or body
401 Missing or invalid token (WWW-Authenticate: Bearer realm="maqpna-admin"); messages: missing bearer token, invalid admin token, token expired, token audience mismatch, token issuer mismatch, identity provider keys unavailable
403 forbidden: requires role <roles> (with (namespace-scoped: pass ?namespace=<one of your namespaces>) on ns-query routes)
404 Not found, or the feature is not configured
409 Conflict (approval already decided, ambiguous session name)
503 Admin API disabled, or a store is unavailable

Timestamps are RFC 3339 in UTC. since and until filters take RFC 3339.

Authentication#

GET /v1/whoami#

Who you are and which roles you hold. Any authenticated caller.

curl -sS "$MAQPNA_GATEWAY_URL/v1/whoami" -H "Authorization: Bearer $TOKEN"
{
  "mode": "oidc",
  "principal": {
    "kind": "oidc",
    "user": "[email protected]",
    "subject": "6f1c0e2a",
    "email": "[email protected]",
    "issuer": "https://sso.example.eu/realms/acme",
    "groups": ["platform"],
    "roles": ["auditor"],
    "namespaceRoles": {"team-a": ["approver"]}
  },
  "approverFromToken": true
}

principal.kind is oidc or token; breakGlass is true for the static token in mode both. CLI: maqpna whoami.

GET /v1/auth/config#

The public login settings the console uses. No authentication.

{"mode":"oidc","oidc":{"issuer":"https://sso.example.eu/realms/acme","audience":"maqpna","clientId":"maqpna-console","scopes":["openid","profile","email"]}}

oidc is absent in mode token; clientId and scopes only when a client ID is configured.

Approvals#

An approval is a tool call a rule held for a human decision: pending, then approved, denied or expired.

GET /v1/approvals#

Roles: viewer, approver, auditor or admin; ns-filtered.

Query Meaning
status pending, approved, denied or expired (empty = all)
namespace Only this namespace
curl -sS "$MAQPNA_GATEWAY_URL/v1/approvals?status=pending" -H "Authorization: Bearer $TOKEN"
{
  "approvals": [
    {
      "id": "apr-3f9c",
      "status": "pending",
      "namespace": "team-a",
      "agent": "coder",
      "session": "fix-4821",
      "user": "[email protected]",
      "server": "echo",
      "tool": "delete_resource",
      "argsSha256": "9b1d…",
      "policy": "default-agent-policy",
      "rule": "destructive-needs-human",
      "reason": "destructive",
      "createdAt": "2026-10-18T14:05:00Z",
      "expiresAt": "2026-10-18T14:10:00Z",
      "argsPreview": "{\"id\":\"prod-db\"}",
      "argsPreviewMode": "redacted",
      "requiredApprovals": 1,
      "canDecide": true
    }
  ]
}

Other fields of an approval, when set: args (only with approvalPreview.mode: full, secrets masked), subject, minApprovers, approverGroups, approverGroupsAlso, votes, approver, approverIss, note, decidedAt, consumed, toolDescription, toolDefSha256, userConfirm, userConfirmation, userConfirmFallback, approvedBy. canDecide and decideBlocked are computed for the caller; decideBlocked is one of missing_role, self_approval, duplicate_approver, approver_not_in_group, not_pending.

Errors: 400 invalid status. CLI: maqpna approvals list.

GET /v1/approvals/{id}#

Roles: as above. Returns one approval in the same shape. 404 approval not found also when the caller may not see it. CLI: maqpna approvals get.

POST /v1/approvals/{id}/approve and POST /v1/approvals/{id}/deny#

Cast a vote. Roles: approver in the approval's namespace (the admin role alone does not decide approvals; the static admin token holds every role).

Body field Meaning
approver Approver name. Required for the static token; ignored for OIDC callers (the verified identity is used)
note Free text, shown to the agent on a deny
argsSha256 Optional: the arguments hash you reviewed. If it differs from the approval's, the vote is refused (409 args_mismatch)
curl -sS -X POST "$MAQPNA_GATEWAY_URL/v1/approvals/apr-3f9c/deny" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"note":"use the staging database"}'

The response is the updated approval. With four-eyes approval (minApprovers > 1) the status stays pending until enough distinct approvers voted.

Status error Meaning
400 approver is required / invalid JSON body
403 forbidden: requires role approver in namespace <ns> / approver_not_in_group
404 verdict must be approve or deny / approval not found
409 self_approval, duplicate_approver, user_confirmation_required, args_mismatch, already decided or expired (with the current approval)

CLI: maqpna approvals approve, maqpna approvals deny.

POST /v1/approvals/{id}/callback/{notifier} and POST /v1/notify/callback/{notifier}#

Interactive approve and deny buttons from chat. Auth: the chat platform's signature, not a bearer token: the Slack signing secret (Slack timestamp and signature headers), or for Microsoft Teams a callbackOIDC bearer token or an X-Maqpna-Signature from a relay. Callbacks are replay-protected; the chat user must be mapped in notifiers[].userMap and hold the approver role for the namespace. Slack always gets 200 with an ephemeral message. Errors: 401 invalid callback signature, 404 no interactive callback configured for this notifier, 409 replayed callback. See Notifications.

Audit ledger#

GET /v1/audit/verify#

Recompute the hash chain. Roles: auditor or admin (global).

{"ok":true,"count":12480,"headSeq":12480,"headHash":"5d2e…"}

A broken chain adds firstBadSeq and error. CLI: maqpna audit verify.

GET /v1/audit/records#

Roles: auditor or admin; ns-query.

Query Meaning
session, namespace, agent, user Filters
since, until RFC 3339
limit Non-negative integer
curl -sS "$MAQPNA_GATEWAY_URL/v1/audit/records?namespace=team-a&limit=2" -H "Authorization: Bearer $TOKEN"
{"records":[{"seq":12479,"time":"2026-10-18T14:05:09Z","session":"fix-4821","namespace":"team-a","agent":"coder","user":"[email protected]","server":"echo","tool":"delete_resource","argsSha256":"9b1d…","decision":"deny","rule":"destructive-needs-human","reason":"approval_denied by [email protected] (apr-3f9c)","approver":"[email protected]","latencyMs":0,"costUsd":0,"prevHash":"…","hash":"…"}]}

Records may also carry schema and ext (string map of extra facts). See Ledger format.

GET /v1/audit/export#

An evidence bundle (JSON, Content-Disposition: attachment; filename="maqpna-evidence.json") for the same filters as /records. With signed checkpoints configured, the bundle embeds them and their keys. Roles: auditor or admin; ns-query. CLI: maqpna audit export.

GET /v1/audit/jwks#

The public keys of signed audit checkpoints (application/jwk-set+json). No authentication: verification needs no secret. 404 when auditCheckpointKey is not configured.

GET /v1/audit/checkpoints#

Roles: auditor or admin. {"checkpoints": [...], "jwks": {...}}; 404 without auditCheckpointKey.

GET /v1/audit/sinks#

SIEM sink delivery state. Roles: auditor or admin.

{"headSeq":12480,"sinks":[{"name":"splunk","type":"splunk-hec","format":"json","cursor":12478,"lag":2,"delivered":12478,"errors":0,"lastError":""}]}

CLI: maqpna audit sinks.

GET /v1/audit/anchors and POST /v1/audit/anchors:anchor#

Transparency-log anchors of the ledger head. GET (auditor or admin) returns {"log": "<log URL>", "anchors": [...]}. POST ...:anchor (admin) anchors now and returns {"anchored": true, "anchor": {...}}, or {"anchored": false, "lastSeq", "headSeq"} when nothing new needs anchoring. 404 when transparencyLog is not configured; 502 when the log is unavailable. CLI: maqpna audit anchor. See Transparency log.

Audit forwarding (offline edges and collectors)#

Route Roles Purpose
GET /v1/audit/forward/status auditor, admin Edge spool, forwarder and revocation freshness (edge), and per-edge state (collector). 404 when neither offline nor auditCollector is configured
POST /v1/audit/forward audit-forwarder, admin A collector accepts one ledger segment. 200 stored or replay; 409 gap, fork or another ledger; 413 too large; 422 signature, chain or unknown edge; 400 malformed. The body is the collector's result either way
POST /v1/audit/forward:seal admin Seal the edge's open segment: {"sealed": [...], "sealedSeq": n}
POST /v1/audit/forward:prune admin Delete acknowledged segments: {"pruned": n, "bytes": n, "ackedSeq": n}

CLI: maqpna audit forward. See Offline edge.

Evidence#

GET /v1/evidence/register#

The DORA Art. 28 register of ICT third-party services (MCP servers, model routes) that agents actually used, built from the ledger. Roles: auditor or admin; ns-query.

Query Meaning
since, until, namespace, agent Filters
format csv for CSV; JSON otherwise
includeUnused true also lists configured servers and routes no agent used

CLI: maqpna evidence register. See Evidence packs.

Sessions#

GET /v1/sessions#

Sessions seen in the ledger, plus sessions waiting on an approval. Roles: viewer, approver, auditor or admin; ns-filtered (passing namespace needs a role there).

Query Meaning
namespace, agent, user, since, until Filters
tainted true or false
result Succeeded, Failed or none
limit 1 to sessions.listMaxSessions, default 50
cursor nextCursor of the previous page
curl -sS "$MAQPNA_GATEWAY_URL/v1/sessions?namespace=team-a&limit=1" -H "Authorization: Bearer $TOKEN"
{
  "hasMore": true,
  "nextCursor": "eyJ0IjoiMjAyNi0xMC0xOFQxNDowNTowOVoiLCJrIjoidGVhbS1hL2ZpeC00ODIxIn0",
  "remaining": 14,
  "sessions": [
    {"namespace":"team-a","session":"fix-4821","agent":"coder","user":"[email protected]","firstSeen":"2026-10-18T14:01:12Z","lastSeen":"2026-10-18T14:05:09Z","calls":{"allow":9,"deny":1},"modelCalls":4,"costUsd":0.0421,"taints":[],"pendingApprovals":0}
  ]
}

Rows may also carry outcome ({result, summary, at, seq, annotated}) and parentSession. Sorted by lastSeen, newest first. CLI: maqpna session list.

GET /v1/sessions/{id}/timeline#

One session as an ordered story of calls, approvals, taint, forks, revocations and its result. Roles: viewer, approver, auditor or admin in the session's namespace.

Query Meaning
namespace Required when the session name exists in several namespaces you can see (409 lists them)
order asc (default) or desc
limit 1 to sessions.timelineMaxItems, default 100
after / before Cursor (a ledger seq) for asc / desc
kind Comma-separated: tool_call, model_call, approval, taint, fork, revocation, exec, egress, a2a, result

The response has session, namespace, order, items, summary, itemCount, hasMore, nextCursor, and when known agent, user, firstSeen, lastSeen, reported, fork, taints, outcome, traceUrlTemplate. Each item has seq, time, kind and, when set, server, tool, decision, rule, reason, approver, latencyMs, costUsd, model, tokensIn, tokensOut, flags, ext, traceId, traceUrl, captureRef, approval. Errors: 400 invalid session id, 404 session not found, 409. CLI: maqpna session describe. See Sessions.

GET /v1/sessions/{id}/cost#

Metered totals of one session. Roles: auditor or admin (global). namespace selects the namespace; without it the name must be unambiguous (409 with sessions). energy=1 adds energy estimates when an energy model is configured.

{"session":"fix-4821","namespace":"team-a","found":true,"totals":{"toolCalls":10,"modelCalls":4,"inputTokens":5210,"outputTokens":880,"sandboxSeconds":0,"toolCostUsd":0,"tokenCostUsd":0.0421,"sandboxCostUsd":0,"costUsd":0.0421},"budget":{"scope":"session","key":"team-a/fix-4821","spentUsd":0.0421,"limitUsd":5,"exceeded":false}}

CLI: maqpna session cost.

GET /v1/sessions/{id}/taint and POST /v1/sessions/{id}/taint:clear#

GET (auditor or admin; optional namespace) returns {"session", "namespace", "tainted", "labels", "states"}.

POST ...:taint:clear (admin) removes labels after a false positive. It is audited (rule taint/admin-clear).

Body field Meaning
namespace Only this namespace
labels Labels to clear; empty = all (*)
actor Who clears. Required for the static token; OIDC callers are recorded by verified identity
note Appended to the audit reason
{"session":"fix-4821","cleared":[{"namespace":"team-a","removed":["web"]}]}

CLI: maqpna taint get, maqpna taint clear.

POST /v1/sessions/self/result#

An agent reports how its run ended. Auth: the session token of that session (not an admin token). The result is written to the AgentSession as annotation maqpna.com/result and to the ledger; the summary passes DLP first.

Body field Meaning
result Succeeded or Failed
summary Optional, valid UTF-8, at most 1 KiB
curl -sS -X POST "$MAQPNA_GATEWAY_URL/v1/sessions/self/result" \
  -H "Authorization: Bearer $(cat "$MAQPNA_TOKEN_FILE")" -H "Content-Type: application/json" \
  -d '{"result":"Succeeded","summary":"Opened PR 4822 with the fix."}'
{"session":"fix-4821","namespace":"team-a","result":"Succeeded","summary":"Opened PR 4822 with the fix.","redacted":false,"annotated":true,"at":"2026-10-18T14:20:00Z","auditSeq":12490}
Status Meaning
400 Bad result, summary too long or not UTF-8, unknown field
401 Missing or invalid token
403 Token names no hosted session, or the session is revoked
413 Body too large
422 Summary blocked by DLP (reason, detector in the body)
503 Ledger unavailable, or the annotation could not be written (Retry-After: 2; the result is in the ledger)

POST /v1/sessions/{id}/llamay:checkpoint and :restore#

Save and restore the llamay inference state of a session (the model half of a snapshot). Roles: admin.

  • checkpoint body {"namespace": "team-a"} returns {"session", "namespace", "checkpoints": [{"route", "upstream", "model", "context", "agent", "user", "file", "sha256", "bytes"}]}.
  • restore body {"namespace", "parentSession", "checkpoints": [...]} restores into a new session and returns {"session", "parentSession", "restored": [{"route", "model", "context", "from"}]}. Refused (403) when either session is revoked; 400 when restoring into the checkpointed session; 409 on a conflict; 502 when llamay fails.

See Llamay models.

Session-scoped endpoints#

These routes are called by a session's own agent with its session token, or by a person with an admin token who passes ?namespace=<session namespace> (and optionally agent). The token must be for session {id}. Each call runs the full pipeline (scope, DLP, policy, approvals, budgets, kill switch, audit) for a built-in server.

Route Enabled by Agent scope Admin roles
POST /v1/sessions/{id}/exec exec.enabled tools:maqpna-exec admin
GET /v1/sessions/{id}/files exec.enabled tools:maqpna-exec admin
GET /v1/sessions/{id}/files/{path...} exec.enabled tools:maqpna-exec admin
POST /v1/sessions/{id}/evidence?kind= always — admin
GET /v1/sessions/{id}/evidence always — auditor, admin
GET /v1/sessions/{id}/evidence/{sha} always — auditor, admin
POST /v1/sessions/{id}/liveview:ticket?namespace= liveView.enabled — (admin token only) approver, admin
GET /v1/sessions/{id}/liveview/{path...} liveView.enabled — (ticket or admin token) approver, admin
POST /v1/sessions/{id}/tools:authorize always tools:maqpna-client admin
POST /v1/sessions/{id}/infra:authorize infra.enabled tools:maqpna-infra admin
POST /v1/sessions/{id}/infra:start infra.enabled tools:maqpna-infra admin
GET /v1/sessions/{id}/infra/authorizations/{aid} infra.enabled tools:maqpna-infra admin
POST /v1/sessions/{id}/infra/authorizations/{aid}/result infra.enabled tools:maqpna-infra admin

Common answers for the governed ones: 202 with {"status": "approval_pending", "approvalId", "expiresAt", ...} when a rule holds the call (retry with X-Maqpna-Approval-Id; send X-Maqpna-Async: 1 to not wait), 403 with {"error", "data"} when denied, 401 / 403 for a token of another session or a missing scope.

POST /v1/sessions/{id}/exec#

Run code in the session's code interpreter (policy server maqpna-exec, tool exec). Body {"code", "language", "timeoutSeconds"}; language defaults to python, timeoutSeconds to 60 (at most exec.maxTimeoutSeconds). With Accept: text/event-stream the output streams as SSE events stdout, stderr, result and error; otherwise one JSON object {"stdout", "stderr", "outputSha256", ...} with the sandbox's result fields (for example exitCode), truncated when the output exceeded exec.maxOutputBytes, and 403 with error when DLP withheld the output.

curl -sS -X POST "$MAQPNA_GATEWAY_URL/v1/sessions/fix-4821/exec" \
  -H "Authorization: Bearer $(cat "$MAQPNA_TOKEN_FILE")" -H "Content-Type: application/json" \
  -d '{"code":"print(2+2)","language":"python"}'

GET .../files lists the session's artefacts; GET .../files/{path} downloads one (application/octet-stream). CLI: maqpna session exec, maqpna session files.

Evidence objects and live view#

POST .../evidence?kind=screenshot|recording|har|other stores the body (image/png, image/jpeg, image/webp, video/webm, video/mp4, application/json, text/plain) write-once and audits it; it answers 201 with {"sha256", "key", "bytes", "contentType", "kind", "store"}. GET .../evidence lists them from the ledger; GET .../evidence/{sha} downloads one from the local store (404 for WORM objects, which are read from the bucket by key).

POST .../liveview:ticket returns {"ticket", "expiresAt", "url"} and sets an HTTP-only cookie; open url to watch the session's browser over noVNC. Live view is read-only. CLI: maqpna session liveview.

POST /v1/sessions/{id}/tools:authorize#

Govern a tool that a client application runs itself (a desktop agent's shell or file tools). Body {"client", "tool", "args"}; client matches [a-z0-9][a-z0-9-]{0,31}, tool matches [A-Za-z0-9._:-]{1,128}. The call is evaluated as server maqpna-client, tool <client>__<tool>, and recorded with executedBy=client.

{"decision":"allow","server":"maqpna-client","tool":"desktop__shell","rule":"allow-read-only","policy":"desktop-policy","reason":"authorized for client execution"}

Infrastructure plans#

POST .../infra:authorize takes {"operation": "apply"|"plan", "tool": "terraform"|"tofu", "planSha256", "plan"} (plan is the output of terraform show -json). An allowed apply returns an authorizationId and expiresAt that the wrapper redeems once with POST .../infra:start ({"authorizationId", "planSha256"}), polls with GET .../infra/authorizations/{aid}, and closes with POST .../result ({"exitCode", "interrupted"}). A revocation makes start and status answer 403. 413 when the plan exceeds infra.maxPlanBytes. See AI SRE infrastructure agent.

Costs and budgets#

GET /v1/costs#

Roles: auditor or admin. Without scope or window: the metering snapshot {"takenAt", "currency", "total", "sessions", "agents", "namespaces", "users"}, each a map of totals (toolCalls, modelCalls, inputTokens, outputTokens, sandboxSeconds, toolCostUsd, tokenCostUsd, sandboxCostUsd, costUsd).

With scope (agent, namespace, user, total; default total), window (day or month; default month) and optional at (RFC 3339):

{"scope":"agent","window":"day","period":"2026-10-18","start":"2026-10-18T00:00:00Z","end":"2026-10-19T00:00:00Z","timezone":"UTC","currency":"EUR","totals":{"team-a/coder":{"costUsd":3.12}}}

CLI: maqpna costs, maqpna costs summary.

GET /v1/costs/focus#

FOCUS 1.2 cost rows (FOCUS is the FinOps cost-export format) built from the ledger. Roles: auditor or admin.

Query Meaning
since, until Default: the current month
namespace, agent, session, user Filters
period Row granularity, a duration that divides 24 h (1h)
format csv (default, text/csv, attachment maqpna-focus.csv) or json

The response carries X-Focus-Version. BillingCurrency is the gateway's budgetCurrency. CLI: maqpna costs focus.

GET /v1/budgets#

The effective budgets document and spend against every configured limit. Roles: auditor or admin.

{"source":"/etc/maqpna/budgets/budgets.json","timezone":"UTC","budgets":{"agents":{"team-a/coder":{"perDayUSD":10}}},"status":[{"scope":"agent","key":"team-a/coder","window":"day","period":"2026-10-18","spentUsd":3.12,"limitUsd":10,"exceeded":false}]}

lastError is set when the last budgets file was rejected (the previous one stays active). CLI: maqpna budgets list. See Budgets and cost limits.

POST /v1/usage/sandbox#

The operator reports a finished session's sandbox time for metering. Enabled by sandboxUsage.enabled. Auth: a Kubernetes ServiceAccount token (audience maqpna-sandbox-usage by default) whose user is listed in sandboxUsage.reporters, checked with a TokenReview.

Body {"namespace", "session", "agent", "user", "tier", "readyAt", "finishedAt", "forkRoot"}; namespace, session and tier are required. Answers {"recorded": true, "auditSeq", "sandboxSeconds"}, or {"recorded": false, "duplicate": true} for a session already reported. 403 not an authorized usage reporter; 503 when the ledger is unavailable.

Policies and taint#

GET /v1/policies#

The active policies. Roles: auditor or admin.

{"source":"/etc/maqpna/policies/policies.json","loaded":true,"revision":"42","sha256":"…","policies":[…]}

lastError is set when the last file was rejected. CLI: maqpna policy list.

POST /v1/policies:evaluate#

Evaluate a call against the active policies without side effects and return the decision trace. Roles: auditor or admin.

Body field Meaning
namespace, agent, session, user Who calls
server, tool What is called; tool is required
args Arguments
groups, scopes The principal's groups and token scopes
sessionTaints Taint labels; when absent and session is set, the session's live taints are used
sinks, at, substrate Extra tool labels, evaluation time, where the session runs
curl -sS -X POST "$MAQPNA_GATEWAY_URL/v1/policies:evaluate" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"namespace":"team-a","agent":"coder","server":"echo","tool":"delete_resource","args":{"id":"prod-db"}}'

The response has revision, loaded, trace and decision ({"action", "policy", "rule", "reason", "approval"}), plus pin (config, observed, status) when server has tool pins. Unknown body fields are rejected (400). See Write policies.

GET /v1/taints#

All tainted sessions: {"sessions": [{"namespace", "session", "labels", ...}]}. Roles: auditor or admin. CLI: maqpna taint list.

MCP servers and A2A routes#

GET /v1/servers#

The effective MCP server registry. Credentials are never returned, only whether they are present. Roles: auditor or admin.

The response has static (static upstreams), and with a registry file source, loaded, revision, sha256, lastError, bindings, skipped and servers: a map from registry key to {"url", "namespaces", "implicit", "trust", "dataClass", "jurisdiction", "protocolVersions", "timeoutSeconds", "hasCA", "auth", "dlpProfile", "pins", "toolLabels"}, where auth is {"type", "credentialKey", "headerName", "tokenURL", "scopes", "audience", "resource", "credentialsPresent"}. CLI: maqpna mcp list.

GET /v1/servers/{ns}/{name}/tools#

Observed tools, hashes and pin status. Roles: auditor or admin. Optional agent selects the agent whose policies' pins apply.

{"namespace":"team-a","server":"github","pinned":true,"config":{…},"tools":[{"namespace":"team-a","server":"github","tool":"create_issue","sha256":"…","firstSha256":"…","firstSeen":"2026-10-01T09:00:00Z","lastSeen":"2026-10-18T14:00:00Z","status":"pinned","pins":["…"],"visible":true,"callable":true}]}

status is pinned, changed, unpinned or unobserved. CLI: maqpna mcp tools.

POST /v1/servers/{ns}/{name}/pins:export#

Render the observed hashes as a pin snippet for GitOps. Roles: admin.

Query Meaning
mode enforce (default), learn or off
source first: first-seen hashes instead of current
target mcpserver: an MCPServer spec.toolPins fragment instead of a ToolPolicy one
format json for JSON; YAML (application/yaml) otherwise
curl -sS -X POST "$MAQPNA_GATEWAY_URL/v1/servers/team-a/github/pins:export" -H "Authorization: Bearer $TOKEN"
# ToolPolicy spec fragment for team-a/github, exported 2026-10-18T14:30:00Z
toolPins:
  - server: "github"
    mode: enforce
    onChange: hide
    pins:
      "create_issue": "…"

404 when no tools were observed yet (list them through the gateway first). CLI: maqpna mcp pin.

GET /v1/a2a#

The A2A routing table. Roles: admin. Each target has kind (agent or peer), url, trust, jurisdiction, allowedCallers, skills, and for peers cardKid or cardError. CLI: maqpna a2a routes.

Kill switch#

Break-glass revocations made through the API take effect on this gateway at once and are journaled; AgentRevocation objects come from the operator. See Kill switch and revocations.

GET /v1/revocations#

Roles: killswitch or admin.

{"revocations":[{"id":"breakglass/3a91c2d4e5f60718","namespace":"team-a","agents":["coder"],"action":"block","reason":"leaked key","createdAt":"2026-10-18T14:32:00Z","createdBy":"[email protected]","source":"breakglass"}],"source":"/etc/maqpna/revocations/revocations.json"}

source per entry is file or breakglass; expired and mirrored (the AgentRevocation it was mirrored to) when set. failClosed: true and lastError mean the revocation list is unreadable and every call is denied.

POST /v1/revocations#

Roles: killswitch or admin.

Body field Meaning
namespace Namespace the match applies to
sessions, agents, users Glob patterns
jtis Token IDs
all Match everything (in namespace)
action block (default), suspend, terminate or quarantine
reason Free text
expiresAt or ttlSeconds Optional expiry
createdBy Recorded for the static token; OIDC callers are recorded by verified identity

all or at least one of sessions, agents, users, jtis is required. Unknown fields are rejected.

curl -sS -X POST "$MAQPNA_GATEWAY_URL/v1/revocations" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"namespace":"team-a","agents":["coder"],"reason":"leaked key","ttlSeconds":3600}'

Answers 201 with the entry (source: breakglass). A quarantine also returns an incident object (frozen sandboxes, revoked accounts, signed incident record). Errors: 400 invalid entry or expiresAt is in the past; 500 cannot persist revocation. CLI: maqpna kill.

DELETE /v1/revocations/{id}#

Remove a break-glass entry (breakglass/ prefix optional). Roles: killswitch or admin. Answers {"deleted": "breakglass/<id>"}; 404 for unknown IDs and for AgentRevocation objects (delete those with kubectl delete arev).

Connected accounts#

Per-user credentials for MCP servers (the token vault). These routes exist only with tokenVault enabled; otherwise they answer 404 token vault not enabled. See Secret vaults.

Route Auth Purpose
GET /v1/connect The end user's bearer token (vault login client, admin OIDC, or an allowed external issuer) {"user", "issuer", "accounts", "providers": [{"name", "preset", "grant", "connectURL"}]}
GET /v1/connect/{provider} Browser Start the consent flow (OIDC login, then the provider); redirects
GET /v1/connect/login/callback Browser (state and binding cookie) Login leg callback
GET /v1/connect/{provider}/callback Browser (state and binding cookie) Provider callback; stores the token; HTML result page
POST /v1/connect/{provider} The user's ID token as bearer Connect an id-jag provider without a browser; returns the account
DELETE /v1/connect/{provider} The end user's bearer token Disconnect your own account
GET /v1/connected-accounts auditor, admin Every account: user, issuer, provider, status, grant, scopes, connectedAt, keyId, hasRefreshToken, … ({"enabled": false} without the vault)
DELETE /v1/connected-accounts/{user}/{provider} killswitch, admin Revoke an account (?issuer= for another realm); {"revoked": true, ...}
GET /v1/vault/keys admin {"primaryKeyId", "accountsByKey", "keyIds", "notUnderPrimary", "retiredKeyFiles"}
POST /v1/vault:rekey admin Re-seal accounts not under the primary key; adds resealed

Memory, tenants and captures#

GET /v1/memory/stores#

Roles: auditor or admin; ns-query. {"stores": [{"store": {...}, "residencyCompliant": true}]}, with residency explaining a violation.

DELETE /v1/memory/users/{user}#

Erase everything memory stores hold about a user (GDPR Art. 17). Roles: auditor or admin; ns-query. Without namespace you need a global role. store (with namespace) limits it to one store.

curl -sS -X DELETE "$MAQPNA_GATEWAY_URL/v1/memory/users/[email protected]?namespace=team-a" -H "Authorization: Bearer $TOKEN"

The answer is a signed erasure certificate:

{"type":"maqpna-memory-erasure+jws","id":"…","user":"[email protected]","userSha256":"…","scope":"team-a","stores":["team-a/support"],"entriesErased":12,"erasedAt":"2026-10-18T14:40:00Z","requestedBy":"[email protected]","requesterIssuer":"https://sso.example.eu/realms/acme","ledgerSeq":12501,"ledgerHash":"…","backends":["builtin"],"signature":"eyJ…"}

signature is a compact JWS verifiable with GET /v1/audit/jwks (empty without a checkpoint key). 500 when the erasure was incomplete (the certificate says so); 404 when memory is not enabled.

GET /v1/tenants#

Roles: admin. {"tenants": [{"name", "namespaces", "trustDomain", "keyId", "ledger", "headSeq", "headHash"}]}. See Multi-tenant service providers.

GET /v1/captures:export#

Sealed argument captures as a tar archive (maqpna-captures.tar) for maqpna replay. Roles: admin. Optional namespace glob. 404 when capture is not enabled.

Operations#

GET /v1/posture#

The security posture report that maqpna doctor also shows. Roles: auditor or admin. {"generatedAt", "score", "status", "summary", "checks": [{"id", "component", "title", "severity", "status", "detail", "fix", "source"}]}. See Production checklist and maqpna doctor.

GET /v1/license#

Licence status, never the raw licence. Roles: auditor or admin. {"state", "edition", "error", "id", "customer", "features", "limits", "issuedAt", "notAfter", "daysLeft", "keyId", "entitlements", "configured"}. See Licensing.

GET and POST /v1/admin/config:bundle#

Signed configuration bundles. Served only when configBundle is configured; POST only with configBundle.push: true. Roles: admin. GET returns the current bundle state. POST applies the bundle in the body: 200 applied or unchanged, 409 rollback, 413 too large, 422 refused by verification, 400 malformed, 500 install failure. See Config bundles.

Resource API: /apis/maqpna.com/v1alpha1/...#

With resourceAPI.enabled, the gateway stores MAQPNA resources without Kubernetes and serves them with Kubernetes REST conventions, so kubectl, client-go and the maqpna CLI work against the gateway with a kubeconfig whose server is the gateway and whose token is an admin token.

Method Path Verb
GET /api, /api/v1, /apis, /apis/maqpna.com, /apis/maqpna.com/v1alpha1 Discovery
GET /apis/maqpna.com/v1alpha1/{plural} List (all namespaces or a cluster-scoped kind)
GET /apis/maqpna.com/v1alpha1/namespaces/{ns}/{plural} List (?labelSelector=)
POST /apis/maqpna.com/v1alpha1/namespaces/{ns}/{plural} Create (?dryRun=All)
GET .../{plural}/{name} Get
PUT .../{plural}/{name} Update (metadata.resourceVersion required)
PATCH .../{plural}/{name} Merge, JSON or apply patch
DELETE .../{plural}/{name} Delete
PUT, PATCH .../{plural}/{name}/status Status subresource

Cluster-scoped kinds drop namespaces/{ns}. Roles: viewer, auditor or admin read; admin writes (dry runs included); namespace roles apply in their namespace. Errors are Kubernetes Status objects. Not served: watch (405), deleteCollection, strategic merge patch. Every committed write is recorded in the ledger. Fields per kind: CRD reference.

/debug/pprof/#

Go profiles, only on the admin listener (adminListen). Roles: admin.

Next steps#