Errors & retries

Errors are returned in the standard OpenAI shape, so existing error-handling code works unchanged:

{
  "error": {
    "message": "You have insufficient credits. Add credits to your account to continue.",
    "type": "insufficient_quota",
    "code": "insufficient_quota"
  }
}

Every response carries a req_pgs_... request id — in the x-request-id header on every response (successes and streams too, not just errors) and in the body of error responses. Include it if you contact support.


Status codes

Status Meaning What to do
400 Malformed request, or an unsupported content part (e.g. an image sent to a text-only model). Fix the request; do not retry unchanged.
401 Missing or invalid API key. Check the Authorization: Bearer pgsk_... header.
402 Insufficient credit: your prepaid balance is depleted. Top up on the Billing page, then retry.
404 Unknown model id. Check the id against the Models page.
429 Rate limited: too many requests, or a concurrency/token cap hit. Back off and retry after Retry-After.
451 The API isn't available in your region. Not retryable; nothing to change on your end.
500 Unexpected error on our side. Retry with backoff; if it persists, contact support with the request id.
502 An upstream model returned an error. Retry with exponential backoff.
503 A model is temporarily unavailable or overloaded (this survived our internal retry ladder). Honor Retry-After, then retry with backoff.

Handling 402 (out of credit)

A 402 means your balance reached zero. Requests will keep returning 402 until you add credit. Watch your Usage and Billing pages, or enable a low-balance email, so you're topped up before you run dry.

from openai import OpenAI, APIStatusError

try:
    resp = client.chat.completions.create(model="glm-5.2", messages=[...])
except APIStatusError as e:
    if e.status_code == 402:
        # Out of credit: surface to the user / page an operator, then top up.
        ...

Handling 429 (rate limits)

The limits themselves (requests per minute, concurrency, output cap) are documented on the Rate limits page.

We retry busy models for you first. When a model is momentarily busy, the Grove rides it out with an internal retry ladder (up to roughly 20 seconds) before giving up; most transient congestion never reaches your code. Two practical consequences: keep your client request timeout at 30 seconds or more, and treat a 429 or 503 that does come back as a genuinely busy model, not a blip: honor the Retry-After before trying again. Rate-limit 429s from your own account (requests per minute or concurrency) are returned immediately, without the wait.

On 429 we return a Retry-After header (seconds). Respect it, then retry with exponential backoff and jitter. The OpenAI SDKs already retry 429/5xx for you; you can tune this with max_retries:

client = OpenAI(
    api_key="pgsk_your_key_here",
    base_url="https://api.pgsgrove.com/v1",
    max_retries=4,   # exponential backoff, honors Retry-After
)

A simple backoff loop (curl / shell logic)

attempt = 0
while attempt < 5:
    send request
    if status == 429 or status >= 500:
        wait = Retry-After header, else min(2 ** attempt, 30) seconds
        sleep(wait + random jitter)
        attempt += 1
        continue
    break

What isn't billed

Failed requests (4xx, 5xx) are not billed: they cost you nothing beyond the round trip. If a stream is cut off partway, you're billed only for the tokens actually produced before the cut.