Reference
Errors & Rate Limits

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.

StatusCategoryWhen you'll see it
200 OKSuccessThe request succeeded; the response body's data field has your payload.
400 Bad RequestValidation / malformedThe request body or query parameters are malformed or missing required fields. Check message for which field; fix the request before retrying.
401 UnauthorizedAuthenticationYour x-api-key header is missing, malformed, or no longer valid (e.g. the key was revoked).
403 ForbiddenAuthorizationThe authenticated identity doesn't have permission to perform the action. Rare today since keys inherit their owner's permissions.
404 Not FoundMissing resourceThe resource referenced by the URL (a task_id, a model_id, etc.) doesn't exist or isn't accessible to this key.
409 ConflictState conflictThe 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 RequestsRate limitYou've exceeded a rate limit. Wait before retrying. See Rate limits below.
500 Internal Server ErrorServer-sideSomething 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 TimeoutTransient infrastructureA 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:

HeaderMeaning
X-RateLimit-LimitThe maximum number of requests allowed in the current window.
X-RateLimit-RemainingThe number of requests left in the current window.
X-RateLimit-ResetWhen the current window resets, so you know when X-RateLimit-Remaining refills.
Retry-AfterOn 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 the Retry-After header 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 error

The 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.create as not idempotent. Only retry on errors that clearly indicate the request never reached the server (connection errors, 5xx returned 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.list to 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.