API · Auth
Agents
The 4-tier agent model, structured scope policies, dual attribution, and the Authorization resource.
Last updated
TL;DR. Agent identities are bearer tokens (tok_…) issued in four tiers —
1 observe, 2 prepare, 3 execute, 4 autonomous. Every token carries a
structured scope-policy DSL (allow / deny / resources / conditions /
limits). Every event written by an agent is dual-attributed
(human_principal_id + agent_id). Tier-3 destructive operations and
out-of-cap tier-4 ones return 202 pending_authorization and pause on a
first-class Authorization resource (auth_…) that a human approves via the
dashboard or POST /v1/authorizations/{id}/approve. Test mode plus
X-Matter-Test-Speed: instant collapses multi-day cascades into seconds —
iterate on agent policies in seconds, not weeks.
Matter is built so a software agent can do anything a human principal can do — file a Delaware C-Corp, run an annual report cascade, advance an M&A deal — with exactly the supervision the human chose, and a complete audit trail naming both parties for the regulator. The model uses tool-side scoping (which actions an agent can call, aligned with the MCP authorization spec) and resource-side scoping (which resources it can act on, aligned with OAuth 2.1 scope conventions); the structured-policy DSL is the layer that ties them together.
This page is the canonical reference for that model. If you want the resource
shapes, jump to /api/auth/tokens and
/api/authorizations/index. If you
want the protocol semantics — tiers, scope, attribution, pausing, MCP catalog
visibility — read on.
The 4-tier model
Every tok_… carries a tier between 1 (observe) and 4 (autonomous). Tier
is a property of the token, not of the request. The same caller cannot
"upgrade" mid-call — they re-mint a higher-tier token, optionally with stricter
scopes, and the upgrade itself is auditable.
| Tier | Verb | Default scope | MCP visibility | Examples |
|---|---|---|---|---|
1 | observe | Read-only across whitelisted resources. No mutations. No ?dry_run. | Full read catalog (matter_list_*, matter_retrieve_*, matter_retrieve_cap_table). | "Show me my cap table." "List filings due this month." |
2 | prepare | Read + dry-run mutations (?dry_run=true). Intents may resolve, never execute. Drafts may render, never submit. | Read catalog + prepare_* / model_* tools. Mutating tools are hidden. | "Draft a board consent for review." "Model the next round." |
3 | execute | Read + write. Every destructive or high-stakes write returns 202 pending_authorization and pauses on an Authorization for human signature. | Full catalog. Auth-required tools annotate with requires_authorization: true. | "Issue these grants once Alice approves." "File the foreign qualification when ready." |
4 | autonomous | Read + write + autonomous on routine ops within limits. Above the cap, or on hardcoded destructive ops, falls back to tier-3 behaviour. | Full catalog with autonomous tools surfaced. | "Run our compliance calendar." "Pay franchise tax automatically up to USD 5,000/month." |
A few rules apply across all tiers:
- Hardcoded destructive ops never autopilot. Dissolution (
/dissolve), M&A close (/corporate_transactions/{id}/close), token revocation, and registered-agent change always pause onAuthorizationregardless of tier. - Tier is monotonic with respect to MCP visibility. A tier-1 token never sees a write tool in the MCP catalog — not even greyed-out. The agent cannot attempt an action it cannot describe.
- Tier downgrades are free. A tier-4 token can voluntarily call with
?dry_run=trueand behave like a tier-2. Tier upgrades require a new token. - No per-request elevation. There is no
Sudoheader. If you need a one-off elevated action, mint a single-action tier-3 token, use it, revoke it.
Why tier 3 is the working tier
Tier 3 is the sweet spot for production agents — the agent drives the workflow
end-to-end, but every consequential mutation gates on a human signature. The
human sees a single Authorization page in the dashboard with the canonical
payload, the action, the agent identity, and an "Approve" button. They are not
asked to read the agent's reasoning trace; they are asked to sign the
artifact.
When to reach for tier 4
Tier 4 is for ops that are routine, capped, and reversible:
- annual reports and franchise-tax payments (capped by
limits.max_spend_per_month) - BOI updates triggered by stakeholder changes
- mail acknowledgement (non-escalated categories only)
- compliance reminders and renewal queueing
Tier 4 is not appropriate for:
- equity issuance (always pauses)
- any document with a wet signature requirement
- M&A workflow advancement past
due_diligence - dissolution of any kind
The policy DSL below is how you declare a tier-4 token's surface precisely.
Structured scope policy DSL
Every token carries a scopes array. Each entry is a structured policy node
with five keys:
{
"scopes": [
{
"allow": ["entities.read", "filings.create"],
"resources": ["ent_Nq3KcAbc"],
"conditions": { "livemode": false },
"limits": { "max_calls_per_hour": 60, "max_cost_usd": 0 }
}
]
}Prop
Type
Verbs are namespaced by resource. The canonical catalog (full list in the
OpenAPI spec under each operation's x-matter-mcp.tool_name):
| Verb | Resources |
|---|---|
*.read, *.list | every resource — observe-tier baseline |
entities.create, entities.update, entities.dissolve | Entity |
filings.create, filings.cancel | Filing |
documents.create, documents.sign, documents.void | Document, SigningEnvelope |
grants.create, valuations.create, share_classes.create | cap-table family |
resolutions.create, resolutions.sign | Resolution |
corporate_transactions.create, corporate_transactions.advance, corporate_transactions.close, corporate_transactions.terminate | CorporateTransaction (M&A) |
mail.acknowledge, mail.escalate, mail.respond | Mail |
tokens.revoke, tokens.rotate | Token |
Evaluation order, per request:
Resolve the target resource. The resolver picks the typed ID from the
request path and the body. Cross-resource calls (e.g. issuing a grant on
ent_X against plan_Y) require both targets in resources.
Match scope entries. Every scopes[] entry whose resources matches
the target and whose conditions evaluate true is a candidate.
Apply allow/deny. The verb must appear in at least one matching
entry's allow and must not appear in any matching entry's deny.
Apply limits. Counters are checked against limits for every matching
entry. The strictest cap wins. Exceeded → 429.
Apply tier guards. If the operation is hardcoded-destructive
(/dissolve, /close, /revoke, /registered_agent), the request is
rewritten to 202 pending_authorization regardless of tier.
If no entry matches, the request fails closed with 403 forbidden and the
error body's extension.reason names the missing verb-resource pair so the
caller can ask for the right grant.
Worked policy examples
A tier-1 dashboard agent that mirrors the cap table for an investor portal. Read everything in a portfolio, mutate nothing.
{
"tier": 1,
"principal": { "human_id": "usr_4Kj2m8pQ", "agent_id": "agt_invreader" },
"scopes": [
{
"allow": [
"entities.read",
"entities.list",
"stakeholders.read",
"stakeholders.list",
"share_classes.read",
"share_classes.list",
"grants.read",
"grants.list",
"valuations.read",
"cap_tables.read"
],
"resources": ["pf_studio_fund_i"],
"limits": { "max_calls_per_hour": 600 }
}
],
"api_version": "2026-05-01"
}The pf_studio_fund_i resource expands transitively — every Entity whose
portfolio_id resolves to that portfolio inherits the read grant. Cap-table
reads also implicitly authorize the underlying ledger reads needed to compute
them. No filings.*, no documents.*, no writes. Calling POST /v1/filings
returns 403 { extension: { reason: "missing_grant", verb: "filings.create" } }.
A tier-2 paralegal copilot that drafts filings for a human to file. Every
mutation must carry ?dry_run=true; otherwise the policy denies it.
{
"tier": 2,
"principal": { "human_id": "usr_4Kj2m8pQ", "agent_id": "agt_paralegal_v2" },
"scopes": [
{
"allow": [
"entities.read",
"filings.read",
"filings.list",
"filings.create",
"documents.read",
"documents.list",
"documents.create",
"intents.create",
"intents.read"
],
"resources": ["ent_*"],
"conditions": { "request.dry_run": true }
}
],
"api_version": "2026-05-01"
}The request.dry_run: true condition is enforced against the
?dry_run query parameter. A POST /v1/filings without ?dry_run=true does
not match this policy entry, falls through, and returns 403. Tier-2 tokens
also expose only prepare_* / model_* MCP tools — the agent can't even
describe a non-dry-run mutation to the model.
A tier-3 chief-of-staff agent that drives end-to-end workflows but pauses on every destructive operation for a named human approver.
{
"tier": 3,
"principal": { "human_id": "usr_4Kj2m8pQ", "agent_id": "agt_cos" },
"scopes": [
{
"allow": [
"entities.*",
"filings.*",
"documents.*",
"grants.*",
"valuations.*",
"resolutions.*",
"stakeholders.*",
"mail.*"
],
"deny": ["entities.dissolve", "tokens.revoke", "tokens.rotate"],
"resources": ["ent_Nq3KcAbc"],
"limits": { "max_calls_per_hour": 240 }
}
],
"api_version": "2026-05-01"
}Every write returns 202 pending_authorization with a signature_url. The
human signs once; the operation resumes; the operation-specific event fires
(filing.submitted, grant.issued, etc.). Dissolution and token mutations
are explicitly removed even from the * allow — defence in depth in case the
verb catalog later adds something hardcoded-destructive.
A tier-4 compliance agent that files annual reports and pays franchise tax on its own — but only on the routine cadence and only up to USD 5,000/month.
{
"tier": 4,
"principal": { "human_id": "usr_4Kj2m8pQ", "agent_id": "agt_compliance" },
"scopes": [
{
"allow": [
"entities.read",
"filings.read",
"filings.list",
"filings.create",
"tax_profiles.read",
"tax_profiles.update",
"mail.acknowledge"
],
"resources": ["ent_*"],
"conditions": {
"filing.type": ["annual_report", "franchise_tax", "boi_update"],
"mail.category": ["routine_correspondence"]
},
"limits": {
"max_cost_usd": 5000,
"max_filings_per_quarter": 12,
"max_calls_per_hour": 60
}
}
],
"api_version": "2026-05-01"
}The filing.type condition restricts this token to a closed allow-list.
Attempting POST /v1/filings { type: "dissolution" } does not match (because
dissolution is not in the allow-list) and falls through to 403. The
max_cost_usd: 5000 cap is enforced as a sliding monthly window — the 1st of
the month resets it. Once the cap is hit, the next filing returns 202 pending_authorization instead of executing. No surprises mid-month.
A tier-4 venture-studio agent that reads across every entity in a portfolio and writes restricted operations on each. Cross-entity reads enable portfolio-wide reporting; writes are gated per-entity.
{
"tier": 4,
"principal": { "human_id": "usr_studio_admin", "agent_id": "agt_studio_ops" },
"scopes": [
{
"allow": [
"entities.read", "entities.list",
"filings.read", "filings.list",
"compliance.read",
"cap_tables.read",
"stakeholders.read", "stakeholders.list",
"grants.read"
],
"resources": ["pf_studio_fund_i"],
"limits": { "max_calls_per_hour": 1200 }
},
{
"allow": ["filings.create", "tax_profiles.update"],
"resources": ["pf_studio_fund_i"],
"conditions": {
"filing.type": ["annual_report", "franchise_tax"],
"entity.jurisdiction": ["US-DE", "US-CA"]
},
"limits": {
"max_cost_usd": 25000,
"max_filings_per_quarter": 60
}
},
{
"allow": ["resolutions.create"],
"resources": ["pf_studio_fund_i"],
"conditions": {
"resolution.type": ["written_consent", "ratification"]
}
}
],
"api_version": "2026-05-01"
}Three policy entries: read-everything across the portfolio; write annual
reports and franchise tax in DE and CA only, capped at USD 25k/quarter;
ratify routine consents. Anything outside this surface — cross-portfolio
reads, foreign-qualification filings, equity issuance — fails closed and
escalates to a human via Authorization.
Conditions cookbook
Conditions are flexible JSON predicates. The following keys are evaluated against the resolved request:
| Condition key | Type | Matches against |
|---|---|---|
livemode | boolean | true for sk_live_ / live tok_, false for test. |
request.dry_run | boolean | The ?dry_run query parameter. Defaults to false. |
entity.jurisdiction | string[] | Target entity's formation.jurisdiction. |
entity.state | string[] | Target entity's lifecycle state (active, suspended, …). |
filing.type | string[] | Body's type for POST /v1/filings. |
corporate_transaction.stage | string[] | Stage of a target CorporateTransaction. |
mail.category | string[] | Resolved category from mail-classification. |
resolution.type | string[] | Body's type for POST /v1/resolutions. |
time_of_day_utc | object { from, to } | Wall-clock UTC range (e.g. business hours). |
metadata.<key> | various | Arbitrary policy metadata on the target resource. |
Conditions short-circuit on first false. They are evaluated after ID matching, so an off-policy resource can never leak through a permissive condition.
Dual attribution
Every event Matter writes that originated from an agent carries both:
human_principal_id— the human who is legally accountable for the actionagent_id— the runtime agent identity that physically executed it
This is non-negotiable. There is no "system" actor type for agent-driven mutations. Even tier-4 autopilot writes carry the human principal — that's the whole point of the standing authorization.
{
"id": "evt_5pQz8KrL",
"object": "event",
"type": "filing.submitted",
"created": 1745539200,
"data": {
"object": {
"id": "flg_2x7Vc3Mn",
"object": "filing",
"type": "annual_report",
"entity_id": "ent_Nq3KcAbc",
"status": "submitted"
}
},
"authorized_by": {
"human_principal_id": "usr_4Kj2m8pQ",
"agent_id": "agt_compliance",
"token_id": "tok_4Kj2m8pQ",
"tier": 4,
"authorization_id": null,
"via": "standing_policy"
},
"sequence": 41,
"livemode": true
}The authorized_by envelope is denormalised onto every event, every
AuditEntry, and every signed receipt. The via field disambiguates the
authorization source:
via | Meaning |
|---|---|
direct | Human signed in the dashboard. No agent involved. |
standing_policy | Tier-4 token executed within limits. No per-action authorization. |
authorization | Tier-3 (or capped tier-4) action that paused on an Authorization. The authorization_id is non-null. |
oauth | Human-user agent client (Claude Desktop, Cursor, …) over OAuth 2.1. |
Why dual-attribution
Two reasons, in priority order:
- Regulatory traceability. Every state filing, every BOI update, every resolution carries the human's identity on the receipt — that's what state registrars, the IRS, and FinCEN expect to see if they audit. The agent identity is the secondary signal: who moved the bits, on whose behalf.
- Reversibility. When something goes wrong, you need to be able to say "agent X took action Y on principal Z's behalf at time T" and unwind from there. Single-attribution would force you to either treat the agent as a legal person (impossible) or anonymise the action (uninvestigable).
See /api/conventions/observability
for how dual attribution surfaces in distributed traces and request logs.
The Authorization resource
When a tier-3 agent invokes a destructive or high-stakes operation — or a
tier-4 agent exceeds its limits cap — the API does not execute. It returns
202 Accepted with the operation in pending_authorization and embeds an
Authorization resource (auth_…) in the response body.
{
"id": "ctx_9hNm2Bxy",
"object": "corporate_transaction",
"stage": "due_diligence",
"status": "pending_authorization",
"authorization": {
"id": "auth_8sQp4LbR",
"object": "authorization",
"token_id": "tok_4Kj2m8pQ",
"action": "corporate_transactions.advance",
"payload_hash": "sha256-d8f4…",
"status": "pending",
"expires_at": 1745625600,
"signature_url": "https://app.mattermode.com/auth/auth_8sQp4LbR",
"created": 1745539200,
"livemode": true
}
}The human reviews the canonical payload (rendered from payload_hash) at
signature_url, or your own UI calls
POST /v1/authorizations/{id}/approve.
Lifecycle
┌─ approved ─→ operation resumes ─→ <op>.* event
auth.pending ─→ human acts ──┤
└─ denied ─→ operation cancels ─→ <op>.cancelled
└─ expired ─→ operation cancels ─→ <op>.cancelled
(TTL, default 24h)| Status | Description | Next |
|---|---|---|
pending | Awaiting human signature. The operation is paused; the original idempotency key is held. | → approved, → denied, → expired |
approved | A human signed via the dashboard or the API. The original operation resumes from where it paused. | terminal |
denied | A human explicitly rejected the action. The original operation is cancelled and emits <op>.cancelled with cancellation_reason: "authorization_denied". | terminal |
expired | TTL elapsed (default 24h, configurable to 1h–7d at token-creation time). The operation cancels with cancellation_reason: "authorization_expired". | terminal |
Approving an authorization
curl -X POST https://api.mattermode.com/v1/authorizations/auth_8sQp4LbR/approve \
-H "Authorization: Bearer $MATTER_KEY" \
-H "Matter-Version: 2026-05-01" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"approver_stakeholder_id": "stk_ceo_alice",
"signature_method": "esign"
}'The approver must be a Stakeholder on the target entity (or the account
holder for account-level actions). Agents cannot be approvers — by design.
See the FAQ below.
Two-of-N approval policies
For the highest-stakes operations, the policy can require multiple approvers
before the authorization transitions to approved. Configured at
token-creation time on the policy entry:
{
"allow": ["entities.dissolve", "corporate_transactions.close"],
"resources": ["ent_*"],
"approval_policy": {
"quorum": 2,
"approver_role": "director",
"min_quorum_seconds_apart": 0
}
}quorum: 2 requires two distinct directors. The first approval transitions
the authorization to partially_approved; the second to approved. Useful
for dissolution, M&A close, and large equity issuances.
Idempotency under pause
A tier-3 caller's Idempotency-Key is held for the lifetime of the
authorization. If the agent retries the same key while the authorization is
pending, Matter returns the same pause envelope — it does not create a
second authorization. After the authorization terminates (approved / denied /
expired), the idempotency record retains the final outcome for the standard
24h window. See /api/conventions/idempotency.
Payload binding
payload_hash is the SHA-256 of the canonical JSON payload (sorted keys, no
whitespace) of the original request. If the client modifies the payload — even
whitespace — the authorization is invalidated and the next call mints a new
one. The human signs the exact artifact the API will execute, not an
agent-narrated description of it.
MCP catalog visibility per tier
Matter ships an MCP server at apps/mcp/ that exposes the API as Model
Context Protocol tools. The catalog is generated from the OpenAPI spec — every
operation with x-matter-mcp.exposed: true becomes one tool. The token's
tier filters which tools the runtime sees on tools/list.
| OpenAPI extension | Meaning | Affects |
|---|---|---|
x-matter-mcp.exposed | Whether the operation appears in the catalog at all. | Catalog membership. |
x-matter-mcp.min_tier | Minimum token tier that sees this tool. 1 for reads, 3 for most writes. | Tier filter. |
x-matter-mcp.read_only | Tool reads but never mutates. Always tier-1 visible. | Tier-1 / tier-2 catalogs. |
x-matter-mcp.destructive | Tool causes irreversible state change. Always pauses on Authorization regardless of tier. | Tier-3+ visibility with hard pause. |
x-matter-mcp.idempotent | Tool may be safely retried with the same Idempotency-Key. | Annotation only — no enforcement. |
x-matter-mcp.tool_name | The MCP tool name. Stable across versions. | All tiers. |
A tier-1 token introspecting the MCP server sees only tools where
read_only: true || min_tier <= 1. A tier-4 token sees the full catalog with
destructive: true tools annotated as requires_authorization.
This is enforced server-side at tools/list time. The agent runtime does not
choose what to expose — the token does.
See /mcp/introduction for the full MCP overview and
/mcp/authentication for the OAuth 2.1 + token
exchange flow.
Test mode for agents
Iterating on agent policies against the live API would be slow — multi-day state filings, real fees, real dissolutions. Test mode is built for this.
A sk_test_ key (or any test-mode tok_) operates against an isolated
test-mode dataset. State filings simulate; fees do not transact. The real
accelerator for agent development is X-Matter-Test-Speed:
| Header value | Behaviour |
|---|---|
real | Default. Test-mode timelines mirror live (multi-day cascades). |
fast | Compresses every wait to 1/100th. A 7-day annual report cycles in ~6 minutes. |
instant | Compresses to milliseconds. The whole formation cascade resolves before your test assertion runs. |
Combine with ?auto_approve=true on a test-mode token-creation request to
have all Authorization resources auto-approve immediately:
curl -X POST https://api.mattermode.com/v1/tokens \
-H "Authorization: Bearer $MATTER_TEST_KEY" \
-H "Matter-Version: 2026-05-01" \
-d '{
"tier": 3,
"principal": { "human_id": "usr_test", "agent_id": "agt_test" },
"scopes": [{ "allow": ["entities.*", "filings.*"], "resources": ["ent_*"] }],
"auto_approve": true,
"api_version": "2026-05-01"
}'The test-mode token created with auto_approve: true sees every
Authorization flip from pending to approved within ~50ms — perfect for
end-to-end agent test suites.
Test-mode receipts are signed with a separate cryptographic root key
hierarchy. Test artifacts cannot be replayed against live mode, and a test
mode Authorization cannot authorize a live-mode operation. The
auto_approve flag is rejected on live keys with 400 invalid_request.auto_approve_live_mode.
See /api/conventions/test-mode for the full test-mode
contract, including TestClock for time travel.
OAuth 2.1 for human-user agent clients
Claude Desktop, Cursor, Windsurf, Zed, Codex, and similar IDE / chat clients use OAuth 2.1 with PKCE to get scoped access tokens on behalf of a signed-in human. The tokens map to the same structured-policy DSL — there is no parallel scope vocabulary.
Endpoints:
- Authorization URL:
https://mattermode.com/oauth/authorize - Token URL:
https://mattermode.com/oauth/token - Revocation URL:
https://mattermode.com/oauth/revoke - Discovery:
https://mattermode.com/.well-known/oauth-authorization-server
The OAuth scope parameter is a space-separated list of the same
<resource>.<verb> strings used in the policy DSL. The authorization server
maps the requested scopes to a synthetic tok_ under the hood, with the
human-user as the human_principal_id and the OAuth client as the
agent_id.
Tier mapping for OAuth flows:
| OAuth scope class | Resulting tier |
|---|---|
*.read only | 1 |
Includes *.create with dry_run enforced | 2 |
Includes any non-dry-run *.create / *.update | 3 |
Requires offline_access + spending caps | 4 |
Tier-4 over OAuth requires the user to explicitly confirm the spending caps and the autonomous surface during consent — the consent screen renders the policy entries verbatim, not as a marketing summary.
See /mcp/authentication for the full OAuth flow,
including PKCE, refresh tokens, and the device-flow variant for headless
clients.
Token lifecycle
Tokens are minted, optionally rotated, and revoked. There is no implicit
expiry beyond what the caller sets via expires_at.
Minting
curl -X POST https://api.mattermode.com/v1/tokens \
-H "Authorization: Bearer $MATTER_KEY" \
-H "Matter-Version: 2026-05-01" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"tier": 3,
"principal": { "human_id": "usr_4Kj2m8pQ", "agent_id": "agt_cos" },
"scopes": [
{
"allow": ["entities.*", "filings.*", "documents.*"],
"deny": ["entities.dissolve"],
"resources": ["ent_Nq3KcAbc"],
"limits": { "max_calls_per_hour": 240 }
}
],
"expires_at": 1772150400,
"api_version": "2026-05-01"
}'The response contains the one-time-shown plaintext tok_… secret. Matter
never stores the plaintext — losing it means revoking and re-minting.
Rotating
POST /v1/tokens/{id}/rotate issues a new secret bound to the same id,
tier, scopes, and principal. The old secret stays valid for 60
seconds to absorb in-flight requests. After 60 seconds the old secret is
permanently rejected.
curl -X POST https://api.mattermode.com/v1/tokens/tok_4Kj2m8pQ/rotate \
-H "Authorization: Bearer $MATTER_KEY" \
-H "Matter-Version: 2026-05-01" \
-H "Idempotency-Key: $(uuidgen)"The 60-second overlap is non-configurable. Longer overlaps amplify the blast radius of a leak; shorter overlaps drop legitimate retries.
Revoking
POST /v1/tokens/{id}/revoke invalidates the token immediately. In-flight
requests using the token receive 401 unauthorized on next call. Audit
entries from before revocation are preserved — revocation is forward-only.
curl -X POST https://api.mattermode.com/v1/tokens/tok_4Kj2m8pQ/revoke \
-H "Authorization: Bearer $MATTER_KEY" \
-H "Matter-Version: 2026-05-01"If the token had pending Authorization resources, they transition to
expired immediately and the paused operations cancel.
Version pinning
Every token pins api_version at creation. The token always behaves as if
the request were made with Matter-Version: <pinned>. A per-request
Matter-Version header overrides for that request only — useful for
canary-testing a new version before rotating the token's pinned version.
See /api/conventions/versioning
for the monthly release cadence.
Errors specific to the agent surface
| Status | Error code | When |
|---|---|---|
401 | invalid_token | Token does not exist, was revoked, or its plaintext does not match. |
401 | token_expired | expires_at has elapsed. |
403 | tier_too_low | Operation requires a higher tier than the token holds. |
403 | missing_grant | No scopes[] entry matched the verb-resource pair. |
403 | condition_not_met | A matching entry's conditions evaluated false (e.g. dry_run required, livemode mismatch). |
409 | pending_authorization | The operation is paused on an Authorization. The body includes the auth_…. |
409 | authorization_payload_mismatch | Caller retried with a modified payload while an authorization for the prior payload was still pending. |
429 | limit_exceeded | A limits cap was hit. Body identifies which cap and when it resets. |
503 | oauth_consent_required | OAuth-derived token attempted an action whose scope was not granted at consent. The body includes a fresh consent_url. |
All errors follow RFC 7807 (application/problem+json). See
/api/conventions/errors for the
common envelope.
FAQ
Related
/api/get-started#authentication— how Bearer auth and version pinning work for all credentials, not just agent tokens./api/auth/tokens— theTokenresource shape, generated from the spec./api/authorizations/index— theAuthorizationresource and its endpoints (approve,deny,retrieve)./api/conventions/test-mode—sk_test_keys,X-Matter-Test-Speed,TestClock, and the test-mode key hierarchy./api/conventions/errors— RFC 7807 error envelope used by every agent-surface error code above./api/conventions/idempotency— howIdempotency-Keyis held across a paused authorization./api/conventions/observability— howauthorized_bysurfaces in logs, traces, and request IDs./api/events/webhooks— HMAC-SHA256 signing, per-entity ordering, and the dual-attribution payload./mcp/introduction— the MCP server, generated from the OpenAPI spec./mcp/authentication— OAuth 2.1 + PKCE for human-user agent clients./mcp/scope-policies— step-by-step authoring guide for the policy DSL with tier-transition examples and a decision tree.