Developer API

Reliability, errors, and rate limits

Build retry-safe requests with idempotency, stable error codes, response headers, and cost safeguards.

Updated 30 Sept 2026

Make every POST idempotent

Send an Idempotency-Key containing 8 to 255 letters, numbers, underscores, or hyphens. The key is scoped per API key and retained for 24 hours; retry the same logical operation with the same value.

The retry replays the stored response, marked Idempotent-Replay: true; nothing is generated or charged twice.
Try this
✦POST /api/v1/images Idempotency-Key: order-58213-hero-image {"prompt": "product photo of a walnut desk lamp on white", "n": 1} Then send the identical request again after a network timeout.
What you getThe second call returns the original response instead of generating and charging for a second image; a key shorter than 8 characters is rejected with OPMD_IDEMP_001.

Handle errors by meaning

400 means the request is invalid or asks for something the current model version cannot do (OPMD_CAPABILITY_001), 401 the key is missing or invalid, 402 credits are insufficient, 403 permission, IP or usage policy blocked the call, 404 the file or endpoint does not exist, 409 the same idempotent operation is still running, 413 the body is over 31 MB, 422 a document could not be read, and 429 the rate limit was exceeded. A 502 with OPMD_EMPTY_001 means the model returned no answer, for example it ran out of output tokens, and nothing was charged. Other 5xx responses indicate a gateway or server problem.

402 is stop-and-alert, never a retry; only a 5xx api_error is retried, with jitter.
Try this
✦Map status codes to actions in your client: 400 fix the request, 401 check the key, 402 top up credits, 403 check permission or IP allowlist, 409 wait and re-read with the same Idempotency-Key, 413 shrink the request or upload the file first, 429 back off, 502 OPMD_EMPTY_001 raise max_tokens, other 5xx retry with jitter.
What you getA run with an exhausted balance stops at 402 with error.code OPMD_BILLING_001 and alerts finance instead of retrying, while a 5xx retries a bounded number of times.

Use stable machine codes

Branch on error.code rather than parsing prose. Safe messages can be surfaced to users, and X-Request-ID belongs in logs and support reports. When the model rejects a request, for example an unreadable image, the code is OPMD_UPSTREAM_400, error.message gives the reason and nothing is charged. /chat/completions, /responses and /files errors also carry param, the field that caused the error.

Branch on error.code, show error.message to users as written, keep X-Request-ID for support.
Try this
✦switch (body.error.code) { case 'OPMD_RATE_001': backOff(); break; case 'OPMD_BILLING_001': notifyBilling(); break; default: log(res.headers['x-request-id'], body.error.code); }
What you getEach failure branches on the stable code, error.message can be shown to users as written, and the X-Request-ID sits in your logs for any support report.

Respect RateLimit headers

Every response carries RateLimit information. Slow down before exhausting the budget, add jitter to retryable backoff, and do not retry validation, authentication, permission, or billing failures unchanged.

Wait RateLimit-Reset seconds plus jitter before retrying; the default budget is 120 requests per minute per key.
Try this
✦After each response read the RateLimit-Remaining header; when it falls below 10 percent of RateLimit-Limit slow your queue, and on a 429 wait for RateLimit-Reset plus random jitter before retrying.
What you getBursts flatten before the limit is hit; a 429 with OPMD_RATE_001 waits out the window, while 400, 401, 402 and 403 responses are never retried unchanged.

Protect spend

Bound max_tokens (default 16384, up to 128000, with reasoning tokens counted as output), image count, duration, and concurrency in your own application. Read the current AUD rates from GET /api/v1/pricing, log billing.charged_aud from every text response, monitor usage and credits, set billing alerts, and place expensive generation behind user authorization.

max_tokens bounds the worst case, and billing.charged_aud reports what this request cost.
Try this
✦In your application, cap max_tokens at 1500, image n at 2 and video duration_seconds at 15, run at most 3 generations at once, and require a signed-in user before any /videos call.
What you getSpend stays inside the bounds you set no matter what a user types, and the Developer console's usage view and your billing alerts confirm the pattern rather than surprise you.
GUIDED LEARNING

Practise this in Oppermind Academy

Follow the related tutorial or course and apply the concept to a real task.

Open learning path