Errors
Every non-2xx response from the Protoface API is a JSON body with a single
error object. The HTTP status is meaningful, and error.type mirrors it.
The error envelope
Section titled “The error envelope”{ "error": { "type": "invalid_request", "code": "transport.type.missing", "message": "Field `transport.type` is required.", "param": "transport.type", "request_id": "req_01HYZ..." }}| Field | Type | Notes |
| ------------ | -------------- | -------------------------------------------------------------------- |
| type | string | Broad category, mirrors the HTTP status. Stable. See the table below. |
| code | string | Stable, machine-readable subcode in lower_snake_case / dotted form. |
| message | string | Human-readable. Not stable — never branch on it. |
| param | string | null | The offending request field (dot-path), when applicable. |
| request_id | string | req_... — quote this to support. Also echoed in X-Request-Id. |
Error types
Section titled “Error types”error.type is one of a fixed set, each mapped to an HTTP status:
| HTTP | error.type | When it happens |
| ---- | --------------------- | --------------------------------------------------- |
| 400 | invalid_request | Malformed JSON, missing or invalid field. |
| 401 | authentication | Missing, bad, or revoked API key. |
| 403 | permission | Authenticated, but the key can’t access this resource. |
| 404 | not_found | Resource doesn’t exist (or isn’t in your scope). |
| 409 | conflict | Idempotency conflict or a state-machine clash. |
| 422 | unprocessable | Request shape is fine but the semantics are invalid. |
| 429 | rate_limit | Per-key rate limit exceeded. |
| 429 | quota_exceeded | Per-org quota exhausted (a subtype of 429). |
| 500 | internal | Unexpected server error. |
| 503 | service_unavailable | No capacity, or scheduled maintenance. |
Rate limits and retries
Section titled “Rate limits and retries”Responses from rate-limited endpoints include:
X-RateLimit-Limit: 10X-RateLimit-Remaining: 7X-RateLimit-Reset: 1748212860X-RateLimit-Reset is a Unix timestamp (seconds). A 429 additionally carries
Retry-After (integer seconds) and you must respect it. A 503 service_unavailable also carries Retry-After when capacity is the cause.
Quota-exhaustion responses
Section titled “Quota-exhaustion responses”A quota_exceeded error names the limit that was hit in error.code:
{ "error": { "type": "quota_exceeded", "code": "concurrent_sessions", "message": "Org org_... already has 5 concurrent sessions (max 5).", "param": null, "request_id": "req_..." }}See Limits & quotas for the caps that produce these.
Handling errors in the SDK
Section titled “Handling errors in the SDK”Every non-2xx response throws a typed ProtofaceError subclass. Switch on the
stable error.code (or instanceof), never the message:
import { RateLimitError, QuotaExceededError, ProtofaceError,} from "@protoface/sdk";
try { await client.sessions.create(req);} catch (err) { if (err instanceof RateLimitError) { await sleep((err.retryAfterSeconds ?? 1) * 1000); } else if (err instanceof QuotaExceededError) { // e.g. err.code === "concurrent_sessions" console.error("quota:", err.code); } else if (err instanceof ProtofaceError) { console.error(err.code, err.requestId); }}The subclasses map to the types above: InvalidRequestError (400),
AuthenticationError (401), PermissionError (403), NotFoundError (404),
ConflictError (409), UnprocessableError (422), RateLimitError (429),
QuotaExceededError (429 subtype), InternalError (5xx), and
ServiceUnavailableError (503). A network/timeout failure that never reached the
API throws ProtofaceConnectionError.