API · Conventions
Errors
RFC 7807 problem+json with Matter extensions. Status codes, the full error-code catalog, and recovery patterns — all on one page.
Last updated
TL;DR. Every non-2xx response is application/problem+json (RFC 7807). Matter
extends the base envelope with code, param, doc_url, request_id, retry_after,
and errors[]. Retryable errors include retry_after in seconds — honor it.
Every non-2xx response uses Content-Type: application/problem+json per RFC 7807.
Matter adds extensions (code, param, doc_url, request_id, retry_after, errors[],
authorized_by) on top of the base {type, title, status, detail, instance}. The
code field is the stable handle — match on it, not on title or detail, both of
which are localized prose subject to change.
Example error response
{
"type": "https://mattermode.com/docs/errors/invalid_state_transition",
"title": "Invalid state transition",
"status": 409,
"detail": "Entity ent_Nq3KcAbc is in state `dissolved` and cannot be dissolved again.",
"instance": "/requests/req_Qw9xYz8A",
"code": "invalid_state_transition",
"doc_url": "https://mattermode.com/docs/api/conventions/errors#invalid-state-transition",
"request_id": "req_Qw9xYz8A",
"authorized_by": {
"human_principal_id": "usr_4Kj2m8pQ",
"agent_id": "agt_Nq3KcAbc"
}
}On validation failures (422), an errors[] array carries field-level detail:
{
"status": 422,
"code": "validation_failed",
"detail": "One or more fields are invalid.",
"errors": [
{"field": "founders[0].equity_percent", "code": "out_of_range", "message": "Must be between 0 and 100."},
{"field": "founders", "code": "sum_mismatch", "message": "Founder equity must sum to 100%."}
]
}HTTP status code reference
| Status | When | Retry? |
|---|---|---|
200 OK | Synchronous success with body. | — |
201 Created | Resource created synchronously. | — |
202 Accepted | Mutation accepted; async work in progress. Watch webhooks for terminal state. | — |
204 No Content | Success with no body. | — |
400 Bad Request | Malformed request or unsupported version. | Fix and retry. |
401 Unauthorized | Missing or invalid key. | No. |
402 Payment Required | Action requires a human-signed Authorization. | Surface the authorization, then retry. |
403 Forbidden | Credential lacks scope, tier, or permission. See code. | No, or request higher-tier token. |
404 Not Found | Resource doesn't exist or is invisible to this key. | No. |
409 Conflict | State conflict (see error-code catalog). | Depends on code. |
422 Unprocessable Entity | Validation failed. errors[] has detail. | Fix and retry. |
429 Too Many Requests | Rate-limited. Honor retry_after. | Yes, after interval. |
500 / 502 / 503 / 504 | Server error. | Yes, with exponential backoff. |
Always log the request_id from any non-2xx response. The Matter support team
can resolve a request to its full trace when you include it. The same value is
returned in the Matter-Request-Id response header on every request, success
or failure.
Error catalog
Every entry below maps a code to its HTTP status, when it fires, an example
payload, and the recommended recovery. Match on code — it's the stable handle.
4xx — client errors
5xx — server errors
Recovery patterns
Match on code to dispatch error handling — title and detail are localized prose
and may change. The SDKs surface a typed MatterError with code for both branches.
import { MatterClient, MatterError } from "@mattermode/node";
try {
return await matter.entities.create(payload, { idempotencyKey: key });
} catch (err) {
if (!(err instanceof MatterError)) throw err;
switch (err.code) {
case "rate_limit_exceeded":
// Honor server-supplied retry_after.
await sleep(err.retryAfter * 1000);
return retry();
case "idempotency_key_reused_with_different_body":
// The body diverged from a prior attempt. Mint a NEW key.
return matter.entities.create(payload, { idempotencyKey: randomUUID() });
case "validation_failed":
// Show err.errors[] (field, code, message) to the user.
throw new UserVisibleError(err.errors);
case "human_signature_required":
case "authorization_required":
// Surface the authorization/document and pause.
return await pauseForHuman(err);
case "server_error":
case "upstream_unavailable":
// Exponential backoff. Preserve the original idempotency key.
return retryWithBackoff(() => matter.entities.create(payload, { idempotencyKey: key }));
default:
throw err;
}
}from matter import MatterClient, MatterError
try:
return matter.entities.create(**payload, idempotency_key=key)
except MatterError as err:
if err.code == "rate_limit_exceeded":
time.sleep(err.retry_after)
return retry()
if err.code == "idempotency_key_reused_with_different_body":
return matter.entities.create(**payload, idempotency_key=str(uuid.uuid4()))
if err.code == "validation_failed":
raise UserVisibleError(err.errors)
if err.code in ("human_signature_required", "authorization_required"):
return pause_for_human(err)
if err.code in ("server_error", "upstream_unavailable"):
return retry_with_backoff(
lambda: matter.entities.create(**payload, idempotency_key=key)
)
raiseRetry guidance
- Transient
5xx: retry with exponential backoff (1s → 4s → 16s → 64s with jitter, max ~85s total). 429 rate_limit_exceeded: sleepretry_afterseconds. Budget againstX-RateLimit-*.409 idempotency_key_reused_with_different_body: pick a recovery path — never retry blindly.409 invalid_state_transition: re-read the state machine before retrying.422: readerrors[], fix the inputs, and retry. Idempotency keys can be reused with the corrected body.403 human_signature_required/402 authorization_required: do not retry; surface the human-action surface.