Theme
Language

Errors and limits

GetVibe API error codes: 401 (key), 402 (balance), 403 (IP), 429 (rate limit). How to handle errors and retry with exponential backoff.

Errors produced by GetVibe on /v1/* have a JSON envelope with error.message, error.type and error.code. Do not rely on the message text or third-party service headers as a contract.

Error format

{
  "error": {
    "message": "insufficient balance",
    "type": "insufficient_quota",
    "code": 402,
    "request_id": "8d4b6b3a-..."
  }
}

Response codes

ParameterTypeDescription
401authentication_error / 401Missing or malformed key, or the key is disabled. Do not retry until the credentials are fixed.
402insufficient_quota / 402No available balance or limit — waiting does not fix it. A 402 never carries Retry-After and should not be retried: top up, or wait for the subscription window to reset.
403permission_error or policy type / 403IP, model or policy does not allow the request. Do not retry without changing the request or the settings.
429rate_limit_exceeded, inflight_client_cap or rate_limit_error / 429Your rate limit, your concurrency limit, a balance reservation briefly held by your own concurrent requests (cause reserved_in_flight), or a supplier limit. Retry only after Retry-After; a 429 never means your balance is exhausted.
410media_endpoint_retired / media_endpoint_retiredOnly /v1/images/edits, /v1/video/generations and /v1/videos (retired). Do not retry; switch to /api/media/generate. /v1/images/generations is served.
413invalid_request_error / 413Files API only: the file exceeds 50 MiB. Reduce the file, then create a new request.
503overloaded / 503Global edge overload. Retry after Retry-After: 2. Other 503s may not be retryable without changing the model or request.

Request errors (400, 413, 422): codes and fixes

Every request error carries a stable error.code from the table below, error.param with the field it concerns (null when the model did not name one), and a link to the matching row at the end of error.message — Codex and other CLIs show only the message, so the link travels there.

error.codeerror.paramWhat to do
unsupported_parameterfield nameThe selected model or channel does not accept this parameter. Remove it from the request (in Codex/IDEs, from the config) and retry.
invalid_parameter_valuefield nameThe parameter is present but its value is not allowed for this model: for example temperature other than 1 with reasoning on, max_tokens above the maximum, or effort "none" where reasoning cannot be turned off. Fix the value as the message says.
missing_parameterfield nameA required field is missing or empty. Add it.
invalid_tool_schematools[].function.*A tool definition was rejected: names may use Latin letters, digits, "_" and "-" only; parameters must be a JSON Schema whose root is type: "object". Or pick another variant of the model.
tool_choice_unsupportedtool_choiceThe model does not support a forced tool choice. Send tool_choice "auto" or "none".
invalid_tool_historymessages / inputThe history contains a tool call without a result. Add the result with the same id, or drop the call together with its result when trimming history.
invalid_image—The image could not be read. Send a complete PNG, JPEG, WebP or GIF file (BMP, TIFF, AVIF and SVG are not accepted).
unsupported_input_modality—The model does not accept this kind of attachment (PDF, audio, video, images). Remove it or pick a model whose catalog entry lists that input.
conversation_state_unreadable—The reasoning in the history was created by another channel. The request is valid; retry it, and if the error repeats, start a new conversation.
invalid_json—The request body is not JSON or arrived truncated. For very long conversations with images, start a new conversation or compact the history.
request_too_large—The request is too large. Reduce the attachments or the history.
request_rejected_by_channel—The model channel refused without naming a reason; the request itself is most likely valid. Retry it; if the refusal repeats, pick another variant of the model (the suffix after the colon) or the model without a suffix. If that does not help, contact support with the request_id.
model_not_available_on_route—The route is not set up for this model. Pick another model or variant.
context_length_exceededmessages / input / promptThe request does not fit the context window. Compact the history (in Codex: /compact) or pick a model with a larger window.
content_moderation—The request or the answer was blocked by the provider's content filter. Rephrase it or pick another model.
invalid_request—The cause was not recognised. Check the request against the API reference; if it is correct, contact support with the request_id from the response.

Response headers

ParameterTypeDescription
x-pv-cost-rubstringBest-effort ruble cost estimate. In streams, x-pv-cost-rub is available when the input estimate is known; pv_cost_rub in the final usage event requires x-pv-cost: 1. The usage history contains the actual debit.
x-pv-generation-idstringUsage-history record id (GET /v1/generations/{id}). Returned only for non-streaming chat/completions requests.
x-pv-dropped-paramsstringComma-separated request parameters that the selected route does not accept and that were therefore not sent to the model (for example temperature,top_p). Present only when something was actually withheld, on answers and on errors alike. We never drop a parameter silently: no header means everything you sent reached the model unchanged.
Retry-AfterintegerSeconds to wait. The edge sets it on 429 rate, 429 concurrency (5), 503 global overload (2) and some transient 402s. Not assumed for every 429/503.

Errors before and after the response starts

A slow stream (stream: true) gets 200 and : pv-keepalive comments after 5 seconds of silence so the connection does not look dead; a long non-stream request gets 200 and whitespace after its first token. From then on the status cannot change and an error arrives in the body. If your client falls back to another supplier on an HTTP error, send x-pv-honest-errors: 1 (or ask support to enable it for the account): there is then no keepalive and no early 200 before the first response byte, and every failure is an ordinary HTTP status with a JSON body. The cost is a silent socket until the first token; your header timeout must cover it.

An error after the response has started is one terminal event: for chat event: error with {"error":{message,type,code,status},"choices":[{"finish_reason":"error"}]} followed by data: [DONE]; for /v1/responses event: response.failed carrying the same error; for /v1/messages Anthropic's event: error. code is the machine cause and status is the HTTP status the error would have had before the response started.

For streaming requests (stream: true) x-pv-generation-id is not returned because billing finishes after the stream ends. Review the request history via GET /v1/generations.

Rate and concurrency limits

Both limits depend on the account's current balance and apply to all of its API keys together. The tier is recalculated automatically: top up and the limit rises; drop below a threshold and the tier goes down. There is no tokens-per-minute (TPM) limit.

Balance fromRequests per minute (RPM)Concurrent requests
0 $30010
1 $1 10050
2 $6 600300
4 $19 800900
12 $44 0002 000
ParameterTypeDescription
429 rate_limit_exceededRPMThe account sent as many requests in the last 60 seconds (a sliding window) as its tier allows. Retry-After is the number of seconds until a slot in the window frees up (at least 1); x-ratelimit-limit-requests is your current RPM. Body: error.cause = key_rate_limited, error.details = { enforced_by: "getvibe", limit, unit: "requests_per_minute", retry_after_seconds }.
429 inflight_client_capconcurrencyAs many requests are already running as the tier allows. A request holds its slot until it finishes; a stream holds it until its last event. Retry-After: 5. Body: error.cause = account_concurrency_limited, error.details = { enforced_by: "getvibe", limit, unit: "parallel_requests", retry_after_seconds: 5 }.
Personal limitRPMIf the account has a personal request limit, it can only lower the tier RPM, never raise it.
503 overloadedserviceNot your limit but service protection: Retry-After: 2 is the global cap across all accounts, Retry-After: 1 means one server is busy. Retry after the pause.
429 from a providerupstreamerror.cause = supplier_rate_limited means the model provider throttled the request and your own limit is not exhausted; channel_rate_limited is the shared cap of a provider channel (Retry-After: 10, nothing was sent).

Retrying requests

For a retryable response first use Retry-After, if present. Without the header, do not treat any 429 or 503 as automatically retryable: check the HTTP status and the public error.type, fix the non-retryable cause and use bounded backoff only for a transient error.

import time, requests

def call_with_retry(url, headers, payload, max_retries=5):
    for attempt in range(max_retries):
        r = requests.post(url, headers=headers, json=payload)
        if r.status_code == 429:
            # honor Retry-After, otherwise exponential backoff
            wait = int(r.headers.get("Retry-After", 2 ** attempt))
            time.sleep(wait)
            continue
        return r
    return r
GetVibe API errors and limits — 401, 402, 403, 429 | GetVibe API