Hosts

API hosts
HostAcceptsUse for
https://api.mutualtrust.ioBearer credentials (mtk_, mtgw_), workload proofs, Stripe signatures. Session cookies are ignored.Automation, gateways, workloads, CRLs, webhooks.
https://app.mutualtrust.ioEverything above, plus the portal session cookie and /auth/* sign-in routes.The portal and browser-based flows.

Both hosts serve the same /v1 routes. Public endpoints: GET /healthz, GET /openapi.json and GET /pki/crl/{issuer_id}.crl.

Authentication

Session and CSRF (portal)

Humans sign in at https://app.mutualtrust.io/auth/login (OIDC authorization code with PKCE; see Identity and access). The session is the host-only cookie __Host-mt_session (HttpOnly; Secure; SameSite=Lax, 12 hours), honoured only on app.mutualtrust.io. Every non-GET request on a cookie session must echo the session’s CSRF token, which GET /v1/me returns as csrf_token:

Cookie session write
POST https://app.mutualtrust.io/v1/services Cookie: __Host-mt_session=… X-CSRF-Token: 3vQd0r1W… Content-Type: application/json

A missing or wrong token returns 403 forbidden (“Missing or invalid CSRF token.”).

API credentials: Bearer mtk_

Scoped API credentials authenticate automation as the user who created them, limited to one organization and a subset of that user’s roles. Effective roles are the credential’s roles intersected with the creator’s current roles. No CSRF token is needed.

API credential
GET https://api.mutualtrust.io/v1/grants?service_id=svc_01jcqa7m5r7s9t1v3w5x7y9z1a Authorization: Bearer mtk_Jx4c…

Create, list and revoke credentials under Organizations. Revoked or expired credentials return 401 unauthenticated.

Gateway credential: Bearer mtgw_

A gateway receives a scoped credential once, when its pairing is approved. It authenticates the gateway’s own manifest, acknowledgment and receipt endpoints only. Acknowledgments and receipts are additionally JWS-signed with the gateway’s P-256 key registered at pairing (kid: "gw:<gateway_id>"). A disabled gateway’s credential stops working.

Workload proofs

Enrollment, polling, renewal and reload confirmation carry no bearer token. They are authorized by what only the workload holds:

Workload proofs
EndpointAuthorized by
POST /v1/enrollmentsA single-use sandbox token or an OIDC workload JWT.
POST /v1/enrollments/{id}/completeA signature over mt-enroll-v1:{enrollment_id}:{challenge} with the CSR key.
GET /v1/issuance-requests/{id}X-Enrollment-Challenge header equal to the enrollment challenge.
POST /v1/certificates/{id}/renewA signature over mt-renew-v1:{certificate_id}:{csr_sha256_hex}:{unix_seconds} with the current key.
POST /v1/certificates/{id}/reloadedA signature over mt-reload-v1:{certificate_id}:{unix_seconds} with the new key.

Pairing (POST /v1/pairings) is unauthenticated and rate-limited; collecting the result needs the X-Poll-Secret returned at creation.

Conventions

API conventions
Content typeSend Content-Type: application/json with any body; other types return 415 unsupported_media_type. An empty body is treated as {}.
ErrorsRFC 9457 application/problem+json with a stable code, a safe title, status, instance and request_id. See the error catalogue.
Closed schemasUnknown fields or invalid enums return 422 validation_failed with errors: [{path, message}] (JSON pointers, up to 10).
IdempotencyIdempotency-Key is required on invitation create and accept and on checkout, and honoured on organization, space and service creation, Try the Orders API, and grant revoke, suspend and resume. The key is scoped to the caller, method and path and kept for 24 hours. Same key and body → the original status and body with Idempotent-Replayed: true; same key with a different body → 409 idempotency_key_reused; missing where required → 428 idempotency_key_required.
ConcurrencyResponses that carry a numeric revision also send ETag: "<revision>". Grant changes need If-Match: "<revision>": missing → 428 precondition_required; stale → 412 revision_mismatch.
VisibilityResources in organizations you are not a member of return 404, never 403.
Throttling429 rate_limited with Retry-After. Pairing is limited to 30 per hour and guest sign-in to 20 per hour per client IP. Transient dependency failures return 503 with Retry-After: 5.
IDs and timesOpaque, prefixed, sortable IDs such as gr_01jcqb3p… (see ID format). Times are UTC RFC 3339 without fractional seconds. Money is integer minor units with a lowercase ISO 4217 currency.
ListsList endpoints return {"data":[…]}, newest first, capped (100 to 200 items). The audit log pages with before / next_cursor.
HeadersEvery response carries X-Request-Id and security headers (HSTS, nosniff, X-Frame-Options: DENY).

Session and overview

GET /v1/me

The signed-in user, CSRF token and memberships.

GET /v1/me
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Success200
200 OK
{ "user_id": "usr_01jcq9z8y6x4w2v0t8s6r4q2p0", "display_name": "Avery Chen", "auth_method": "oidc", "csrf_token": "3vQd0r1W…", "memberships": [ { "tenant_id": "org_01jcqa7m2k9x4v8r3t6p5w2h7n", "role": "owner" }, { "tenant_id": "org_01jcqa7m2k9x4v8r3t6p5w2h7n", "role": "service_owner" } ], "environment_notice": null }

GET /v1/me/recovery-codes

Recovery code status for a passkey account. See Recovery codes.

GET /v1/me/recovery-codes
AuthSession cookie on app.mutualtrust.io
Success200 {"available":true,"remaining":9,"total":10,"generated_at":"…","last_used_at":"…"}; available: false for guest and single sign-on accounts

POST /v1/me/recovery-codes

Generates ten new one-time recovery codes and invalidates every earlier code. The codes are returned once and stored only as hashes.

POST /v1/me/recovery-codes
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes); not API credentials
Success201 {"codes":["mtrc_…", …],"generated_at":"…"}
Errorsforbidden (sandbox guest, single sign-on account or recovery session)

GET /v1/overview

