Skip to main content

Error shapes

PyAI returns two error shapes depending on which layer produced the error. Branch on the stable code, never the human message.

Data plane (gateway) — OpenAI-compatible

Auth, scope, rate-limit, and billing errors use the OpenAI envelope:
{
  "error": {
    "message": "Rate limit exceeded.",
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "param": null
  }
}

Control plane — RFC 7807 problem+json

Request-validation and resource errors (e.g. 400, 404, 409) use application/problem+json. The stable code is the last path segment of type:
{
  "type": "https://api.pyai.com/problems/idempotency_conflict",
  "title": "Conflict",
  "status": 409,
  "detail": "Idempotency-Key was reused with a different request body.",
  "request_id": "req_..."
}

Error code reference

HTTPCodeMeaningRetry?
401unauthorizedMissing/invalid/revoked keyNo
403forbiddenKey lacks the required scopeNo
403origin_not_allowedPublishable-token origin not allow-listedNo
400invalid_agent_idagent_id on a realtime/Omni URL is malformed (control chars / too long)No — fix the id
402credit_exhaustedOrg out of prepaid creditNo — add credit
402key_budget_exceededPer-key monthly cap reachedNo — raise budget
402insufficient_quotaPlan quota exhaustedNo — upgrade
409idempotency_conflictIdempotency-Key reused with a different bodyNo — use a new key
429rate_limit_exceededPer-key/IP rate limitYes — honor Retry-After
429concurrency_limit_exceededToo many concurrent realtime sessionsYes — wait + retry
429daily_cap_exceededSandbox/publishable daily capYes — after reset

Rate limits

Every key has a per-second rate limit (with burst) and a cap on concurrent realtime sessions, set by your plan. Exceeding either returns 429 with a Retry-After header (seconds to wait). Back off and retry; the official SDKs do this automatically.

Idempotency

POST /v1/transcription/jobs accepts an Idempotency-Key header so a retried request can’t create a duplicate job:
  • Same key + same body → the original response is replayed (no new job).
  • Same key + different body409 idempotency_conflict.
Send a fresh idempotency key (e.g. a UUID) per logical operation, and reuse it when retrying that exact operation after a network blip.

Pagination

List endpoints are cursor-paginated, newest first. Pass limit (1–100, default 20) and the previous page’s next_cursor as cursor. next_cursor is null on the last page.