View as Markdown

Error Handling

Every non-2xx response from the Not AI Public REST API uses the same JSON envelope and the same enumerated code list. Treat the HTTP status as the coarse signal and the error.code string as the stable identifier you write logic against.

Error envelope

{
  "error": {
    "code": "INVALID_API_KEY",
    "message": "The presented API key is missing, malformed, or unknown.",
    "details": {
      "field": "..."
    }
  }
}
  • code is a stable identifier from the enumerated list below. Codes never change meaning inside a major version.
  • message is a human-readable sentence. Treat it as opaque. Copy changes are not breaking.
  • details is an optional object with field-level context. Validation errors populate it with the offending field name; most other errors omit it.

Health (GET /health) and the OpenAPI document (GET /openapi/v3.json) are the only routes that never return this envelope. They have no error responses defined in v1.

HTTP status codes

Status Meaning
200 Success. Body is the response envelope.
204 Success with no body. Used where the operation is intentionally empty.
400 The request was malformed or failed validation. details will name the offending field.
401 The request was unauthenticated. The key was missing, malformed, unknown, or for the wrong region.
403 The key is valid but is not permitted to access the requested resource (for example, a session that belongs to another integration).
404 The requested resource does not exist.
429 A per-minute rate-limit window was exhausted. A Retry-After header is included when the edge can compute one; if absent, back off with exponential delay. See Rate Limits.
5xx The API failed to serve the request. Retryable for idempotent reads.

Stable error codes

These codes are stamped on the v1 contract. New codes may be added in v1 (additive change); existing codes will never be removed or repurposed.

Code Typical status When you see it
INVALID_API_KEY 401 The presented key was missing, malformed, unknown, or for the wrong region. Identical envelope for all four cases by design.
MISSING_API_KEY 401 Reserved. The shipped middleware collapses all auth failures to INVALID_API_KEY; treat this code identically if you ever observe it.
INVALID_REQUEST 400 A query string or path parameter failed validation. details names the offending field.
NOT_FOUND 404 The path resolved to a resource that does not exist or is not visible to this integration.
FORBIDDEN 403 The key resolved successfully but cannot access the resource.
RATE_LIMITED 429 A per-minute rate-limit window was exhausted. Authorization: Bearer callers are isolated by key; x-api-key callers share a per-tier edge bucket and can be throttled by unrelated same-tier traffic. Honor Retry-After when present; otherwise back off exponentially.
UNSUPPORTED_OPERATION 400 The endpoint is documented but the specific combination of arguments is not yet supported. Adjust the request.
SERVICE_UNAVAILABLE 503 A downstream dependency is unavailable. Retry with backoff.
INTERNAL_ERROR 500 An unhandled exception was caught by the global error middleware. Retry with backoff and report if it persists.

Examples

400, bad query string:

{
  "error": {
    "code": "INVALID_REQUEST",
    "message": "pageSize must be between 1 and 100.",
    "details": { "field": "pageSize" }
  }
}

401, any auth failure:

{
  "error": {
    "code": "INVALID_API_KEY",
    "message": "The presented API key is missing, malformed, or unknown."
  }
}

404, unknown session id:

{
  "error": {
    "code": "NOT_FOUND",
    "message": "No session with that id is visible to this integration."
  }
}

500, unhandled server-side fault:

{
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "An unexpected error occurred."
  }
}

Retry semantics

The API performs no automatic server-side retries on your behalf. Clients should implement their own retry policy with the following rules:

  • 2xx. Never retry.
  • 400, 401, 403, 404. Never retry. Fix the request and try again.
  • 429. Honor Retry-After when the response includes it; otherwise back off with exponential delay and jitter. Do not retry sooner than the floor of either path.
  • 5xx (500, 502, 503, 504). Retry the request with exponential backoff and jitter. All v1 endpoints are GET and idempotent, so a retry is always safe.

A reasonable default policy for a server-to-server consumer:

import os
import time
import random
import requests

BASE_URL = "https://api.isnotai.com"
HEADERS = {"Authorization": f"Bearer {os.environ['ISNOTAI_API_KEY']}"}

def get_with_retry(path, params=None, max_attempts=5):
    for attempt in range(max_attempts):
        response = requests.get(f"{BASE_URL}{path}", headers=HEADERS, params=params, timeout=10)

        if response.status_code < 400:
            return response.json()

        if response.status_code == 429:
            retry_after = response.headers.get("Retry-After")
            if retry_after and retry_after.isdigit():
                time.sleep(int(retry_after))
            else:
                time.sleep((2 ** attempt) + random.random())
            continue

        if 500 <= response.status_code < 600:
            backoff = (2 ** attempt) + random.random()
            time.sleep(backoff)
            continue

        response.raise_for_status()

    raise RuntimeError(f"giving up on {path} after {max_attempts} attempts")

Log the full error envelope (code, message, and details) when a retry policy gives up. That is what Not AI support will need to triage.