Skip to content

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.

{
"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.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. |

Responses from rate-limited endpoints include:

X-RateLimit-Limit: 10
X-RateLimit-Remaining: 7
X-RateLimit-Reset: 1748212860

X-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.

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.

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.