aegis · Reference
Error codes
Every JSON-RPC error code aegis returns, what stage produced it, the matching HTTP status, and how a client should handle each refusal.
On this page
Every refusal from aegis is a JSON-RPC 2.0 error object with a specific code, so your client can branch on exactly why a call stopped: a policy denial, a threat block, a held call or an unavailable upstream. This page lists every code the invoke path (/mcp and /v1/invoke) returns, what triggers it and what to do next.
How errors come back#
An error response has the standard shape: error.code, a readable error.message and, for a few codes, an error.data object. The id echoes your request, except on an HTTP 400, where the body could not be read as a request and id is null. Every JSON-RPC response also carries an X-Correlation-ID header, which ties it to the gateway's logs and audit records. If you send a valid W3C traceparent header, its trace-id becomes the correlation ID.
Most refusals return HTTP 200 with the error in the body, so check error.code, not the status. These are the exceptions:
A notification, a request with no id or a null one, gets HTTP 202 with an empty body once it is authenticated and parsed. It never produces a JSON-RPC error.
Standard JSON-RPC codes#
aegis codes#
The twelve codes from -32003 to -32064 are specific to aegis. -32000 is the generic upstream error, covered under Upstream errors below.
Checks run in a fixed order. The session, rate limit and quota come first, then routing, the kill-switch and RBAC. So another tenant's tool returns -32601, and a stopped tool returns -32062 whatever the caller's policy says. How aegis works gives the full order of checks.
Codes that carry data#
Three codes put structured detail in error.data.
-32060 carries tier (free, growth or enterprise), limit, used and period (the UTC month as YYYYMM), so a client can report which cap it hit.
-32061 carries a status of pending, denied or unavailable. A pending hold also carries the approval_id, which operators see as the id of the request in GET /admin/v1/approvals:
{
"jsonrpc": "2.0",
"error": {
"code": -32061,
"message": "Approval required for echo on mock-primary",
"data": { "approval_id": "9b1d3c2e-...", "status": "pending" }
},
"id": 3
}
An approval is tied to the caller, the server, the tool and a hash of the exact params. Sending the same call again while it is pending returns the same approval_id. Once approved, the next identical call from the same caller runs and uses up the approval. Both the approval and a denial apply only within APPROVAL_TTL_SECS (default 3600) of when the call was first held. See Hold, approve and stop tool calls.
-32064 tells the caller plainly that the outcome is unknown:
{
"jsonrpc": "2.0",
"error": {
"code": -32064,
"message": "Upstream error: upstream did not respond within 10s; the call may have executed",
"data": {
"executed": "unknown",
"safe_to_retry": false,
"guidance": "The upstream received this call and did not answer in time. It may have executed. Confirm the outcome before retrying; if the tool is idempotent, retry with the SAME idempotency key."
}
},
"id": 7
}
The deadline is UPSTREAM_TIMEOUT_SECS (default 10), and the audit log records the call as indeterminate, not error.
Upstream errors#
aegis returns -32000 with a message that starts Upstream error: when the upstream could not be reached, answered with a non-2xx HTTP status, or sent a reply aegis could not read. Before giving up, aegis retries connection failures, where the request never left, up to UPSTREAM_MAX_RETRIES times (default 2). It does not retry timeouts or HTTP errors, because the tool may have run. For the same reason, treat a -32000 on a non-idempotent tool the way you treat -32064. The detail is capped at 300 characters, and any brokered credential is removed from it.
Each -32000 or -32064 counts toward the circuit breaker for that tenant and server. After CIRCUIT_FAILURE_THRESHOLD consecutive failures (default 5), the circuit opens and calls fast-fail with -32063 for CIRCUIT_COOLDOWN_SECS (default 10). After that, one probe call goes through, and a success closes the circuit.
If the MCP server answers with a 2xx response that holds its own JSON-RPC error, aegis passes it through with the server's code, after removing any brokered credential and scanning it for PII and secrets like any other response. That reply does not count against the circuit breaker. A code not listed on this page came from the MCP server, not from aegis.
When a code is not returned#
Several checks can log a finding and let the call through instead of refusing it:
THREAT_MODE=monitor:-32050,-32051,-32053and-32052are logged, not returned.INPUT_VALIDATION=monitororoff: no-32602from schema validation.EGRESS_ACTION=monitororoff: no-32053.PII_ACTION=redact(the default) ormonitor: no-32052. Hits are masked or only logged.- Email addresses, phone numbers and dates of birth never cause
-32052or-32053. Only credentials, card numbers and SSNs block. - Billing not enabled, or
BILLING_ENFORCEunset or0: no-32060. With billing on and enforcement off, overages are recorded. If the usage meter is unreachable, or the tier store has never loaded, the call counts as under quota. RATE_LIMIT_PER_SESSION=0orRATE_LIMIT_PER_TOOL=0turns that limit off (defaults are 120 and 60 calls per minute). If Redis is unreachable, the limiter lets calls through.UPSTREAM_MAX_CONCURRENT=0turns off backpressure (default 64 in-flight calls per upstream), so-32063then comes only from an open circuit.
Each setting is described in Configuration, and the threat checks in Threat detection.
Handling errors in a client#
On HTTP 401 with a body of {"error":"unauthorized: ..."}, get a fresh token. If the message says your tenant is not served, the token is valid but this gateway instance does not serve that tenant. If the body is a -32603 session error, drop the X-Session-ID header. aegis mints a new session and returns its ID in the X-Session-ID response header. Log X-Correlation-ID with every error so an operator can find the call in the gateway's records. For the endpoints behind these codes, see the API reference.