Errors and reason codes
Every API error is an RFC 9457 problem document with content type application/problem+json. The type URL points at the matching section of this page, so https://docs.sectigoaxiom.com/reference/errors#idempotency_conflict lands on idempotency_conflict.
{
"type": "https://docs.sectigoaxiom.com/reference/errors#idempotency_conflict",
"title": "idempotency conflict",
"status": 409,
"code": "idempotency_conflict",
"detail": "operation key reused with different material parameters; nothing was changed",
"reason_code": "material_change",
"changed_fields": ["params.amount"],
"action_id": "act_01kd0m7s2v8x9yz3abcdefghjk"
}| Field | Present when |
|---|---|
code | Always. A stable error code; branch on this, not on title or detail. |
reason_code | The error is a policy or lifecycle decision (for example tenant_mismatch, wrong_resume_actor). |
changed_fields | idempotency_conflict on submit, or conflict on resume: the material fields that differ. |
action_id | The error refers to an existing action. |
issues | validation_failed: { field, message } per failing field (dotted paths, e.g. params.amount). |
retry_after_seconds | A retry can succeed later. Also sent as the Retry-After header. |
Two other error shapes exist:
- The OAuth endpoints (
/oauth/token,/oauth/register,/oauth/authorize) answer with RFC 6749 §5.2 bodies:{ "error": "invalid_client", "error_description": "…" }. Codes used:invalid_client(401),invalid_dpop_proof(401),invalid_grant,invalid_requestandunsupported_grant_type(400). The SDK maps both shapes onto oneAxiomProblemwith acode. - MCP endpoints answer with JSON-RPC errors; a domain error inside a tool becomes a tool result with
isError: trueand the problem document asstructuredContent. See MCP.
A caller that cannot see a resource always gets 404 not_found, never 403, so the response does not reveal whether the resource exists.
Error codes
Defined in packages/core/src/errors.ts (ERROR_CODES).
unauthenticated
401. No credential was presented where one is required (agent endpoints, console endpoints, the Gateway proxy). The proxy and MCP endpoints add a WWW-Authenticate header: DPoP algs="ES256 EdDSA" on the Gateway proxy, and Bearer resource_metadata="…/.well-known/oauth-protected-resource/mcp" on the Axiom MCP server so MCP clients can discover the authorization server.
What to do: authenticate. Agents get a DPoP-bound token (Authentication); people sign in to the console or use the OAuth authorization-code flow.
invalid_token
401. The access token is unknown, expired (agent tokens last 5 minutes), revoked, presented with the wrong scheme (Bearer for a DPoP-bound token), issued for a different MCP resource, or its credential has been revoked or has expired (reason_code: credential_revoked). Also returned for an invalid or revoked project API key.
What to do: get a new token and retry once. The SDK does this automatically. If the new token is rejected too, the credential itself is no longer valid: see Lost or compromised keys.
invalid_dpop_proof
401. The DPoP header is missing, malformed, signed by a key other than the one the token is bound to, has the wrong htm or htu, an iat more than 120 seconds from server time, a wrong ath, or a jti that was already used (replay). On /v1/authorize it also means the proof was not made for the service's upstream origin.
What to do: fix the proof; do not retry in a loop. Check the clock, that htu is the exact URL without query string, that ath is the base64url SHA-256 of the token, and that every request uses a fresh jti.
forbidden
403. The caller is authenticated but the request is refused. Usually carries a reason_code: tenant_mismatch (the request names another tenant), wrong_resume_actor, agent_suspended, issuer_not_trusted, separation_of_duties, approver_ineligible, delegation reasons, or a Gateway proxy refusal (operation_not_permitted for an unmapped route, or a denied action). Also used when a token lacks a required scope (actions:submit) or a project API key lacks provider:authorize.
What to do: read reason_code, report it and stop. A different request, credential or grant is needed; retrying the same call will not succeed.
not_found
404. The resource does not exist, or the caller may not see it. Agents get 404 for other agents' actions; people get 404 for projects, tenants and approvals outside their roles. Also returned when an operation is not protected on the named service.
What to do: treat the resource as absent. Never guess IDs.
conflict
409. The request conflicts with the record. On resume: the parameters, resource or operation key differ from the approved request (reason_code: material_change, changed_fields). On the approval page: the request changed after the approver viewed it. Control endpoints use it for, for example, a domain verified by another organisation.
What to do: do not "fix" the parameters to match. A changed request needs a new submit with a new operation key and a fresh approval.
idempotency_conflict
409. The operation key is already used for a different request in this tenant and service: either a material field differs (reason_code: material_change, with changed_fields and the existing action_id) or another agent used the key. The existing action is unchanged.
What to do: the original action stands. Report changed_fields. If the change is intended, it is a new intent: choose a new key. See Idempotency and conflicts.
invalid_state
409. The action is in the wrong state for the call: resuming before approval (with retry_after_seconds and the current reason_code), resuming a closed action, cancelling after dispatch, or reporting an outcome for an action that is not claimed. Two execution-ownership rules also answer invalid_state, on every Axiom path (REST resume, the HTTP and MCP proxies and the Axiom MCP server):
- the service runs in embedded-SDK mode, so only the provider may claim: repeat the original request to the provider with the same operation key;
- after a cutover, a customer-hosted gateway holds the project's executor flag: resume through that gateway.
The Gateway proxy also uses it for an expired or cancelled action.
What to do: read status and follow the state table in Action state machine.
precondition_failed
412. Reserved for conditional requests. No endpoint in the current release returns it.
invalid_request
400. The request is malformed: the body is not JSON, a gateway request body exceeds 1 MiB, a required Idempotency-Key is missing on a proxied write, a challenge is expired or already used, a redirect URI is not allowed, or a connector does not support the requested operation.
What to do: fix the request.
validation_failed
422. The body failed schema validation, or material fields failed the operation's field specification (wrong type, missing required field, undeclared field, decimal not a string, currency not ISO 4217, wrong resource type). issues lists each field.
What to do: fix the fields in issues. Decimals are strings ("25000.00"), currencies are three upper-case letters.
rate_limited
429. Too many attempts: sign-in (magic links, passkeys) or the free public services (for example 30 identity checks per minute per client address). Carries retry_after_seconds and Retry-After.
What to do: wait for retry_after_seconds.
quota_exceeded
402. The project's plan limit is reached: new actions in the month, or another protected service. Existing actions, approvals, status, resume and receipts keep working. Paid plans with a spend limit above zero can exceed included actions. See Plans and limits.
What to do: report to the user; ask the project owner to raise the plan or spend limit.
upstream_error
502. A system Axiom depends on failed: for example the enterprise identity provider rejected an SSO code.
What to do: retry later; if it persists, check the upstream system.
upstream_timeout
504. The Gateway proxy dispatched the request but the upstream did not answer (transport error or timeout after 20 seconds). The action is now outcome_unknown (reason_code: target_timeout) and reconciliation by operation key is queued. The response carries Axiom-Action-Id.
What to do: never resend. Track the action ID until reconciliation restores a final state. See Execution and delivery.
internal
500. An unexpected server error. The x-request-id response header identifies the request.
What to do: retry a read. For a submit, retry with the same operation key, which is always safe. For a resume, read status before doing anything else.
Reason codes
Reason codes (REASON_CODES in packages/core/src/states.ts) explain a decision. They appear as reason_code on every action status and on problem documents that are decisions.
Allow
| Code | Meaning | What to do |
|---|---|---|
allowed_by_grant | The grant covers the request and no policy layer requires approval or denies it. | Nothing: within a standing grant the action is dispatched at once (unless execute: false). |
Approval and progress
| Code | Meaning | What to do |
|---|---|---|
approval_required | A rule requires approval and does not name a more specific reason. | Persist the action ID and wait. |
finance_approval_required | A rule requiring finance approval matched (for example a refund above the threshold). | Persist, tell the user who can review it, wait. |
second_approver_required | One approval was recorded but the rule requires more distinct approvers. | Keep waiting; a different person must approve. |
awaiting_delivery | Reserved. Delivery progress is reported as progress: delivery_pending. | Wait. |
awaiting_decision | Reserved. Reported as progress: waiting_on_person. | Wait. |
decision_readback_unavailable | Reserved. Reported as progress: decision_readback_unavailable when the connector cannot be read. | Wait; an admin should check the connector. |
awaiting_agent_resume | Every required approval is recorded; the action is approved and waits for the original agent. | Resume as the original agent before resume_deadline. |
Deny and close
| Code | Meaning | What to do |
|---|---|---|
no_grant | The agent holds no grant on this service. | Stop. A tenant admin must grant access. |
grant_expired | The grant expired (at submit, or found expired when the agent resumes). | Stop; submit a new request once authority exists. |
grant_revoked | The grant, a parent grant in its delegation chain, or a suspended grant no longer authorises. | Stop. |
grant_scope_mismatch | The grant is not yet valid, belongs to another agent, or a child grant is bound to another root action. | Stop. |
tenant_mismatch | The request names a tenant other than the authenticated agent's, or the grant is for another tenant. | Stop; tenant always comes from the credential. |
audience_mismatch | The grant is for a different service. | Stop. |
resource_not_permitted | The resource is outside the grant's resource patterns. | Stop. |
operation_not_permitted | The grant does not include the operation, or (Gateway proxy) the route or tool is not mapped. | Stop. |
agent_suspended | The agent is suspended (or not active). | Stop; see Disabled agents. |
agent_retired | The agent is retired. | Stop; a retired agent cannot return. |
credential_revoked | The credential used for the action or token was revoked or expired. | Re-authenticate with an active key; an action created with a revoked credential is revoked at resume. |
issuer_not_trusted | A workload identity's issuer is not in the tenant's trust profile. | Ask the tenant admin to add it. |
ceiling_exceeded | A hard ceiling rule matched; no approval or override can lift it. | Stop. |
policy_deny | A deny rule matched and names no more specific reason. | Stop. |
emergency_deny | An incident published an emergency deny covering the agent, credential, grant, tenant, service or project. | Stop; see Safe release after containment. |
delegation_not_permitted | The parent grant is not delegable, or the child is the same agent. | Stop. |
delegation_expands_scope | The child grant would outlive the parent or widen operations or resources. | Request a narrower child grant. |
delegation_depth_exceeded | The parent is already a delegated grant; one hop is supported. | Stop. |
wrong_resume_actor | A different agent tried to resume. Only the original agent can. | Stop. |
material_change | The request differs from the approved or recorded one. | Submit a new request with a new key. |
approval_denied | An approver denied the request. | Report and stop. |
approval_expired | No verified decision arrived before decision_deadline, or the request validity ended. | Report and stop; a new request is needed. |
approver_ineligible | At resume, a recorded approver no longer holds the required role; or the caller cannot decide this approval. | Stop; a fresh approval is needed. |
separation_of_duties | The approver owns or sponsors the agent, or authored the policy that required approval. | A different person must approve. |
policy_stale | Policy changed after approval and now requires an approval that was not satisfied. | Submit a new request for fresh approval. |
status_stale | Reserved for freshness-bounded decisions at customer-hosted gateways. | Stop and retry once the gateway is fresh. |
quota_exceeded | Reserved for quota decisions; quota errors are returned as the quota_exceeded error code. | See the error code. |
invalid_request | Reserved; used as the reason on a proxied write without Idempotency-Key. | Fix the request. |
Execution
| Code | Meaning | What to do |
|---|---|---|
dispatched | Execution was claimed (executing) and sent to the target. | Track by status; never resend. |
target_confirmed | The action succeeded and the provider's signed outcome attestation confirms it. | Read the outcome and receipt. |
target_failed | The action failed and the provider's signed attestation says the business write did not commit. | Read the outcome. |
gateway_observed_success | The action succeeded on an unsigned 2xx: the gateway observed the response; the destination did not confirm it, and the receipt's outcome check is missing. | Read the outcome; register outcome keys on the service for destination confirmation. |
gateway_observed_failure | The action failed on an unsigned 4xx (other than 408 and 429): the gateway observed a rejection that the destination did not attest. | Read the outcome; a new attempt needs a new operation key. |
target_timeout | The target did not answer, answered 5xx, 408 or 429, or returned an invalid attestation: outcome_unknown. | Track; reconciliation by operation key is running. |
resume_expired | The action was approved but the original agent did not resume before resume_deadline (or the request validity ended). | Stop; a new request needs fresh approval. |
reconciled | Reconciliation restored a final state from the provider's signed record. | Read the outcome and receipt. |
cancelled_by_requester | The original agent cancelled before dispatch. | Nothing further happens. |
The fine-grained progress field on a status adds: delivery_pending (the approval request is being delivered), waiting_on_person, decision_readback_unavailable (the connector could not be read; it is retried every 60 seconds), awaiting_agent_resume and reconciling.