Everything the user can see across organizations: spaces, services with gateway health, relationships with certificate expiry, and alerts (stale gateways, expiring certificates, and payment notices for owners and billing admins).

GET /v1/overview
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Success200 {user, organizations[], spaces[], services[], relationships[], alerts[]}
200 OK (abridged)
{ "user": { "id": "usr_01jcq9z8y6x4w2v0t8s6r4q2p0", "display_name": "Avery Chen", "auth_method": "oidc" }, "organizations": [ { "id": "org_01jcqa7m2k9x4v8r3t6p5w2h7n", "name": "Northstar", "kind": "customer", "domain_verification": "unverified", "roles": [ "owner", "trust_admin", "service_owner", "partner_admin" ] } ], "spaces": [ { "id": "spc_01jcqa7m4f6g8h2j4k6m8n0p2q", "tenant_id": "org_01jcqa7m2k9x4v8r3t6p5w2h7n", "name": "Orders sandbox", "environment": "sandbox", "trust_fingerprint": "9f2c41d8e3b7a6051c4e8d2f7a9b3c6e1d5f0a8b2c7e4d9f3a6b1c8e5d2f7a40" } ], "services": [ { "id": "svc_01jcqa7m5r7s9t1v3w5x7y9z1a", "tenant_id": "org_01jcqa7m2k9x4v8r3t6p5w2h7n", "space_id": "spc_01jcqa7m4f6g8h2j4k6m8n0p2q", "name": "Orders API", "environment": "sandbox", "active_grants": 1, "gateways": 1, "stale_gateways": 0, "last_allow": "2026-11-14T15:40:12Z" } ], "relationships": [ { "id": "gr_01jcqb3p1n3p5q7r9s1t3v5w7x", "state": "active", "consumer_org_id": "org_01jcqa7m3a2b7c9d4e6f8g1h3j", "sponsor_org_id": "org_01jcqa7m2k9x4v8r3t6p5w2h7n", "service_name": "Orders API", "sponsor_name": "Northstar", "consumer_name": "Harbor Logistics", "workload_name": "harbor-order-sync", "cert_expires_at": "2026-11-15T15:30:30Z", "renew_after": "2026-11-15T07:26:41Z" } ], "alerts": [] }

POST /v1/sandbox/try-orders-api

Creates the sample Northstar and Harbor organizations, a sandbox space and the Orders API service with three route templates.

POST /v1/sandbox/try-orders-api
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
HeadersIdempotency-Key (optional)
Success201
201 Created
{ "sponsor": { "id": "org_01jcqa7m2k9x4v8r3t6p5w2h7n", "name": "Northstar (sample)", "kind": "sample" }, "partner": { "id": "org_01jcqa7m3a2b7c9d4e6f8g1h3j", "name": "Harbor (sample)", "kind": "sample" }, "space": { "id": "spc_01jcqa7m4f6g8h2j4k6m8n0p2q", "org_id": "org_01jcqa7m2k9x4v8r3t6p5w2h7n", "name": "Orders sandbox", "environment": "sandbox", "trust_fingerprint": "9f2c41d8e3b7a6051c4e8d2f7a9b3c6e1d5f0a8b2c7e4d9f3a6b1c8e5d2f7a40", "issuing_fingerprint": "4b8e2d7f1a9c3e6b0d5f8a2c7e1b4d9f6a3c8e0b5d2f7a1c4e9b6d3f8a0c5e27" }, "service": { "id": "svc_01jcqa7m5r7s9t1v3w5x7y9z1a", "space_id": "spc_01jcqa7m4f6g8h2j4k6m8n0p2q", "name": "Orders API", "environment": "sandbox" } }

Sign-in routes (app host only)

Sign-in routes
RouteBehaviour
GET /auth/config{"oidc_configured":bool,"sandbox_guest":bool}
GET /auth/login?return_to=/path302 to the identity provider (PKCE S256, scope=openid email profile). return_to must be a same-site path. 503 oidc_unconfigured if sign-in is not configured.
GET /auth/callbackExchanges the code, verifies the ID token and sets the session cookie, then 302 to return_to.
POST /auth/sandboxCreates a labelled sandbox guest and session: 201 {"user_id","csrf_token"}. 403 if disabled.
POST /auth/logoutRevokes the session and clears the cookie: 204.
POST /auth/passkey/register/verifyCreates a passkey account: 201 {"user_id","csrf_token","recovery_codes":[…10]}. The recovery codes appear only in this response.
POST /auth/passkey/recovery{"code":"mtrc_…"} redeems one recovery code: 201 {"user_id","csrf_token","purpose":"passkey_recovery","remaining_codes"} and a 15-minute session that can only call GET /v1/me, GET /v1/me/passkeys, POST /v1/me/passkeys/options and POST /v1/me/passkeys (everything else is 403 forbidden). Registering the passkey ends the session. Unknown or used codes: 401 unauthenticated. Rate limited per address.

Organizations

POST /v1/organizations

Creates an organization and makes you its owner, trust admin, service owner and partner admin. Billing admin is not implied.

POST /v1/organizations
AuthSession or API credential (any signed-in user)
HeadersIdempotency-Key (optional)
Request bodyname (string, 1 to 80, no control characters or <>)
Success201 {"id":"org_…","name":"Northstar","kind":"customer"}
Errorsvalidation_failed

GET /v1/organizations/{id}/members

GET /v1/organizations/{id}/members
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
RoleAny member
Success200 {"data":[{"user_id","display_name","email","roles":[…]}]}

POST /v1/organizations/{id}/roles

Adds a role to an existing member. Additive and idempotent.

POST /v1/organizations/{id}/roles
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleowner
Request bodyuser_id (usr_…), role (owner | trust_admin | service_owner | partner_admin | billing_admin | auditor)
Success200 {"user_id":"usr_…","role":"billing_admin"}
Errorsnot_found, forbidden, validation_failed

DELETE /v1/organizations/{id}/members/{user_id}/roles/{role}

