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.