SDKs
Error handling
Typed error hierarchies in each SDK, mapped to RFC 7807 problem objects. Retry policy by class. Side-by-side TypeScript, Python, and Go.
Last updated
TL;DR. Every Matter SDK ships a typed error hierarchy. The class corresponds to the
RFC 7807 type URI on the response. Catch the specific class for branching, the base
MatterError for last-resort logging. The SDK already retries 429 and 5xx — your
catch block is for the cases retry can't fix.
The hierarchy
All three SDKs share the same shape. Concrete classes extend a base; the base carries
the RFC 7807 envelope (type, title, status, detail, code, request_id).
MatterError base — every other class extends this
├── MatterValidationError 400 invalid_request
├── MatterAuthenticationError 401 authentication_required
├── MatterAuthorizationError 403 authorization_required
│ └── MatterAuthorizationPendingError tier-3 awaiting human signature
├── MatterNotFoundError 404 resource_not_found
├── MatterStateConflictError 409 invalid_state_transition
├── MatterIdempotencyReplayError 409 idempotency_key_replay
├── MatterRateLimitError 429 rate_limit_exceeded
├── MatterUpstreamUnavailableError 503 upstream_unavailable
└── MatterSignatureError (webhook receiver only)RFC 7807 code | HTTP | Retry safe? |
|---|---|---|
invalid_request | 400 | No — fix the request |
authentication_required | 401 | No — rotate the key |
authorization_required | 403 | No — check scopes / tier |
resource_not_found | 404 | No — verify the ID |
invalid_state_transition | 409 | No — read the resource state |
idempotency_key_replay | 409 | No — call already executed |
rate_limit_exceeded | 429 | Yes — honor retry_after |
upstream_unavailable | 503 | Yes — exponential backoff |
The SDK already retries the two retry-safe rows up to maxRetries. Your catch handles
the rows that can't be retried, plus the post-retry surface of the safe rows.
Side-by-side
The same shape, the same five branches, in each SDK.
import {
MatterError,
MatterValidationError,
MatterAuthorizationError,
MatterIdempotencyReplayError,
MatterRateLimitError,
MatterUpstreamUnavailableError,
} from "@mattermode/node";
try {
await matter.entities.create({ jurisdiction: "US-DE" });
} catch (err) {
if (err instanceof MatterValidationError) {
// 400 — surface field-level errors to the caller
return { ok: false, fields: err.errors };
}
if (err instanceof MatterAuthorizationError) {
// 403 — token tier or scope is wrong
log.warn("scope insufficient", { code: err.code });
throw err;
}
if (err instanceof MatterIdempotencyReplayError) {
// 409 — same key with a different body. Bug in the caller.
log.error("idempotency replay", { key: err.idempotencyKey });
throw err;
}
if (err instanceof MatterRateLimitError) {
// 429 — SDK already retried; we've exhausted maxRetries
log.warn("rate limited", { retryAfter: err.retryAfter });
throw err;
}
if (err instanceof MatterUpstreamUnavailableError) {
// 503 — Secretary of State portal down. Already retried.
log.error("upstream down", { upstream: err.upstream });
throw err;
}
if (err instanceof MatterError) {
log.error("matter error", { type: err.type, requestId: err.requestId });
throw err;
}
throw err; // network, parse, or unknown
}from matter.errors import (
MatterError,
MatterValidationError,
MatterAuthorizationError,
MatterIdempotencyReplayError,
MatterRateLimitError,
MatterUpstreamUnavailableError,
)
try:
matter.entities.create(jurisdiction="US-DE")
except MatterValidationError as err:
# 400 — surface field-level errors to the caller
return {"ok": False, "fields": err.errors}
except MatterAuthorizationError as err:
# 403 — token tier or scope is wrong
log.warning("scope insufficient", extra={"code": err.code})
raise
except MatterIdempotencyReplayError as err:
# 409 — same key with a different body. Bug in the caller.
log.error("idempotency replay", extra={"key": err.idempotency_key})
raise
except MatterRateLimitError as err:
# 429 — SDK already retried; we've exhausted max_retries
log.warning("rate limited", extra={"retry_after": err.retry_after})
raise
except MatterUpstreamUnavailableError as err:
# 503 — Secretary of State portal down. Already retried.
log.error("upstream down", extra={"upstream": err.upstream})
raise
except MatterError as err:
log.error("matter error", extra={"type": err.type, "request_id": err.request_id})
raiseimport (
"errors"
"github.com/matterhq/matter-go"
)
_, err := client.Entities.Create(ctx, &matter.EntityCreateParams{Jurisdiction: "US-DE"})
if err != nil {
var verr *matter.ValidationError
var aerr *matter.AuthorizationError
var ierr *matter.IdempotencyReplayError
var rerr *matter.RateLimitError
var uerr *matter.UpstreamUnavailableError
var merr *matter.Error
switch {
case errors.As(err, &verr):
// 400 — surface field-level errors to the caller
return Result{Ok: false, Fields: verr.Errors}, nil
case errors.As(err, &aerr):
log.Warn("scope insufficient", "code", aerr.Code)
return Result{}, err
case errors.As(err, &ierr):
log.Error("idempotency replay", "key", ierr.IdempotencyKey)
return Result{}, err
case errors.As(err, &rerr):
log.Warn("rate limited", "retry_after", rerr.RetryAfter)
return Result{}, err
case errors.As(err, &uerr):
log.Error("upstream down", "upstream", uerr.Upstream)
return Result{}, err
case errors.As(err, &merr):
log.Error("matter error", "type", merr.Type, "request_id", merr.RequestID)
return Result{}, err
default:
return Result{}, err // network, ctx cancellation, etc.
}
}Field-level validation errors
MatterValidationError carries a errors array — one entry per field that failed
validation. Each entry has path, code, and message. The shape mirrors the OpenAPI
problem extension validation_errors.
catch (err) {
if (err instanceof MatterValidationError) {
for (const e of err.errors) {
console.log(`${e.path}: ${e.code} — ${e.message}`);
}
}
}except MatterValidationError as err:
for e in err.errors:
print(f"{e.path}: {e.code} — {e.message}")var verr *matter.ValidationError
if errors.As(err, &verr) {
for _, e := range verr.Errors {
fmt.Printf("%s: %s — %s\n", e.Path, e.Code, e.Message)
}
}Retry policy by class
| Class | SDK retries | You should |
|---|---|---|
MatterValidationError | No | Fix the request |
MatterAuthenticationError | No | Rotate or reissue the credential |
MatterAuthorizationError | No | Widen scopes or escalate the token tier |
MatterAuthorizationPendingError | No | Surface auth ID; resume on authorization.approved |
MatterNotFoundError | No | Verify the ID, check livemode |
MatterStateConflictError | No | Read the resource, branch on status |
MatterIdempotencyReplayError | No | Bug — same key with different body |
MatterRateLimitError | Yes (4×) | Surface to caller after retries |
MatterUpstreamUnavailableError | Yes (4×) | Surface to caller after retries |
The SDK's retry schedule is exponential with jitter — 1s → 4s → 16s → 64s — and honors
the retry_after field on 429 responses. Override with maxRetries: 0 to disable.
Always log request_id
Every error carries request_id (req_…) which is the same value as the X-Request-Id
response header. Include it in every log line so support can trace from your error to
the server-side request.
[error] matter:invalid_request request_id=req_2c1a4d9f8b path=/v1/entities