Removes one role from a member. Credentials the member created lose the role at once. Removing the last role ends the membership, which revokes the member’s API credentials and audit export rights.

DELETE /v1/organizations/{id}/members/{user_id}/roles/{role}
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleowner
Success200 {"user_id":"usr_…","role":"auditor","removed":true,"member_removed":false,"credentials_revoked":0}
Errorslast_owner (the organization’s only owner), not_found (no such membership), forbidden, validation_failed (unknown role)

DELETE /v1/organizations/{id}/members/{user_id}

Removes a member entirely and revokes every API credential they created in the organization, in one batch with the audit entry.

DELETE /v1/organizations/{id}/members/{user_id}
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleowner
Success200 {"user_id":"usr_…","removed":true,"roles":["service_owner"],"credentials_revoked":2}
Errorslast_owner (including removing yourself as the only owner), not_found, forbidden

GET /v1/organizations/{id}/partner-handle

The handle API owners use to invite this organization. See Invite an existing partner by handle.

GET /v1/organizations/{id}/partner-handle
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
RoleAny member
Success200 {"org_id":"org_…","handle":"harbor-logistics-7kq2m9xd","rotated_at":null}

POST /v1/organizations/{id}/partner-handle/rotate

Replaces the handle. The old one stops resolving immediately; invitations already sent stay bound to the organization.

POST /v1/organizations/{id}/partner-handle/rotate
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleowner
Success200 {"org_id":"org_…","handle":"harbor-logistics-q3w8p2rt","rotated_at":"…"}
Errorsnot_found, forbidden, concurrent_change

GET /v1/partner-handles/{handle}

Resolves a partner handle to the organization’s display name and nothing else. Only organizations someone has claimed resolve.

GET /v1/partner-handles/{handle}
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleservice_owner or trust_admin in any organization
Success200 {"handle":"harbor-logistics-7kq2m9xd","organization_name":"Harbor Logistics"}
Errorsnot_found, forbidden, rate_limited (30 lookups per hour per principal)

GET /v1/organizations/{id}/workload-issuers

GET /v1/organizations/{id}/workload-issuers
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
RoleAny member
Success200 {"data":[{"id","issuer_url","jwks_uri","state","created_at"}]}

POST /v1/organizations/{id}/workload-issuers

Approves an OIDC workload issuer for this organization’s workloads.

POST /v1/organizations/{id}/workload-issuers
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Rolepartner_admin
Request bodyissuer_url (https URI), jwks_uri (https URI)
Success201 {"id":"wli_01jcqc2e4f6g8h0j2k4m6n8p0q","issuer_url","jwks_uri","state":"approved"}

GET /v1/organizations/{id}/audit

GET /v1/organizations/{id}/audit
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleowner, trust_admin, auditor
Querybefore: the next_cursor of the previous page
Success200 {"data":[audit events],"next_cursor":string|null}, 100 per page
Audit event
{ "id": "aud_01jcqm4x7h3j5k7m9n1p3q5r7s", "actor_type": "user", "actor_id": "usr_01jcq9z8y6x4w2v0t8s6r4q2p0", "action": "grant.revoke", "resource_type": "grant", "resource_id": "gr_01jcqb3p1n3p5q7r9s1t3v5w7x", "resource_revision": 4, "reason": "contract_ended", "operation_id": "op_01jcqm4x7f9g1h3j5k7m9n1p3q", "request_id": "req_8e1f3a9c2b7d4e60", "environment": "sandbox", "detail": null, "created_at": "2026-11-14T16:02:11Z" }

GET /v1/organizations/{id}/audit-export

GET /v1/organizations/{id}/audit-export
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleaudit.read plus an active export grant
Queryfrom, to (RFC 3339, optional; to exclusive)
Success200 attachment, mt.audit_export.v1 canonical JSON, header X-Content-SHA256
Errorsforbidden

GET /v1/organizations/{id}/support-grants

GET /v1/organizations/{id}/support-grants
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleowner
Success200 {"data":[{"id","staff_email","action","reason_code","expires_at","created_at","approved_by","active"}]} (active is 1 or 0)

POST /v1/organizations/{id}/support-grants

POST /v1/organizations/{id}/support-grants
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleowner
Request bodystaff_email, action (audit.detail | invitation.resend), reason_code (customer_request | incident | diagnostics), expires_in_hours (1 to 72)
Success201 {"id":"sg_01jcqd3f5g7h9j1k3m5n7p9q1r","staff_email","action","reason_code","expires_at"}

POST /v1/organizations/{id}/support-grants/{gid}/revoke

POST /v1/organizations/{id}/support-grants/{gid}/revoke
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleowner
Success200 {"id","expires_at","active":false}
Errorsnot_found

GET /v1/organizations/{id}/export-grants

GET /v1/organizations/{id}/export-grants
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleowner
Success200 {"data":[{"id","user_id","display_name","expires_at","revoked_at","created_at","active"}]}

POST /v1/organizations/{id}/export-grants

POST /v1/organizations/{id}/export-grants
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleowner
Request bodyuser_id (a member holding auditor, owner or trust_admin), expires_in_hours (1 to 168)
Success201 {"id":"exg_01jcqd4g6h8j0k2m4n6p8q0r2s","user_id","expires_at"}
Errorsvalidation_failed

POST /v1/organizations/{id}/export-grants/{gid}/revoke

POST /v1/organizations/{id}/export-grants/{gid}/revoke
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleowner
Success200 {"id","active":false}

GET /v1/organizations/{id}/api-credentials

GET /v1/organizations/{id}/api-credentials
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
RoleAny member
Success200 {"data":[{"id","name","roles","prefix","purpose","expires_at","revoked_at","last_used_at","created_at","created_by","active"}]}

POST /v1/organizations/{id}/api-credentials

