Errors & Rate Limits

ACP uses standard HTTP status codes and returns structured JSON error bodies. Rate limits protect the service from abuse and ensure fair access for all clients.

HTTP Status Codes

All endpoints return standard HTTP status codes. Successful responses use 200 or 202. Error responses include a JSON body with additional context.

CodeStatusMeaning
200OKRequest succeeded. Response body contains the result.
202AcceptedAsync task created. Poll the returned ID for the result.
400Bad RequestMissing or invalid parameters. Check the error message for specifics.
401UnauthorizedInvalid or missing API key. Provide a valid key via x-api-key or Authorization header.
404Not FoundThe requested resource (e.g., consensus task ID) does not exist.
429Too Many RequestsRate limit exceeded. Wait and retry after the Retry-After period.
500Internal Server ErrorUnexpected server error. If persistent, check /health and report the issue.
503Service UnavailableVectorize or an upstream LLM provider is temporarily unavailable.

Error Response Structure

All error responses follow a consistent JSON structure. The error field contains a machine-readable error type, and message provides a human-readable description.

Error response format
{
  "error": "bad_request",
  "message": "Missing required field: query",
  "status": 400
}

Error Fields

FieldTypeDescription
errorstringMachine-readable error type (e.g., "bad_request", "unauthorized", "rate_limited", "internal_error")
messagestringHuman-readable description of the error
statusintegerHTTP status code (mirrors the response status)

Error Examples

400 Bad Request

Returned when the request is malformed or missing required fields.

Missing query field
{
  "error": "bad_request",
  "message": "Missing required field: query",
  "status": 400
}

401 Unauthorized

Returned when the API key is invalid, expired, or not provided.

Invalid API key
{
  "error": "unauthorized",
  "message": "Invalid API key",
  "status": 401
}

429 Too Many Requests

Returned when the client exceeds the rate limit. The response includes a Retry-After header indicating how many seconds to wait.

Rate limited
{
  "error": "rate_limited",
  "message": "Rate limit exceeded. Retry after 12 seconds.",
  "status": 429
}

500 Internal Server Error

Returned for unexpected server failures. These are typically transient and can be retried.

Server error
{
  "error": "internal_error",
  "message": "An unexpected error occurred. Please try again.",
  "status": 500
}

Rate Limits

Rate limits are enforced per IP address and per API key. Exceeding these limits results in a 429 response.

LimitValueScope
Per-minute limit100 requests / minutePer IP address
Per-hour limit1,000 requests / hourPer API key
Burst limit10 requests / secondPer IP address

Rate limit headers

Every response includes rate-limit headers so your client can proactively throttle:

X-RateLimit-Limit -- Maximum requests in the current window.

X-RateLimit-Remaining -- Requests remaining in the current window.

X-RateLimit-Reset -- Unix timestamp when the window resets.

Retry-After -- Seconds to wait (only on 429 responses).

Retry Strategy

When you receive a rate-limit or transient error, use exponential backoff with jitter to avoid thundering herd problems.

Status CodeRetryableStrategy
400NoFix the request and resend. Do not retry the same payload.
401NoCheck your API key. Do not retry without fixing credentials.
429YesWait for the Retry-After duration, then retry.
500YesRetry with exponential backoff (1s, 2s, 4s, ...). Max 3 retries.
503YesUpstream is down. Retry after 5-10 seconds. Check /health.
Exponential backoff example
import time
import random
import requests

def request_with_backoff(url, payload, max_retries=3):
    for attempt in range(max_retries):
        response = requests.post(url, json=payload)

        if response.status_code == 200:
            return response.json()

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

        if response.status_code >= 500:
            wait = (2 ** attempt) + random.uniform(0, 1)
            time.sleep(wait)
            continue

        # Non-retryable error
        response.raise_for_status()

    raise Exception("Max retries exceeded")

Consensus endpoint latency

The /consensus-iterative and /consensus/sync endpoints can take 10-60 seconds to complete due to multiple LLM round-trips. Do not confuse a slow response with a timeout or error. Set your HTTP client timeout to at least 120 seconds for consensus requests.