Error and reason codes
The JSON-RPC codes, the error envelope, every reason the MAQPNA gateway returns, the SDK error for each, and the CLI exit codes.
When the MAQPNA gateway refuses or holds a call, it says why in one envelope with a stable reason. Match on the reason, not on the message text. This page lists the JSON-RPC codes, the envelope, every published reason with the SDK error it becomes and what to do, and the exit codes of the maqpna CLI.
flowchart LR
G[Gateway decision] --> C{JSON-RPC code}
C -->|-32001| PD["PolicyDenied (reason: policy_denied, dlp:…, revoked, budget_exceeded:…)"]
C -->|-32002| AP["ApprovalPending (reason: approval_required)"]
C -->|-32003| UA["Unauthorized (reason: unauthorized, missing_scope:…)"]
C -->|-32603| RE["RPCError (reason: audit_unavailable, internal_error)"]
C -->|-32020 / -32022| PR["HeaderMismatch / UnsupportedProtocolVersion"]
JSON-RPC codes#
| Code | Meaning | Default reason | Python / TypeScript | Go |
|---|---|---|---|---|
-32001 |
Denied | policy_denied |
PolicyDenied |
*PolicyDenied |
-32002 |
Held for approval (only when the request carries X-Maqpna-Async: 1) |
approval_required |
ApprovalPending |
*ApprovalPending |
-32003 |
Unauthorized: bad or expired token, or a missing scope | unauthorized |
Unauthorized |
*Unauthorized |
-32020 |
Model Context Protocol (MCP) revision 2026-07-28: an Mcp-Method, Mcp-Name or MCP-Protocol-Version header does not match the body |
header_mismatch |
HeaderMismatch |
*HeaderMismatch |
-32021 |
MCP 2026-07-28: a client capability the request needs was not declared | missing_client_capability |
RPCError |
*RPCError |
-32022 |
MCP 2026-07-28: protocol version not supported; data.supported lists the fallbacks |
unsupported_protocol_version |
UnsupportedProtocolVersion |
*UnsupportedProtocolVersion |
-32700, -32600, -32601, -32602, -32603 |
Standard JSON-RPC: parse error, invalid request, method not found, invalid params, internal error | parse_error, invalid_request, method_not_found, invalid_params, internal_error |
RPCError |
*RPCError |
In sync mode (the default) a call that needs approval does not return -32002: the gateway holds it until a person decides, then returns the result or -32001 with approval_denied or approval_expired. See Human approvals.
The error envelope#
Every error the gateway generates carries the same fields in error.data:
| Field | Meaning |
|---|---|
domain |
Always maqpna.com for gateway errors. |
reason |
<token>[:<qualifier>]. The token is from the table below; the optional qualifier adds machine-readable context: dlp:pan, budget_exceeded:agent/day, missing_scope:tools:github. Match on the text before the first :. |
detail |
Free text when there is more to say, such as the reason text of the rule that denied the call, or approval_denied by reviewer@localhost (apr_…). |
policy, rule |
The policy and rule that decided, when one did. Revocations show revocation/<id>; data loss prevention (DLP) shows policy dlp and the profile name as rule. |
| others | approvalId and expiresAt (-32002), connectURL (consent_required). |
Real envelopes from a local MAQPNA (maqpna dev up), as maqpna call prints them:
# a rule denied the call
error -32001: denied by policy baseline-guardrails rule never-touch-system-namespaces: system namespaces are off-limits to agents
data: {"detail":"system namespaces are off-limits to agents","domain":"maqpna.com","policy":"baseline-guardrails","reason":"policy_denied","rule":"never-touch-system-namespaces"}
# an approver denied the held call
error -32001: denied by policy default-agent-policy rule destructive-needs-human: approval_denied by reviewer@localhost (apr_902e3a670541349ffc7844a3)
data: {"detail":"approval_denied by reviewer@localhost (apr_902e3a670541349ffc7844a3)","domain":"maqpna.com","policy":"default-agent-policy","reason":"approval_denied","rule":"destructive-needs-human"}
# the session was revoked with maqpna kill
error -32001: revoked: calls of this token are blocked by revocation/breakglass/51c5fe6e6ada148d (INC-1: test)
data: {"domain":"maqpna.com","policy":"revocation/breakglass/51c5fe6e6ada148d","reason":"revoked","rule":""}
A bad token, as raw JSON-RPC:
{"jsonrpc":"2.0","id":null,"error":{"code":-32003,"message":"unauthorized: identity: malformed token: expected 3 segments","data":{"domain":"maqpna.com","reason":"unauthorized"}}}
A DLP block (the profile is the rule):
data: {"domain":"maqpna.com","policy":"dlp","reason":"dlp:pan","rule":"payments"}
Model route errors#
The model route (/llm/<route>/v1/*) answers OpenAI-style, so OpenAI SDKs raise their normal API errors. type is the error class, code keeps its earlier value for compatibility, and reason is the published token:
{"error":{"code":"route_not_found","domain":"maqpna.com","message":"unknown model route \"nope\"","reason":"route_not_found","type":"maqpna_not_found"}}
The types are maqpna_unauthorized, maqpna_policy_denied, maqpna_approval_required, maqpna_budget_exceeded (HTTP 402), maqpna_rate_limited, maqpna_egress_denied, maqpna_dlp_blocked, maqpna_revoked, maqpna_audit_unavailable, maqpna_upstream_error, maqpna_not_found and invalid_request_error.
Admin API errors#
The admin API (/v1/*, used by the CLI, the console and MAQPNA Desk) answers {"error": "<message>"} for now. Moving it to the same envelope is Planned.
Reasons#
The list is append-only: a reason is never renamed or removed in v1alpha1. The audit ledger uses the same words, so a reason in an error matches the reason in the audit record.
Authentication and authorisation#
These come with -32003 (Unauthorized) on tool calls, or HTTP 401/403 on the model route.
| Reason | Meaning | What to do |
|---|---|---|
unauthorized |
Missing, invalid or insufficient credentials (default of -32003). |
Check that the agent sends the session token (MAQPNA_TOKEN_FILE or MAQPNA_TOKEN). Inspect it with maqpna token inspect. |
missing_token |
No bearer token. | Run the agent under maqpna dev run, or check the sandbox's token mount. |
invalid_token |
Token malformed or not verifiable. | Mint a new token; check the gateway trusts the broker's public keys (JWKS). |
token_expired |
The token has expired. | The SDKs re-read the token file; make sure the operator can re-mint it. Locally, raise maqpna dev run --ttl. |
token_signature_invalid |
Signature invalid or unknown signing key. | Check key rotation: maqpna token verify TOKEN --jwks URL. |
token_audience_mismatch |
Wrong aud. |
Mint for audience maqpna-gateway. |
missing_scope |
The token lacks the scope, for example missing_scope:tools:github. |
Add the MCP server to the agent's tools (Agent.spec.tools), or the scope to maqpna dev run --scope. |
insufficient_scope |
An external client token lacks a step-up scope. | Ask the user to re-authorise with the scope. |
header_mismatch |
An identity header does not match the token, or an MCP header does not match the body (-32020). |
Do not set X-Maqpna-* identity headers yourself; let the SDK build the MCP headers. |
principal_unverified |
The user the agent acts for could not be verified. | Check the session's user assertion and the identity provider configuration. |
svid_invalid, svid_required, svid_trust_domain, svid_mismatch |
Workload identity (SPIFFE) client certificate problems. | Check the SPIRE setup and tls.svidMode. |
tenant_mismatch |
The token's key or trust domain belongs to another tenant. | Mint the token with the tenant's own broker key. |
Policy#
These come with -32001 (PolicyDenied).
| Reason | Meaning | What to do |
|---|---|---|
policy_denied |
A rule denied the call; its text is in detail, the rule in rule. |
Read the rule; test a change with maqpna policy test. See Write policies. |
no_applicable_policy |
No policy applies to this agent and namespace (default deny). | Create a ToolPolicy that selects the agent. |
default_action |
No rule matched and the policy's defaultAction decided. |
Add a rule for the tool, or change the default. |
rate_limited |
maxCallsPerMinute or tokensPerMinute was exhausted. |
Back off and retry; raise the limit in the rule if it is too low. |
revoked |
A revocation (kill switch) matches the agent, session, user or token. | Find it with maqpna revocations list. See Kill switch and revocations. |
revocation_list_unavailable |
The gateway cannot read the revocation list, so calls are denied. | Check the gateway's revocation file or state backend; maqpna doctor. |
budget_exceeded |
A budget is used up; qualifier <scope>/<window>, for example budget_exceeded:agent/day. |
Wait for the window to reset or raise the budget. See Budgets and cost limits. |
dlp |
A DLP profile found sensitive data; qualifier is the detector, for example dlp:pan, dlp:iban, dlp:github_token. |
Remove the data from the arguments, or change the profile. See DLP profiles. |
guard |
A prompt-injection guard flagged the content; qualifier <guard>:<label>. |
See Taint and prompt-injection containment. |
guard_unavailable |
The guard is unreachable and failOpen is false, so calls are denied. |
Check the guard service. |
tool_definition_changed |
The tool's definition changed after it was pinned (drift). | Review the change, then re-pin with maqpna mcp pin. See MCP servers and tool pinning. |
tool_unpinned |
The tool is not pinned and unpinned tools are denied. | Pin it. |
tool_definition_unverified |
The tool definition could not be verified. | Check the MCP server and its pins. |
cedar_unavailable |
A Cedar policy exists but this gateway was built without Cedar support. | Use a gateway build with Cedar, or rewrite the rule. |
cedar_error |
Cedar evaluation failed, so the call is denied. | Check the policy with maqpna policy lint. |
rego_unavailable, external_policy_error |
The external policy engine (OPA or an adapter) failed, so the call is denied. | Check the engine's health and timeout. |
egress_denied |
The destination is outside the egress or residency policy. | Allow the host in the policy, if it is allowed in your jurisdiction. |
model_not_allowed_for_data_class |
The model route is not allowed for the session's data class. | Use a model route approved for that data class. |
Approvals#
| Reason | Code | Meaning | What to do |
|---|---|---|---|
approval_required |
-32002 |
Held for a person (async mode). data.approvalId, data.expiresAt. |
Poll with wait_for_approval / waitForApproval / WaitForApproval, or tell the user. |
approval_denied |
-32001 |
An approver denied the call; detail names the approver and the approval. |
Do not retry the same call; change the plan. |
approval_expired |
-32001 |
Nobody decided before the approval expired. | Retry to queue a new approval, or ask approvers to watch the queue (maqpna approvals watch). |
approval_invalid |
-32001 |
The X-Maqpna-Approval-Id does not belong to this exact call. |
An approval covers exactly one identical call; queue a new one. |
approval_aborted |
-32001 |
The approval wait was aborted. | Retry. |
approval_required_not_supported_for_egress, approval_required_unsupported_for_models |
-32001 |
A rule asks for approval on egress or a model call, which cannot wait. | Use allow or deny for those, and approvals for tool calls. |
user_confirmation_unavailable |
-32001 |
User approval on their own device (CIBA) could not be asked, and the fallback denied the call; for example user_confirmation_unavailable:ciba_rejected. |
Check the CIBA configuration and the user's device. |
ciba_rejected |
-32001 |
The user rejected the request on their device. | Respect the decision. |
ciba_unavailable |
-32001 |
The CIBA request could not be sent. | Check the identity provider's CIBA endpoint. |
Routing and resources#
| Reason | Meaning | What to do |
|---|---|---|
unknown_server |
No MCP server of that name is visible to the caller (-32003). |
Check the name with maqpna mcp list and the agent's tools. |
ambiguous_server |
The name matches several MCP servers. | Use a unique server name. |
cross_tenant_server |
The server belongs to another tenant. | Use a server in your tenant's namespaces. |
route_not_found |
Unknown model route. | Check the route name in MAQPNA_MODEL_ENDPOINT. |
route_namespace_not_allowed, namespace_not_allowed |
The model route is not allowed for this namespace. | Allow the namespace on the route. |
route_agent_not_allowed, agent_not_allowed |
The model route is not allowed for this agent. | Allow the agent on the route. |
consent_required |
The user must connect the provider first; data.connectURL has the link (-32001). |
Send the user to connectURL, then retry. |
per_user_unavailable |
Per-user credentials are not configured. | Configure the token vault for the provider. |
memory_backend_unavailable |
The memory store backend is unavailable. | Check the memory store. |
Agent-to-agent (A2A)#
| Reason | Meaning |
|---|---|
unknown_agent |
The A2A callee is unknown. |
caller_not_allowed |
The caller is not in the callee's allowedCallers. |
unknown_skill, unknown_method |
The skill or method does not exist. |
delegation_depth |
The delegation chain is too deep. |
delegation_loop |
The delegation loops back to an earlier agent. |
callee_revoked |
The callee is revoked. |
peer_card_unverified |
The remote peer's agent card signature could not be verified. |
Availability#
These usually come with -32603 (RPCError), or TransportError when the gateway's answer is not JSON-RPC.
| Reason | Meaning | What to do |
|---|---|---|
audit_unavailable |
auditFailurePolicy: closed and the gateway could not write the audit record, so it refused the call. |
Check the ledger volume or the Postgres state backend: maqpna doctor. |
upstream_unavailable |
The MCP server or model could not be reached. | Check the upstream; retry with backoff. |
upstream_credential_unavailable |
The credential for the upstream is missing. | Check the referenced Secret. |
upstream_error |
The upstream failed. | See the upstream's logs. |
egress_dial_failed, egress_upstream_failed |
The egress proxy could not connect, or the destination failed. | Check DNS and the destination. |
Protocol#
| Reason | Meaning |
|---|---|
parse_error |
Invalid JSON (-32700). |
invalid_request |
Invalid JSON-RPC request (-32600). |
invalid_params |
Invalid params (-32602). |
method_not_found |
Unknown method (-32601). |
internal_error |
Internal error (-32603), and any error without a reason. |
invalid_body, invalid_json |
The model-route request body could not be read, or is not JSON. |
unsupported_protocol_version |
-32022: the gateway, or the MCP server's declared protocolVersions, does not support the revision; data.supported lists the fallbacks. The SDKs fall back automatically with protocol="auto". |
missing_client_capability |
-32021: the request needs a client capability that was not declared. |
CLI exit codes#
Every maqpna command (and the maqpna-install plugin) uses the same exit codes, so scripts and CI can branch on them:
| Exit code | Meaning | Examples |
|---|---|---|
0 |
Success. | A call was allowed; audit verify found the chain intact. |
1 |
Error: the action did not happen. | Gateway unreachable, file not found, HTTP 404 from the admin API. |
2 |
Usage error, including a destructive command run without a terminal and without --yes. |
An unknown flag; maqpna kill in CI without --yes. |
3 |
A check failed, or the gateway refused the call. | maqpna call denied by policy, DLP or a revocation; maqpna audit verify found tampering; maqpna policy test --expect mismatch. |
128+N |
The maqpna-install plugin died of signal N. |
— |
maqpna dev run exits with your command's exit code. For example, a denied maqpna call under it:
maqpna dev: session dev-0cf86530 (agent coder, namespace dev, on behalf of you@localhost)
error -32001: denied by policy baseline-guardrails rule never-touch-system-namespaces: system namespaces are off-limits to agents
data: {"detail":"system namespaces are off-limits to agents","domain":"maqpna.com","policy":"baseline-guardrails","reason":"policy_denied","rule":"never-touch-system-namespaces"}
maqpna dev: dev-0cf86530: 1 deny, cost $0.0000 (maqpna dev timeline --last)
$ echo $?
3
Next: Python SDK, Troubleshooting.