POST /v1/organizations/{id}/api-credentials
AuthSigned-in session only (a credential cannot create credentials)
RoleAny member; requested roles must be ones you hold
Request bodyname (1 to 80), roles (non-empty, unique, subset of yours), expires_in_days (1 to 365, default 90)
Success201
Errorsforbidden, validation_failed
201 Created
{ "id": "cred_01jcqd5h7j9k1m3n5p7q9r1s3t", "secret": "mtk_Jx4c…", "expires_at": "2027-02-12T16:20:00Z", "roles": [ "service_owner" ], "name": "terraform-ci", "note": "Shown once. Send as \"Authorization: Bearer <secret>\" to https://api.mutualtrust.io." }

POST /v1/organizations/{id}/api-credentials/{cid}/revoke

POST /v1/organizations/{id}/api-credentials/{cid}/revoke
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
RoleThe credential’s creator or an owner
Success200 {"id":"cred_…","active":false}
Errorsnot_found, forbidden

POST /v1/mcp/authorizations

Portal consent for an MCP client: mints a 30-day purpose: "mcp" credential and hands it once to the MCP server. See MCP server.

POST /v1/mcp/authorizations
AuthSigned-in session only
RoleRequested roles must be ones you hold in org_id
Request bodyrequest_id (16 to 128 URL-safe characters, from the MCP authorization request), org_id, roles, client_name (optional, ≤120)
Success201 {"redirect_url":"https://mcp.mutualtrust.io/oauth/complete?req=…&code=…","credential_id","expires_at"}
Errorsmcp_request_used, mcp_unconfigured, forbidden

GET /v1/organizations/{id}/billing

GET /v1/organizations/{id}/billing
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Rolebilling_admin, owner, auditor
Success200, see billing summary

Spaces

POST /v1/spaces

Creates a trust space and provisions its private root, issuing CA and manifest key through the signer. Production spaces cannot be created by a sandbox guest and are provisioned with Sectigo PKI Operations on hardware-protected keys; request production access from the portal (see Environments).

POST /v1/spaces
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleowner, trust_admin
HeadersIdempotency-Key (optional)
Request bodyorg_id, name (1 to 80), environment (sandbox | production)
Success201
Errorsspace_exists, forbidden, not_found
201 Created
{ "id": "spc_01jcqa7m4f6g8h2j4k6m8n0p2q", "org_id": "org_01jcqa7m2k9x4v8r3t6p5w2h7n", "name": "Orders sandbox", "environment": "sandbox", "trust_fingerprint": "9f2c41d8e3b7a6051c4e8d2f7a9b3c6e1d5f0a8b2c7e4d9f3a6b1c8e5d2f7a40", "issuing_fingerprint": "4b8e2d7f1a9c3e6b0d5f8a2c7e1b4d9f6a3c8e0b5d2f7a1c4e9b6d3f8a0c5e27" }

GET /v1/spaces/{id}

GET /v1/spaces/{id}
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
RoleAny member
Success200
200 OK
{ "id": "spc_01jcqa7m4f6g8h2j4k6m8n0p2q", "tenant_id": "org_01jcqa7m2k9x4v8r3t6p5w2h7n", "name": "Orders sandbox", "environment": "sandbox", "root_issuer_id": "iss_01jcqa7m4c1d3e5f7g9h1j3k5m", "issuing_issuer_id": "iss_01jcqa7m4s8t0v2w4x6y8z0a2b", "manifest_key_version_id": "kv_01jcqa7m4n6p8q0r2s4t6v8w0x", "created_at": "2026-11-14T15:20:04Z", "issuers": [ { "id": "iss_01jcqa7m4c1d3e5f7g9h1j3k5m", "kind": "root", "subject_dn": "CN=Mutual Trust SANDBOX TEST Root · Orders sandbox, O=Northstar, OU=spc_01jcqa7m4f6g8h2j4k6m8n0p2q", "cert_fingerprint": "9f2c41d8e3b7a6051c4e8d2f7a9b3c6e1d5f0a8b2c7e4d9f3a6b1c8e5d2f7a40", "not_before": "2026-11-14T15:19:04Z", "not_after": "2027-02-12T15:20:04Z", "state": "active", "signer_mode": "simulator", "profile": "mt.ca.root.v1" }, { "id": "iss_01jcqa7m4s8t0v2w4x6y8z0a2b", "kind": "intermediate", "subject_dn": "CN=Mutual Trust SANDBOX TEST Issuing CA · Orders sandbox, O=Northstar, OU=spc_01jcqa7m4f6g8h2j4k6m8n0p2q", "cert_fingerprint": "4b8e2d7f1a9c3e6b0d5f8a2c7e1b4d9f6a3c8e0b5d2f7a1c4e9b6d3f8a0c5e27", "not_before": "2026-11-14T15:19:04Z", "not_after": "2027-01-13T15:20:04Z", "state": "active", "signer_mode": "simulator", "profile": "mt.workload.clientauth.v1" } ] }

GET /v1/spaces/{id}/trust-bundle.pem

GET /v1/spaces/{id}/trust-bundle.pem
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
RoleAny member
Success200 application/x-pem-file attachment: each issuer preceded by # <kind> <issuer_id> state=<state> sha256=<fingerprint>

Services

POST /v1/services

POST /v1/services
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleowner, trust_admin, service_owner
HeadersIdempotency-Key (optional)
Request bodyspace_id, name (1 to 80), contact (optional, ≤200, shown to partners), route_templates (1 to 50 of {name, methods[], path, restricted?})
Success201 {"id":"svc_…","space_id","name","environment"}
Errorsspace_not_provisioned, service_exists, validation_failed
Request
{ "space_id": "spc_01jcqa7m4f6g8h2j4k6m8n0p2q", "name": "Orders API", "contact": "api-platform@northstar.example", "route_templates": [ { "name": "Read orders", "methods": [ "GET" ], "path": "/orders/*" }, { "name": "Submit orders", "methods": [ "POST" ], "path": "/orders" }, { "name": "Admin export (restricted)", "methods": [ "GET" ], "path": "/admin/*", "restricted": true } ] }

GET /v1/services/{id}

Service detail with gateways, current policy revision and the readiness checklist.

