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
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": "enum"
    }
  ]
}

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

The API allows a default of 300 requests per minute per API key in both production and staging. When you exceed that limit, the API returns a 429 with the standard detail body:

HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
{
  "detail": "API key rate limit exceeded",
  "limit": 300
}

The 429 response carries these headers:

HeaderMeaning
Retry-AfterSeconds to wait before retrying
X-RateLimit-LimitRequests allowed in the window (300)
X-RateLimit-RemainingRequests remaining in the window

A few specialized endpoints (for example, Knowledge Base ingestion) enforce stricter, per-endpoint limits through a separate limiter and return an { "error": "rate_limit_exceeded", "message": "...", "retry_after": N } body instead — that envelope is the exception, not the norm.

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 — Pace your requests to stay under the per-key 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