Skip to main content

First-call failures

These are the envelopes a new key hits most often. None of them are transient except the sandbox daily cap. Do not retry the same request unchanged.

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:

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:

Error code reference

Product-specific setup codes

Account-creation limits

These bound how many new free accounts and anonymous sandbox keys one source network may create. They are not per-key rate limits and they never affect traffic from a key you already hold. Refusals are 429 with an actionable detail naming the limit, the window, and the way forward. The window is rolling, not a fixed daily reset: the cap frees up as your oldest account or key ages past 24 hours, so a shared egress recovers on its own. Do not hammer the endpoint waiting for it — retry after the window, or take one of the routes below. If you are behind a shared egress (an office, a VPN, a CI runner) and need more, email support@pyai.com to have the network raised. Teams on a company domain get a much larger allowance once the mailbox is proven — sign up with Continue with Google rather than email + password, so the domain is verified rather than merely claimed. Already have an account? Signing in is never capped. Only brand-new tenant creation is.

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. Call GET /v1/me to read the rps, burst, concurrency, and quota posture applied to the calling key.

Per-request input ceilings

Synthesis takes a bounded amount of text per request. POST /v1/audio/speech and the Clone synthesis routes accept up to 4,096 characters in input / text; longer bodies are refused with 400 input_too_long before any audio is rendered, and the message names both the limit and the length you sent. Split long copy into requests and concatenate the audio.

Idempotency

JSON audio_url requests to POST /v1/transcription/jobs accept 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.
Multipart audio uploads are not deduplicated. Do not assume an Idempotency-Key makes a retried binary upload safe.
Send a fresh idempotency key (e.g. a UUID) per logical operation, and reuse it when retrying that exact operation after a network blip.

Async transcription failures

Submission failures use the stable request codes above. After a job is accepted, a terminal processing failure is returned by GET /v1/transcription/jobs/{id} as:
The job’s error is a normalized human-readable message, not a stable machine-readable code. Branch on status == "failed" and decide whether to submit a new idempotent operation; do not parse the message. See Transcribe recordings with timestamps for job lifecycle, signed webhooks, input limits, and retention.

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.