api reference
Intended Documentation
API Authentication
How Intended authenticates API requests — credential types, the fail-closed gate, headers, scopes, rate limits, and the exact errors you must handle.
API Authentication#
Every Intended API call passes through a single authentication gate before any route handler runs. The gate resolves a tenant context — which tenant is calling, as which actor, with which permissions — and denies the request if it cannot establish one. There is no anonymous or best-effort path: a request that fails to resolve a verified tenant context is rejected, not allowed through. This is the same fail-closed principle the runtime applies to actions.
Each stage is fail-closed: if it cannot pass, the request is denied with a specific status rather than continuing.
Credential types#
Intended accepts four credential sources. The gate resolves them in this order; the first one present is used. Most server-to-server integrations use an API key.
| Credential | How you present it | Typical caller |
|---|---|---|
| API key | Authorization: Bearer <key> or X-API-Key: <key> | Backend services, CI, agents |
| Auth0 JWT | Authorization: Bearer <jwt> | First-party apps mid-migration |
| Portal session | x-portal-session-id header or intended_portal_session cookie | Browser / console-proxied calls |
| Legacy headers | x-tenant-id / x-user-id / x-user-role | Local development only |
Warning
Legacy identity headers are disabled in production. They are honored only when the server runs with INTENDED_ALLOW_LEGACY_HEADERS=1 outside production, and browsers can never set them. Never build a production integration on them.
API keys#
Customer API keys are prefixed intended_live_… so you can recognize them at a glance. A legacy mrt_… prefix is still accepted for older staff/backoffice keys, but new customer keys are always minted as intended_live_….
There is no separate sandbox or test key tier — there is one kind of customer key. A key carries an environment label (for example development, staging, or production), but that label is cosmetic: it is metadata for your own bookkeeping and does not gate authorization, billing, data isolation, or any side effect. The same key behaves the same way regardless of the label. An unrecognized prefix is rejected with 401 INVALID_API_KEY.
Keys are stored only as a SHA-256 hash and compared in constant time, so the raw value can never be recovered from Intended. Treat it like any other secret: keep it server-side, inject it from your secrets manager, and rotate it immediately if it is exposed. Scope each key to the minimum permissions it needs (see Scopes).
Authenticating a request#
The same authenticated call to POST /intent, in three forms. In every case the credential travels in the Authorization header and the tenant in x-tenant-id.
The tenant header#
Tenant-scoped routes require the x-tenant-id header. Several routes additionally require the tenantId in the request body or path to match the tenant your credential resolves to. A mismatch is rejected with 403 TENANT_MISMATCH — a hard cross-tenant isolation boundary, not a warning. After the gate verifies the credential, it re-injects the resolved tenant server-side, so a forged x-tenant-id cannot widen access beyond what the credential grants.
Scopes and permissions#
A credential resolves to a permission set, derived from the caller's role and, for API keys, the scopes attached to the key. Each route checks the specific permission it needs:
| Operation | Required permission |
|---|---|
Submit an intent (POST /intent) | intent:create |
Run an authority evaluation (POST /authority/evaluate) | authority:evaluate |
| Read policies / drafts | authority:policy:read |
| Create or deploy policy | authority:policy:write |
A key used only to submit intents should not carry policy-write. Missing or insufficient permission returns 403 with the required permission named in the response body; an unresolved or invalid credential returns 401.
Rate limits and protective controls#
The gate also enforces platform- and tenant-protection controls. Build for them rather than discovering them in production:
- Rate limiting — a global per-IP limit (500 requests/minute) plus a stricter limit on authentication endpoints. Exceeding it returns
429 RATE_LIMITED; honor it with exponential backoff. - IP allowlisting — if a tenant configures an allowlist, calls from other addresses return
403 IP_NOT_ALLOWED. - MFA and session freshness — privileged portal roles must satisfy MFA (
403 MFA_REQUIRED); idle or expired portal sessions return401 INVALID_PORTAL_SESSION. - Request hardening — a 1 MB body limit, URL/header size caps, and input sanitization apply to every request.
Errors you must handle#
Intended is fail-closed: when authentication or authorization cannot be established, the request is denied rather than allowed through. Handle each explicitly.
| Status | Code | Cause | What to do |
|---|---|---|---|
| 401 | UNAUTHORIZED | No valid credential | Check the key value and Authorization header |
| 401 | INVALID_PORTAL_SESSION | Idle or expired session | Re-authenticate the session |
| 403 | TENANT_MISMATCH | Credential's tenant ≠ requested tenant | Send the correct x-tenant-id / body tenantId |
| 403 | FORBIDDEN | Authenticated but lacks the permission | Grant the named scope/role to the key |
| 403 | IP_NOT_ALLOWED | Source IP not on the tenant allowlist | Call from an allowlisted address |
| 403 | MFA_REQUIRED | Privileged role without MFA | Complete MFA on the session |
| 429 | RATE_LIMITED | Rate limit exceeded | Back off and retry |
Tip
Only operational and discovery routes are public and unauthenticated: /health, /ready, and /.well-known/jwks.json. Everything else requires a verified tenant context.
See also#
- API Quickstart — obtain a key and make your first call
- Intents API — the primary authority endpoint
- Roles and Token Governance — managing keys and roles at the tenant level