AurionAI Docs

Error Handling

HTTP status codes, error response format, and retry strategies for the Aurion API.

Error Handling

The Aurion API uses standard HTTP status codes and returns structured JSON error responses.

HTTP Status Codes

CodeMeaningWhen
200OKRequest succeeded
201CreatedResource created successfully
204No ContentResource deleted successfully
400Bad RequestInvalid request body or parameters
401UnauthorizedMissing or invalid API key
403ForbiddenValid key but insufficient scope
404Not FoundResource does not exist
409ConflictIdempotency-Key reused with a different request body, or a request with the same key is still in progress
422Unprocessable EntityValidation error on request body
429Too Many RequestsRate limit exceeded
500Internal Server ErrorUnexpected server error
503Service UnavailableTemporary outage or maintenance

Error Response Format

Error responses use FastAPI's standard shape — a single detail field. There is no machine-readable error code on standard 4xx/5xx responses. Branch on the HTTP status code plus the detail string, not on a non-existent error field. (The only exception is a handful of endpoints with their own per-endpoint limiter — see Rate Limiting below.)

{
  "detail": "Human-readable description of what went wrong"
}
FieldTypeDescription
detailstringHuman-readable error description. For 422 validation errors, this is an array of error objects instead (see below).

Example bodies by status

StatusExample body
400{"detail": "Subject must be between 1 and 255 characters"}
401 (invalid key){"detail": "Invalid API key"} (also sets WWW-Authenticate: Bearer)
401 (missing credentials){"detail": "Missing authentication credentials"}
403 (missing scope){"detail": "API key missing required scope: tickets:write"}
403 (denied endpoint){"detail": "API keys are not permitted for this endpoint"}
404{"detail": "Ticket not found"}
409{"detail": "Idempotency key reused with a different payload"}
500{"detail": "Internal server error"}

Validation errors (422)

For request-body validation, FastAPI returns a 422 whose detail is an array of error objects with loc / msg / type keys — not a custom {field, message} array:

{
  "detail": [
    {
      "loc": ["body", "priority"],
      "msg": "Input should be 'low', 'medium', 'high' or 'urgent'",
      "type": "literal_error"
    }
  ]
}

Note that some endpoints surface business-rule validation as a plain-string 400 instead. For example, the public tickets endpoint raises 400 with {"detail": "<message>"} when ticket creation fails a business rule, rather than a structured 422.

Rate Limiting

Every API-key request passes two limits, and both count per API key.

Plan limit. It depends on your tenant's subscription plan and on the kind of endpoint. Each tier has a per-minute limit and a burst limit over 10 seconds; a request must fit both.

PlanGeneral endpointsSearchExport
Free, Starter100/min, burst 40 per 10 s20/min, burst 10 per 10 s5/min, burst 2 per 10 s
Pro, Enterprise300/min, burst 120 per 10 s60/min, burst 30 per 10 s15/min, burst 6 per 10 s

Any other plan, and a tenant without an active subscription, gets the Free limits. Export applies to endpoints whose path contains export (except GET on an …/exports list); Search applies to /api/v1/conversations with a search or query parameter; everything else is General.

Responses that pass the plan limiter carry its headers for the tier the request used:

HeaderMeaning
X-RateLimit-LimitRequests allowed per minute
X-RateLimit-RemainingRequests left in the current minute
X-RateLimit-ResetUnix time one full minute window after this request
X-RateLimit-Burst-LimitRequests allowed per 10-second burst window
X-RateLimit-Burst-RemainingRequests left in the burst window
X-RateLimit-Burst-ResetUnix time one full burst window after this request

When either window is exhausted, the API returns a 429 with those headers plus Retry-After:

HTTP/1.1 429 Too Many Requests
Retry-After: 59
X-RateLimit-Limit: 100
X-RateLimit-Burst-Limit: 40
X-RateLimit-Burst-Remaining: 0
{
  "error": "rate_limit_exceeded",
  "message": "Rate limit exceeded. Try again in 59 seconds.",
  "retry_after": 59,
  "tier": "general",
  "plan": "bundle_free"
}

Per-key ceiling. On every plan, one API key may also make at most 300 requests per minute. This check runs first; when it is exceeded the 429 carries Retry-After, X-RateLimit-Limit and X-RateLimit-Remaining, and a detail body:

{
  "detail": "API key rate limit exceeded",
  "limit": 300
}

A few specialized endpoints (for example, Knowledge Base ingestion) add stricter per-endpoint limits, listed on their own pages. Most of those return {"error": "Rate limit exceeded: …"} and may omit Retry-After; back off for the window shown on the endpoint's page.

Branch on the 429 status and honour Retry-After when it is present rather than parsing the body.

Retry Strategy

For transient errors (429, 500, 503), use exponential backoff with jitter:

Python
import time
import random
import requests

def request_with_retry(method, url, max_retries=3, **kwargs):
    for attempt in range(max_retries + 1):
        response = requests.request(method, url, **kwargs)

        if response.status_code == 429:
            retry_after = int(response.headers.get("Retry-After", 5))
            time.sleep(retry_after)
            continue

        if response.status_code in (500, 503) and attempt < max_retries:
            delay = (2 ** attempt) + random.uniform(0, 1)
            time.sleep(delay)
            continue

        return response

    return response
TypeScript
async function requestWithRetry(
  url: string,
  options: RequestInit,
  maxRetries = 3
): Promise<Response> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const response = await fetch(url, options);

    if (response.status === 429) {
      const retryAfter = parseInt(
        response.headers.get("Retry-After") ?? "5"
      );
      await new Promise((r) => setTimeout(r, retryAfter * 1000));
      continue;
    }

    if ([500, 503].includes(response.status) && attempt < maxRetries) {
      const delay = 2 ** attempt + Math.random();
      await new Promise((r) => setTimeout(r, delay * 1000));
      continue;
    }

    return response;
  }

  throw new Error("Max retries exceeded");
}

Best Practices

  • Branch on the HTTP status code — Don't depend on a machine-readable error field; standard errors return a detail string
  • Log the full response — Include the detail string for debugging
  • Watch X-RateLimit-Remaining and X-RateLimit-Burst-Remaining — Pace your requests to stay under both the per-minute and the 10-second burst limit
  • Respect Retry-After — On a 429, don't retry faster than the header suggests
  • Don't retry 4xx — Client errors (except 429) indicate a problem with your request

On this page