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 returns429 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
JSONaudio_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 body →
409 idempotency_conflict.
Idempotency-Key makes a retried binary upload safe.
Async transcription failures
Submission failures use the stable request codes above. After a job is accepted, a terminal processing failure is returned byGET /v1/transcription/jobs/{id} as:
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. Passlimit (1-100, default
20) and the previous page’s next_cursor as cursor. next_cursor is null
on the last page.