Permalink to Errors and limitsErrors and limits
Every error on every route — /v1, /v1beta, and the unauthenticated endpoints — uses the same
body:
{
"error": {
"message": "invalid api key",
"type": "authentication_error",
"code": "invalid_api_key",
"param": null
}
}| Field | Use it for |
|---|---|
code | branch on this. Stable, machine-readable, specific |
type | the OpenAI-compatible category, for clients that already switch on it |
message | show to a human. Redacted, but free-form and not a contract |
param | always null today; present for OpenAI SDK compatibility |
type is one of invalid_request_error, authentication_error, permission_error,
not_found_error, rate_limit_error, insufficient_quota, upstream_error, timeout_error,
server_error.
The message is scrubbed before it leaves us: provider names, base URLs, upstream model names and
credentials never appear in it. An upstream's raw text is kept in our logs only, which is why quoting
X-Request-Id in a support ticket is worth more than pasting the message.
Permalink to status-codesStatus codes
| Status | When |
|---|---|
400 | the request is malformed — a missing field, a bad enum, an unusable URL |
401 | no credential, an unknown or disabled key, or a disabled account |
402 | not enough credits |
403 | an OAuth token lacks the scope for this route |
404 | unknown route, unknown model, or a job that is not yours |
409 | an idempotency conflict, or a /content link asked for before an asset exists |
413 | the body or an uploaded file exceeds its cap |
425 | an identical idempotent request is still being prepared — retry shortly |
429 | a rate limit, a concurrency limit, or too many open jobs |
499 | the caller went away before we finished |
500 | our bug |
502 | the upstream failed or returned nothing usable |
503 | no provider is currently available, or our database is briefly unreadable |
504 | the upstream did not answer in time |
Note 402 and 503: neither is retryable in the same shape. A 402 needs credits; a 503 from
no_available_provider or precheck_failed should be retried with backoff, because it means our
side is briefly unable to serve, not that your request is wrong.
Permalink to rate-limitsRate limits
Two separate limits, both per API key:
| Limit | Default | On refusal |
|---|---|---|
| requests per minute | 60 | 429 rate_limited, Retry-After: 60 |
| concurrent requests | 15 | 429 too_many_concurrent_requests, Retry-After: 1 |
These are the defaults; a deployment may raise them. They are token-bucket and semaphore respectively — requests are refused, never queued. An OAuth access token has no key row, so it is limited per account instead.
A third limit applies only to asynchronous media: at most 64 queued or running jobs per
account, counted across every key and session it owns. Exceeding it is 429
media_open_job_capacity with Retry-After: 30. See
Asynchronous jobs.
There are no X-RateLimit-* headers. Read Retry-After and back off.
Permalink to retrying-safelyRetrying safely
425,429,503,502,504— retry with exponential backoff and jitter. HonourRetry-Afterwhen present.4xxother than425and429— correct the request before retrying.425 admission_in_progressmeans the original submission is still being prepared; retry it with the sameIdempotency-Key.- Creating a job — send an
Idempotency-Key. A retry with the same key returns the original job instead of creating and charging a second one. - Chat streaming — a stream that fails after the first byte cannot be retried by us and stays at
HTTP
200. Check every SSE frame for anerrorkey, and re-issue the whole request yourself if you need to.
Permalink to codes-you-will-see-on-every-routeCodes you will see on every route
code | Status | Meaning |
|---|---|---|
missing_token | 401 | no credential was found |
invalid_api_key | 401 | the key does not exist |
key_disabled / key_expired | 401 | the key row is unusable |
account_disabled | 401 | the owning account is suspended |
insufficient_scope | 403 | an OAuth token is missing this modality's scope |
insufficient_credits | 402 | the balance cannot cover the call |
no_account | 401 | the credential resolves to nothing billable |
precheck_failed | 503 | the credit ledger is briefly unreadable |
rate_limited | 429 | requests per minute |
too_many_concurrent_requests | 429 | concurrent requests |
model_not_found | 404 | the model is not routable for this operation under your key |
no_capable_provider | 404 | providers exist but none implements this capability |
no_available_provider | 503 | every candidate is cooling down |
request_timeout | 504 | |
request_canceled | 499 | the client disconnected |
not_found | 404 | no route matches the method and path |
internal_error | 500 | our bug — quote the request id |
Route-specific codes are listed on each endpoint's page: images, video, audio, chat, Gemini, jobs.
Permalink to failover-and-what-it-means-for-youFailover, and what it means for you
A request that fails on one upstream is retried on another before you see an error, so a 502 means
every candidate failed, not just the first. Two rules bound it:
- a streamed response never fails over once the first byte is on the wire;
- a client error is never retried, because retrying a malformed request only multiplies load.
This is invisible in the response except for the latency it can add. Set generous client timeouts on
/v1/chat/completions — the default 10 seconds in some HTTP clients is not enough for a long
completion, let alone a failover.
Permalink to healthHealth
One unauthenticated endpoint reports whether the gateway can serve at all:
curl https://hypit.ai/healthz{
"status": "ok",
"version": "v0.4.2",
"db": "ok",
"outbox_pending": 0,
"cleanup_running": true,
"cleanup_pending": 12,
"cleanup_oldest_due_age_s": 0
}status and db are the two fields worth reading — a degraded gateway answers 503. The
outbox_* and cleanup_* counters are operational and may change without notice. Use this for a
load-balancer probe or a status page, not as a per-request preflight.