GET /v1/services/{id}
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
RoleAny member of the owning organization
Success200
200 OK (abridged)
{ "id": "svc_01jcqa7m5r7s9t1v3w5x7y9z1a", "tenant_id": "org_01jcqa7m2k9x4v8r3t6p5w2h7n", "space_id": "spc_01jcqa7m4f6g8h2j4k6m8n0p2q", "environment": "sandbox", "name": "Orders API", "contact": "api-platform@northstar.example", "owner_user_id": "usr_01jcq9z8y6x4w2v0t8s6r4q2p0", "policy_epoch": 1, "created_at": "2026-11-14T15:20:05Z", "sample_partner_org_id": "org_01jcqa7m3a2b7c9d4e6f8g1h3j", "route_templates": [ { "name": "Read orders", "methods": [ "GET" ], "path": "/orders/*" } ], "space_name": "Orders sandbox", "trust_fingerprint": "9f2c41d8e3b7a6051c4e8d2f7a9b3c6e1d5f0a8b2c7e4d9f3a6b1c8e5d2f7a40", "current_policy": { "epoch": 1, "revision": 2, "policy_hash": "c7d2e9f4a1b6c3d8e5f0a7b2c9d4e1f6a3b8c5d0e7f2a9b4c1d6e3f8a5b0c7d2", "created_at": "2026-11-14T15:29:41Z", "cause": "grant.activated" }, "gateways": [ { "id": "gw_01jcqa9b6k8m0n2p4q6r8s0t2v", "name": "orders-edge-1", "state": "paired", "connector_version": "mt-ref-gateway/0.9.0", "last_ack_epoch": 1, "last_ack_revision": 2, "last_ack_hash": "c7d2e9f4a1b6c3d8e5f0a7b2c9d4e1f6a3b8c5d0e7f2a9b4c1d6e3f8a5b0c7d2", "last_ack_at": "2026-11-14T15:29:55Z", "last_seen_at": "2026-11-14T15:41:02Z", "gateway_key_thumbprint": "3nUq0b2s9kTq7m1x…", "created_at": "2026-11-14T15:24:10Z", "status": "current", "policy_age_seconds": 667 } ], "readiness": [ { "step": "trust_anchor", "label": "Private trust anchor provisioned", "ok": true }, { "step": "gateway_paired", "label": "Gateway paired with a pinned policy key", "ok": true }, { "step": "policy_applied", "label": "Gateway acknowledged the current policy revision", "ok": true }, { "step": "allow_receipt", "label": "Allowed request observed", "ok": true }, { "step": "deny_receipt", "label": "Denied control request observed", "ok": true } ], "verified": true }

PATCH /v1/services/{id}

Changes service settings. grant_approval: "sponsor_review" makes every accepted invitation wait for a service owner’s approval. See Sponsor review.

PATCH /v1/services/{id}
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleservice_owner, trust_admin
Request bodyAt least one of grant_approval (automatic | sponsor_review) and contact (string up to 200, or null)
Success200 {"id":"svc_…","grant_approval":"sponsor_review","contact":"…"}
Errorsvalidation_failed, not_found, forbidden

GET /v1/services/{id}/receipts

GET /v1/services/{id}/receipts
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Rolegrant.read (owner, trust admin, service owner, partner admin, auditor) in the owning organization
Success200 latest 100 receipts
200 OK
{ "data": [ { "receipt_id": "rc_5d8e2a7c1f4b9e3a6c0d", "request_id": "req_7b2e9d4a1f6c3e8b5a0d", "gateway_id": "gw_01jcqa9b6k8m0n2p4q6r8s0t2v", "gateway_name": "orders-edge-1", "epoch": 1, "revision": 2, "tls_verdict": "verified", "tls_reason": null, "authz_verdict": "deny", "authz_reason": "route_not_permitted", "grant_id": "gr_01jcqb3p1n3p5q7r9s1t3v5w7x", "principal": "urn:mutualtrust:workload:sandbox:org_01jcqa7m3a2b7c9d4e6f8g1h3j:wl_01jcqb3p1y2z4a6b8c0d2e4f6g", "cert_serial": "5c1e9a7b3d2f48a6b0c4e8d1f3a5b7c9", "method": "GET", "route": "/admin/export", "observed_at": "2026-11-14T15:40:31Z" } ] }

GET /v1/services/{id}/invitations

GET /v1/services/{id}/invitations
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Rolegrant.read
Success200 {"data":[{"id","consumer_org_id","consumer_name","scope","state","expires_at","created_at"}]} (latest 100)

POST /v1/services/{id}/invitations

POST /v1/services/{id}/invitations
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleservice_owner, trust_admin
HeadersIdempotency-Key (required)
Request bodyconsumer ({org_handle}, {new_org_name} or {org_id}), scope, expires_in_hours (1 to 720, default 168), grant_expires_at (optional date-time)
Success201, includes the one-time token and accept_url
Errorsidempotency_key_required, idempotency_key_reused, validation_failed, not_found (unknown or unclaimed partner handle)
201 Created
{ "id": "inv_01jcqb2n8c4d6e8f0g2h4j6k8m", "service_id": "svc_01jcqa7m5r7s9t1v3w5x7y9z1a", "consumer_org_id": "org_01jcqa7m3a2b7c9d4e6f8g1h3j", "consumer_binding": "existing_org", "state": "pending", "expires_at": "2026-11-21T15:40:00Z", "scope": { "v": 1, "routes": [ { "methods": [ "GET" ], "path": "/orders/*" }, { "methods": [ "POST" ], "path": "/orders" } ] }, "token": "mtinv_V9n2…", "accept_url": "https://app.mutualtrust.io/#/invitations/inv_01jcqb2n8c4d6e8f0g2h4j6k8m?token=mtinv_V9n2…" }

POST /v1/services/{id}/gateways

Approves a pairing code shown by a gateway and binds the gateway to this service.

