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": "..."
}
}
}
codeis a stable identifier from the enumerated list below. Codes never change meaning inside a major version.messageis a human-readable sentence. Treat it as opaque. Copy changes are not breaking.detailsis 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-Afterwhen 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
GETand 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.