Permalink to Errors and limitsErrors and limits

Every error on every route — /v1, /v1beta, and the unauthenticated endpoints — uses the same body:

json
{
  "error": {
    "message": "invalid api key",
    "type": "authentication_error",
    "code": "invalid_api_key",
    "param": null
  }
}
FieldUse it for
codebranch on this. Stable, machine-readable, specific
typethe OpenAI-compatible category, for clients that already switch on it
messageshow to a human. Redacted, but free-form and not a contract
paramalways 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

StatusWhen
400the request is malformed — a missing field, a bad enum, an unusable URL
401no credential, an unknown or disabled key, or a disabled account
402not enough credits
403an OAuth token lacks the scope for this route
404unknown route, unknown model, or a job that is not yours
409an idempotency conflict, or a /content link asked for before an asset exists
413the body or an uploaded file exceeds its cap
425an identical idempotent request is still being prepared — retry shortly
429a rate limit, a concurrency limit, or too many open jobs
499the caller went away before we finished
500our bug
502the upstream failed or returned nothing usable
503no provider is currently available, or our database is briefly unreadable
504the 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:

LimitDefaultOn refusal
requests per minute60429 rate_limited, Retry-After: 60
concurrent requests15429 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. Honour Retry-After when present.
  • 4xx other than 425 and 429 — correct the request before retrying. 425 admission_in_progress means the original submission is still being prepared; retry it with the same Idempotency-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 an error key, 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

codeStatusMeaning
missing_token401no credential was found
invalid_api_key401the key does not exist
key_disabled / key_expired401the key row is unusable
account_disabled401the owning account is suspended
insufficient_scope403an OAuth token is missing this modality's scope
insufficient_credits402the balance cannot cover the call
no_account401the credential resolves to nothing billable
precheck_failed503the credit ledger is briefly unreadable
rate_limited429requests per minute
too_many_concurrent_requests429concurrent requests
model_not_found404the model is not routable for this operation under your key
no_capable_provider404providers exist but none implements this capability
no_available_provider503every candidate is cooling down
request_timeout504
request_canceled499the client disconnected
not_found404no route matches the method and path
internal_error500our 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:

bash
curl https://hypit.ai/healthz
json
{
  "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.