Errors
TL;DR — Every error AuthPlane can return, in one place. OAuth errors follow RFC 6749 §5.2 with an RFC 9457 problem+json envelope on top. Auth challenges use RFC 6750 (
Bearer) or RFC 9449 (DPoP)WWW-Authenticateheaders. Consent errors from the SDKs surface as MCP JSON-RPC-32042UrlElicitationRequiredError. Symptom→fix table at the bottom for the ones you’ll actually hit.
Error envelope
Every error response includes both OAuth error fields and RFC 9457 Problem Details fields, served with Content-Type: application/problem+json:
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
{
"error": "invalid_grant",
"error_description": "authorization code has already been used",
"type": "https://docs.authplane.ai/errors/invalid_grant",
"title": "Bad Request",
"status": 400,
"detail": "authorization code has already been used"
}
error+error_description— OAuth 2.1 wire format (RFC 6749 §5.2). Every OAuth client library reads these.type+title+status+detail— RFC 9457 Problem Details. Standard HTTP APIs read these.
Both are always present. Pick whichever your stack understands.
OAuth error codes
Domain errors are sentinels in internal/domain/errors.go. Each carries an OAuth code + a default HTTP status.
OAuth error codes (mixed sources)
The codes below all use the RFC 6749 §5.2 envelope shape but come from different specs. Sourcing matters if you’re building on top of these: §5.2 defines only the six token-endpoint codes; the rest are borrowed from the authorization endpoint, other RFCs, or OIDC.
DPoP-specific codes (RFC 9449)
RFC 9449 defines two different delivery mechanisms depending on which server rejects the proof:
- Authorization server (
/oauth/token, §8) — HTTP 400 with the RFC 6749 §5.2 JSON envelope{"error":"…"}. NoWWW-Authenticateheader. If the AS needs a nonce it also setsDPoP-Nonce: <value>. - Resource server (§9) — HTTP 401 with
WWW-Authenticate: DPoP error="…". This is the challenge form clients see ontools/*calls.
Fix for either: regenerate the proof against the exact request URL + method, and (if the server sent DPoP-Nonce) include that value as the nonce claim.
AuthPlane-specific codes
WWW-Authenticate challenge patterns
The WWW-Authenticate response header tells clients how to retry.
Bearer (RFC 6750 §3)
WWW-Authenticate: Bearer realm="https://mcp.example.com/mcp",
error="invalid_token",
error_description="The access token expired",
resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"
The resource_metadata field (RFC 9728 §5.1) tells the client where to fetch the PRM document — which in turn tells it where to fetch a new token. All AuthPlane SDKs emit this automatically on 401 responses.
DPoP (RFC 9449 §7.1)
WWW-Authenticate: DPoP realm="https://mcp.example.com/mcp",
error="invalid_dpop_proof",
error_description="DPoP proof jti has already been used",
algs="ES256 RS256 PS256",
resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"
For use_dpop_nonce the response ALSO includes:
DPoP-Nonce: <server-generated-nonce-value>
The client must include this nonce in the next DPoP proof’s nonce claim.
Insufficient scope
Whether Bearer or DPoP, scope failures return error="insufficient_scope" with a scope= field listing what’s required:
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope",
error_description="token missing required scope",
scope="tools/write"
MCP JSON-RPC errors
Two MCP-specific errors that the SDKs surface on top of the OAuth layer:
-32042 — URL elicitation required
Emitted when a tool handler triggers a client.exchange() call that hits consent_required on a Broker resource. The SDK auto-translates the OAuth error into an MCP JSON-RPC error -32042 with a consentUrl in the data payload:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32042,
"message": "URL elicitation required",
"data": {
"elicitation_id": "elic_...",
"url": "https://auth.example.com/connect/github?return_to=..."
}
}
}
The MCP client shows the user the URL, they complete the Connect flow, then the client retries the original tools/call.
The SDKs handle this automatically — you don’t try/except for it in tool code. See Concepts: Token Vault.
Complete domain error catalog
Every error sentinel in internal/domain/errors.go. Grouped by concern.
OAuth core
CIMD
OIDC federation
Encryption + keys
Broker / Vault
DPoP
Symptom → cause → fix (top 20)
The ones you’ll actually hit while wiring things up.
Reading structured logs
Every error path logs a structured slog event at WARN or ERROR with:
error— the sentinel namecode— the OAuth error codeclient_id,resource,grant_type— request context when availabletrace_id,span_id,request_id— for correlation
Example (auth code replay):
2026-07-01T00:14:20Z ERROR msg="token exchange failed"
error=ErrCodeConsumed code=invalid_grant
client_id=my-client resource=https://mcp.example.com/mcp
request_id=r_abc123 trace_id=t_def456
Grep or ship to a log aggregator; every error field is queryable.
Related
- Reference: RFC compliance — which RFC defines each error code
- Reference: Public API — endpoint-level error responses in the OpenAPI spec
- Guides: Enable DPoP end-to-end — the
invalid_dpop_proofscenarios in depth - Troubleshooting: Debugging — end-to-end debug checklist
- Troubleshooting: Common errors — richer walkthroughs for the top-20 above