Errors, Rate Limits & Quotas
Every API has failure modes. This page covers how the AIOZ AI API tells you something went wrong, which errors are worth retrying and which aren't, and the rate limits and quotas you'll bump into at scale.
The error response shape
When a request fails, the API returns a non-2xx HTTP status code and a JSON body of the shape:
{
"status": "fail",
"message": "human-readable error description"
}The status field is "fail" (or "error" for some server-side issues), and the message field is intended to be safe to surface to your own users or to log. The HTTP status code carries the category of failure; the message carries the specifics.
The SDKs translate every failed response into a single typed error per language, carrying the HTTP status code, the API status, and the API message. See the SDKs page for try/except examples in each language.
HTTP status code reference
The codes you'll commonly see, with their general meaning across endpoints. Endpoint-specific failures use the same codes; the message field tells you what specifically went wrong.
| Status | Category | When you'll see it |
|---|---|---|
200 OK | Success | The request succeeded; the response body's data field has your payload. |
400 Bad Request | Validation / malformed | The request body or query parameters are malformed or missing required fields. Check message for which field; fix the request before retrying. |
401 Unauthorized | Authentication | Your x-api-key header is missing, malformed, or no longer valid (e.g. the key was revoked). |
403 Forbidden | Authorization | The authenticated identity doesn't have permission to perform the action. Rare today since keys inherit their owner's permissions. |
404 Not Found | Missing resource | The resource referenced by the URL (a task_id, a model_id, etc.) doesn't exist or isn't accessible to this key. |
409 Conflict | State conflict | The request can't be applied because of the resource's current state. For example, trying to rename a key to a name that's already in use. |
429 Too Many Requests | Rate limit | You've exceeded a rate limit. Wait before retrying. See Rate limits below. |
500 Internal Server Error | Server-side | Something went wrong on our side. The request itself was well-formed; retrying after a short delay will often succeed. |
502 Bad Gateway / 503 Service Unavailable / 504 Gateway Timeout | Transient infrastructure | A node, the gateway, or an upstream dependency is briefly unavailable. Retry with backoff. |
Rate limits
The API applies rate limits per API key to protect the platform from accidental overload and to keep latency predictable across all callers. When you exceed a limit, requests are rejected with a 429 Too Many Requests until the limit window resets.
Specific limit values are not yet finalized for v1: the numbers below are provisional defaults and may change before general availability. The response headers on each request (see below) are the authoritative source for your current limit status; always prefer them over any value documented here.
Provisional limits (subject to change before GA):
- General endpoints: 600 requests per minute per API key.
- Task creation (
tasks.create): 60 requests per minute per API key. This limit is tighter than the general one because each task consumes node capacity.
Response headers. Rate-limited responses, and successful responses on rate-limited endpoints, include the following headers so you can track your status without guessing:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | The maximum number of requests allowed in the current window. |
X-RateLimit-Remaining | The number of requests left in the current window. |
X-RateLimit-Reset | When the current window resets, so you know when X-RateLimit-Remaining refills. |
Retry-After | On a 429, the number of seconds to wait before retrying. |
Design your client to handle 429 gracefully: back off, retry after a delay (use Retry-After when present), and don't burst all your requests at once. The retry guidance below applies regardless of the specific limit values.
Quotas
In addition to per-window rate limits, your account has a balance-based quota: you can only run tasks until you've used your available credits. See How Billing Works for how credits are consumed and how to check your balance with account.balance. There is no separate API call that "reserves" credits; a tasks.create that would push you past your balance fails at the time of creation.
Retry guidance
Some errors are worth retrying and some aren't. As a rule:
400,401,403,404,409: don't retry. These are deterministic failures; the same request will keep failing until you fix it.429: retry, but only after a delay. Use theRetry-Afterheader if the response includes one; otherwise back off exponentially.500,502,503,504: retry with exponential backoff, capped at a small number of attempts. These are usually transient.
A reasonable pattern for retryable errors:
attempt = 0
max_attempts = 5
while attempt < max_attempts:
response = call()
if response is successful:
return response
if response status is non-retryable:
raise
wait = min(60, 2 ** attempt) seconds, with a little jitter
sleep(wait)
attempt += 1
raise last errorThe SDKs handle automatic retries for transient transport-level failures (connection resets, brief network blips) at the HTTP layer. Application-level retry decisions (whether to retry a 429, a 500, or a timeout) are left to your code, since the right strategy depends on what the request was doing.
A note on retrying tasks.create
Retrying a failed tasks.create is special: if the original request actually reached the server and created a task, retrying creates a second task. You'll then have two tasks running, and if both succeed you'll be billed for both.
Two approaches that work:
- Treat
tasks.createas not idempotent. Only retry on errors that clearly indicate the request never reached the server (connection errors,5xxreturned by an upstream gateway before the request body could be read). Don't retry on read timeouts. - Track tasks by your own ID. Submit each task with a client-side correlation identifier (in your application's own database), and before retrying, list recent tasks with
tasks.listto check whether the original attempt actually went through.
For most workloads, the first approach is enough. Reach for the second only if you're submitting tasks at scale or in contexts where double-billing is a real concern.