POST /v1/services/{id}/gateways
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleservice_owner, trust_admin
Request bodycode (XXXX-XXXX, case-insensitive), name (1 to 80)
Success201 {"gateway_id":"gw_01jcqa9b6k8m0n2p4q6r8s0t2v","service_id","name","key_thumbprint"}
Errorsnot_found (unknown, used or expired code)

Invitations

POST /v1/invitations/{id}/preview

POST /v1/invitations/{id}/preview
AuthThe invitation secret in the body (no sign-in needed)
Request bodytoken
Success200 recipient view, see Partners
Errorsnot_found

POST /v1/invitations/{id}/accept

POST /v1/invitations/{id}/accept
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential, plus the invitation secret
Rolepartner_admin or owner in the consumer organization, or none when claiming an unclaimed organization
HeadersIdempotency-Key (required)
Request bodytoken, consumer_org_id, workload (name, attestation_kind = sandbox_token | oidc_jwt, and for OIDC issuer, audience, subject and optional subject_uid)
Success201; state is pending with awaiting_approval: true when the service uses sponsor review
Errorsinvitation_not_pending, invitation_expired, capacity_exceeded, concurrent_change (a new organization was claimed first), forbidden, not_found, validation_failed
201 Created
{ "grant_id": "gr_01jcqb3p1n3p5q7r9s1t3v5w7x", "state": "active", "service": "Orders API", "environment": "sandbox", "workload": { "id": "wl_01jcqb3p1y2z4a6b8c0d2e4f6g", "name": "harbor-order-sync", "principal": "urn:mutualtrust:workload:sandbox:org_01jcqa7m3a2b7c9d4e6f8g1h3j:wl_01jcqb3p1y2z4a6b8c0d2e4f6g" }, "scope": { "v": 1, "routes": [ { "methods": [ "GET" ], "path": "/orders/*" }, { "methods": [ "POST" ], "path": "/orders" } ] } }

POST /v1/invitations/{id}/decline

POST /v1/invitations/{id}/decline
AuthThe invitation secret in the body
Request bodytoken
Success200 {"id","state":"declined"}
Errorsinvitation_not_pending, not_found

POST /v1/invitations/{id}/cancel

POST /v1/invitations/{id}/cancel
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleservice_owner, trust_admin (sponsor)
Success200 {"id","state":"canceled"}
Errorsinvitation_not_pending

Grants and operations

GET /v1/grants

GET /v1/grants
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Rolegrant.read
Queryservice_id (sponsor view) or org_id (partner view); one is required
Success200 {"data":[…]} (latest 200)
Errorsvalidation_failed when neither is given
200 OK: sponsor view item
{ "id": "gr_01jcqb3p1n3p5q7r9s1t3v5w7x", "state": "active", "revision": 2, "scope": { "v": 1, "routes": [ { "methods": [ "GET" ], "path": "/orders/*" }, { "methods": [ "POST" ], "path": "/orders" } ] }, "expires_at": null, "environment": "sandbox", "consumer_org_id": "org_01jcqa7m3a2b7c9d4e6f8g1h3j", "consumer_name": "Harbor Logistics", "workload_id": "wl_01jcqb3p1y2z4a6b8c0d2e4f6g", "workload_name": "harbor-order-sync", "updated_at": "2026-11-14T15:29:40Z", "cert_expires_at": "2026-11-15T15:30:30Z" }
200 OK: partner view item
{ "id": "gr_01jcqb3p1n3p5q7r9s1t3v5w7x", "state": "active", "revision": 2, "scope": { "v": 1, "routes": [ { "methods": [ "GET" ], "path": "/orders/*" }, { "methods": [ "POST" ], "path": "/orders" } ] }, "expires_at": null, "environment": "sandbox", "service_name": "Orders API", "contact": "api-platform@northstar.example", "sponsor_name": "Northstar", "workload_id": "wl_01jcqb3p1y2z4a6b8c0d2e4f6g", "workload_name": "harbor-order-sync", "cert_expires_at": "2026-11-15T15:30:30Z", "renew_after": "2026-11-15T07:26:41Z" }

GET /v1/grants/{id}

GET /v1/grants/{id}
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Rolegrant.read on either side
Success200 with ETag
200 OK
{ "id": "gr_01jcqb3p1n3p5q7r9s1t3v5w7x", "side": "sponsor", "sides": [ "sponsor" ], "state": "active", "revision": 2, "environment": "sandbox", "scope": { "v": 1, "routes": [ { "methods": [ "GET" ], "path": "/orders/*" }, { "methods": [ "POST" ], "path": "/orders" } ] }, "expires_at": null, "reason_code": null, "created_at": "2026-11-14T15:29:40Z", "updated_at": "2026-11-14T15:29:40Z", "service": { "id": "svc_01jcqa7m5r7s9t1v3w5x7y9z1a", "name": "Orders API", "contact": "api-platform@northstar.example" }, "sponsor": { "id": "org_01jcqa7m2k9x4v8r3t6p5w2h7n", "name": "Northstar" }, "consumer": { "id": "org_01jcqa7m3a2b7c9d4e6f8g1h3j", "name": "Harbor Logistics" }, "workload": { "id": "wl_01jcqb3p1y2z4a6b8c0d2e4f6g", "name": "harbor-order-sync", "attestation_kind": "sandbox_token", "attestation_issuer": "urn:mutualtrust:sandbox", "attestation_subject": "sbx_01jcqb3p2a4b6c8d0e2f4g6h8j", "principal": "urn:mutualtrust:workload:sandbox:org_01jcqa7m3a2b7c9d4e6f8g1h3j:wl_01jcqb3p1y2z4a6b8c0d2e4f6g" }, "certificates": [ { "id": "crt_01jcqb5r0d2e4f6g8h0j2k4m6n", "serial_hex": "5c1e9a7b3d2f48a6b0c4e8d1f3a5b7c9", "fingerprint": "2e7a…", "not_before": "2026-11-14T15:30:30Z", "not_after": "2026-11-15T15:30:30Z", "renew_after": "2026-11-15T07:26:41Z", "state": "issued", "revoked_at": null, "revocation_reason": null, "predecessor_id": null, "reload_confirmed_at": null, "issuer_id": "iss_01jcqa7m4s8t0v2w4x6y8z0a2b", "created_at": "2026-11-14T15:31:01Z" } ], "last_successful_call": "2026-11-14T15:40:12Z" }

POST /v1/grants/{id}/approve

Approves a pending grant (sponsor review): pending to active, emits grant.activated and publishes a policy revision. In production, connection capacity is checked atomically.

POST /v1/grants/{id}/approve
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleservice_owner, trust_admin (sponsor)
HeadersIf-Match: "<revision>" (required), Idempotency-Key (optional)
Request bodynote (optional, up to 500)
Success200 GrantTransition
Errorsprecondition_required, revision_mismatch, invalid_transition (not pending), capacity_exceeded (the grant stays pending), forbidden, not_found

POST /v1/grants/{id}/reject

Rejects a pending grant: pending to revoked with a reason the partner can see.

POST /v1/grants/{id}/reject
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleservice_owner, trust_admin (sponsor)
HeadersIf-Match: "<revision>" (required), Idempotency-Key (optional)
Request bodyreason_code (contract_ended | security_incident | owner_request), note (optional)
Success200 GrantTransition
Errorsprecondition_required, revision_mismatch, invalid_transition, validation_failed, forbidden, not_found

POST /v1/grants/{id}/suspend

POST /v1/grants/{id}/suspend
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleservice_owner, trust_admin (sponsor)
HeadersIf-Match (required), Idempotency-Key (optional)
Request bodyreason_code (contract_ended | security_incident | owner_request), note (optional, ≤500)
Success200 transition result (below)
Errorsprecondition_required, revision_mismatch, invalid_transition, forbidden

POST /v1/grants/{id}/resume

POST /v1/grants/{id}/resume
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
RoleSponsor service_owner, trust_admin
HeadersIf-Match (required), Idempotency-Key (optional)
Request bodynote (optional)
Success200 transition result
Errorsprecondition_required, revision_mismatch, invalid_transition

POST /v1/grants/{id}/revoke

Withdraws the grant permanently and revokes its live certificates.

POST /v1/grants/{id}/revoke
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
RoleSponsor service_owner, trust_admin
HeadersIf-Match (required), Idempotency-Key (optional)
Request bodyreason_code, note (optional)
Success200 with ETag
Errorsprecondition_required, revision_mismatch, invalid_transition, forbidden
Transition result (suspend, resume, revoke)
{ "grant_id": "gr_01jcqb3p1n3p5q7r9s1t3v5w7x", "state": "revoked", "revision": 4, "operation_id": "op_01jcqm4x7f9g1h3j5k7m9n1p3q", "effective_control_plane_time": "2026-11-14T16:02:11Z", "intended_policy_revision": { "epoch": 1, "revision": 4 }, "pending_gateway_count": 1 }

PATCH /v1/grants/{id}/scope

PATCH /v1/grants/{id}/scope
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
RoleSponsor service_owner, trust_admin
HeadersIf-Match (required unless preview)
Request bodyscope, preview (boolean), note (optional)
Success200 preview {"preview":true,"diff":{added,removed,unchanged},"current_revision"} or result {"grant_id","revision","diff","operation_id"}
Errorsrevision_mismatch, invalid_transition, validation_failed

GET /v1/operations/{id}

GET /v1/operations/{id}
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
RoleAny member of the organization that owns the operation (the sponsor)
Success200, see Follow the operation

Enrollment and certificates

PATCH /v1/workloads/{id}/binding

Binds or rebinds an OIDC workload to a Kubernetes service account UID (the token claim kubernetes.io.serviceaccount.uid). Audited for the partner and for every API owner with a live grant to the workload. See Bind the service account UID.

PATCH /v1/workloads/{id}/binding
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Rolepartner_admin of the workload’s organization
Request bodysubject_uid (string, or null to remove the binding), note (optional)
Success200 {"workload_id":"wl_…","attestation_subject":"system:serviceaccount:orders:order-sync","attestation_subject_uid":"0a0b…","previous_subject_uid":"6f1a…","binding_updated_at":"…"}
Errorsvalidation_failed (sandbox workload or malformed UID), concurrent_change, forbidden, not_found

POST /v1/workloads/{id}/enrollment-tokens

Sandbox only: approves enrollment and returns a single-use token valid for 30 minutes.

POST /v1/workloads/{id}/enrollment-tokens
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Rolepartner_admin of the workload’s organization
Request bodygrant_id
Success201 {"token":"mtenr_…","expires_at","grant_id","workload_id","label"}
Errorsgrant_not_active, validation_failed, not_found

POST /v1/enrollments

POST /v1/enrollments
AuthAttestation in the body
Request bodyattestation ({kind: sandbox_token | oidc_jwt, token}); for OIDC also workload_id and grant_id
Success201 {"enrollment_id","challenge","expires_at","proof_input","workload_id","grant_id"}
Errorsattestation_invalid, attestation_replayed, unauthenticated, grant_not_active, jwks_unavailable

POST /v1/enrollments/{id}/complete

POST /v1/enrollments/{id}/complete
AuthProof of possession
Request bodycsr (base64 DER or PEM, P-256, ECDSA-SHA256), proof (base64url raw signature over proof_input)
Success200 issued certificate, or 202 {"state":"pending","issuance_request_id","poll_url","retry_after_seconds":5}
Errorsenrollment_used, challenge_expired, validation_failed, grant_not_active, grant_expiring, issuance_refused, issuer_unavailable

Response body: see Enrollment.

GET /v1/issuance-requests/{id}

GET /v1/issuance-requests/{id}
AuthX-Enrollment-Challenge header
Success200 issued certificate, {"state":"pending",…} while signing is retried, or {"state":"failed","issuance_request_id"} if the signer refused it
Errorsnot_found (unknown request or wrong challenge)

POST /v1/certificates/{id}/renew

POST /v1/certificates/{id}/renew
AuthKey-continuity proof
Request bodycsr, proof, timestamp (unix seconds, ±300 s)
Success200 issued certificate (or state: "pending")
Errorsnot_found, certificate_revoked, certificate_expired, grant_not_active, validation_failed

POST /v1/certificates/{id}/reloaded

POST /v1/certificates/{id}/reloaded
AuthProof with the new key
Request bodyproof, timestamp
Success200 {"certificate_id","reload_confirmed":true,"predecessor_retirable"}
Errorsnot_found, validation_failed

POST /v1/certificates/{id}/revoke

POST /v1/certificates/{id}/revoke
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
RoleSponsor service_owner/trust_admin, or the workload’s partner_admin
Request bodyreason_code (key_compromise | superseded | cessation_of_operation | owner_request)
Success200 {"certificate_id","state":"revoked","operation_id","effective_control_plane_time"}
Errorsalready_revoked, not_found

GET /v1/certificates/{id}/chain.pem

GET /v1/certificates/{id}/chain.pem
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Rolecertificate.read on either side
Success200 application/x-pem-file: leaf, issuing CA, root

Gateways

POST /v1/pairings

A gateway registers its public key and receives a code to show its operator. The code expires after 15 minutes.

POST /v1/pairings
AuthNone (rate-limited: 30 per hour per client IP)
Request bodygateway_public_jwk ({kty:"EC", crv:"P-256", x, y}), connector_version (optional, ≤40)
Success201
Errorsvalidation_failed, rate_limited
201 Created
{ "pairing_id": "pair_01jcqa9a4w6x8y0z2a4b6c8d0e", "code": "K7QM-4TXZ", "poll_secret": "bW9…", "expires_at": "2026-11-14T15:39:10Z", "key_thumbprint": "3nUq0b2s9kTq7m1x…", "approve_url": "https://app.mutualtrust.io/#/pair?code=K7QM-4TXZ" }

GET /v1/pairings/{id}

Poll until approved. The approved payload (including the credential) is returned exactly once; later polls return {"state":"collected"}.

GET /v1/pairings/{id}
AuthX-Poll-Secret header
Success200 {"state":"pending"}, {"state":"expired"} or the approved payload
200 OK: approved (once)
{ "state": "approved", "gateway_id": "gw_01jcqa9b6k8m0n2p4q6r8s0t2v", "tenant_id": "org_01jcqa7m2k9x4v8r3t6p5w2h7n", "service_id": "svc_01jcqa7m5r7s9t1v3w5x7y9z1a", "environment": "sandbox", "credential": "mtgw_…", "manifest_url": "https://api.mutualtrust.io/v1/gateways/gw_01jcqa9b6k8m0n2p4q6r8s0t2v/manifest", "policy_verification_key": { "kid": "kv_01jcqa7m4n6p8q0r2s4t6v8w0x", "alg": "ES256", "jwk": { "kty": "EC", "crv": "P-256", "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" } }, "refresh_seconds": 15 }

POST /v1/gateways/{id}/disable

Disables a gateway; its credential stops working immediately.

POST /v1/gateways/{id}/disable
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Roleservice_owner, trust_admin
Success200 {"gateway_id","state":"disabled"}

GET /v1/gateways/{id}/manifest

GET /v1/gateways/{id}/manifest
AuthGateway credential (its own ID only)
Success200 {"manifest":"<compact JWS>","epoch","revision"}
Errorspolicy_not_published, not_found

Format: Manifest JWS.

POST /v1/gateways/{id}/acks

POST /v1/gateways/{id}/acks
AuthGateway credential + JWS signed by the gateway key
Request bodyack (compact JWS, typ: mt.ack.v1+jws)
Success202 {"accepted":true,"received_at"}
Errorsvalidation_failed (bad signature, unknown revision, hash mismatch)

POST /v1/gateways/{id}/receipts

POST /v1/gateways/{id}/receipts
AuthGateway credential + JWS signed by the gateway key
Request bodyreceipts (≤100 compact JWS, typ: mt.receipt.v1+jws)
Success202 {"accepted":n,"rejected":n}

Billing

POST /v1/billing/checkout-sessions

POST /v1/billing/checkout-sessions
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Rolebilling_admin
HeadersIdempotency-Key (required)
Request bodyorg_id, sku (team_10 | team_50)
Success201 {"operation_id":"chk_…","url","mode"}
Errorssubscription_exists, plan_unavailable, billing_unconfigured, stripe_error, stripe_unavailable

POST /v1/billing/portal-sessions

POST /v1/billing/portal-sessions
AuthSession cookie on app.mutualtrust.io (+ X-CSRF-Token on writes) or API credential
Rolebilling_admin
Request bodyorg_id
Success201 {"url"}
Errorsbilling_unconfigured, stripe_unavailable

POST /webhooks/stripe

For Stripe only.

POST /webhooks/stripe
AuthStripe-Signature over the raw body (5-minute tolerance)
Success200 {"received":true,"duplicate":bool}; 400 {"code":"invalid_signature"}

Instrumentation and public endpoints

POST /v1/adoption-events

POST /v1/adoption-events
AuthNone
Request bodymilestone (demo_started | paired | enrolled | first_allow | first_deny | unattended_renewal), elapsed_ms, connector_version, error_code, cohort_id (all optional except milestone). No personal data or tokens.
Success202 {"recorded":true}

GET /healthz

GET /healthz
AuthNone
Success200 {"ok":true,"deployment":"sandbox"}

GET /openapi.json

GET /openapi.json
AuthNone
Success200 OpenAPI 3.1.1 document generated from the command schemas

GET /pki/crl/{issuer_id}.crl

GET /pki/crl/{issuer_id}.crl
AuthNone
Success200 DER application/pkix-crl, Cache-Control: public, max-age=300; 404 until first published

OpenAPI coverage

The published OpenAPI document covers the core automation surface. This page is the complete reference, including account and overview routes (/v1/me, /v1/overview), organization administration, invitation preview and cancel, GET /v1/grants and issuance polling.