Skip to main content

Error Handling

The anyformat API uses conventional HTTP response codes and provides structured error responses to help you build robust integrations.

HTTP Status Codes

Status CodeMeaningDescription
200OKRequest succeeded
201CreatedResource was successfully created
202AcceptedRequest accepted for processing (async operations)
204No ContentRequest succeeded with no response body
400Bad RequestInvalid request data or parameters
401UnauthorizedInvalid API key (rejected by backend validation)
403ForbiddenMissing API key, empty Bearer token, or insufficient permissions
404Not FoundResource doesn’t exist
412Precondition FailedResults not yet complete (poll again)
429Too Many RequestsRate limit exceeded (check Retry-After header)
500Internal Server ErrorUnexpected server error
502Bad GatewayUpstream service error
503Service UnavailableService temporarily unavailable
504Gateway TimeoutBackend did not respond in time

Error Response Format

All error responses follow a consistent JSON structure:
{
  "error": "Brief, human-readable error description",
  "detail": "Detailed explanation of what went wrong",
  "error_code": "MACHINE_READABLE_ERROR_CODE",
  "retryable": false,
  "request_id": "a1b2c3d4e5f67890abcdef1234567890"
}
FieldDescription
errorShort, human-readable summary
detailDetailed explanation for debugging (may be an array for validation errors)
error_codeMachine-readable code for programmatic handling
retryableWhether the client should retry this request
request_idUnique request identifier for debugging and support

Error Codes Reference

Error CodeHTTP StatusRetryableDescription
AUTH_FAILED401NoInvalid API key (rejected by backend)
ACCESS_DENIED403NoMissing API key, empty Bearer token, or insufficient permissions
VALIDATION_ERROR400NoRequest validation failed
INVALID_JSON400NoMalformed JSON in request body
NOT_FOUND404NoResource not found
PRECONDITION_FAILED412YesResults not yet complete (poll again)
RATE_LIMITED429YesToo many requests (check Retry-After header)
INTERNAL_ERROR500/502/503YesServer error
GATEWAY_TIMEOUT504YesBackend did not respond in time

Authentication Errors

AUTH_FAILED (401) — Invalid API key
{
  "error": "Authentication failed",
  "detail": "Invalid or missing authentication credentials.",
  "error_code": "AUTH_FAILED",
  "retryable": false,
  "request_id": "a1b2c3d4e5f67890abcdef1234567890"
}
ACCESS_DENIED (403) — Missing API key or insufficient permissions
{
  "error": "Access denied",
  "detail": "API key is required. Use 'Authorization: Bearer <key>'.",
  "error_code": "ACCESS_DENIED",
  "retryable": false,
  "request_id": "a1b2c3d4e5f67890abcdef1234567890"
}

Validation Errors

VALIDATION_ERROR (400) — The detail field is an array of validation error objects:
{
  "error": "Validation failed",
  "detail": [
    {"type": "missing", "loc": ["body", "name"], "msg": "Field required"},
    {"type": "missing", "loc": ["body", "fields"], "msg": "Field required"}
  ],
  "error_code": "VALIDATION_ERROR",
  "retryable": false,
  "request_id": "a1b2c3d4e5f67890abcdef1234567890"
}
INVALID_JSON (400)
{
  "error": "Invalid JSON format",
  "detail": "The request body contains invalid JSON. Please check your JSON syntax.",
  "error_code": "INVALID_JSON",
  "retryable": false,
  "request_id": "a1b2c3d4e5f67890abcdef1234567890"
}

Resource Errors

NOT_FOUND (404)
{
  "error": "Resource not found",
  "detail": "The requested resource could not be found.",
  "error_code": "NOT_FOUND",
  "retryable": false,
  "request_id": "a1b2c3d4e5f67890abcdef1234567890"
}

Processing Status

PRECONDITION_FAILED (412) — Results not yet available. The retryable flag indicates whether to continue polling:
  • retryable: true — processing is still running. Poll again with backoff.
  • retryable: false — processing reached a terminal failure (error or cancelled). Stop polling.
{
  "error": "Extraction not yet available",
  "detail": "Extraction status is 'processing'. Results are available when status is 'processed'.",
  "error_code": "PRECONDITION_FAILED",
  "retryable": true,
  "request_id": "a1b2c3d4e5f67890abcdef1234567890"
}

Rate Limiting

RATE_LIMITED (429) — Response includes Retry-After header
{
  "error": "Rate limited",
  "detail": "Rate limit exceeded. Try again in 12s.",
  "error_code": "RATE_LIMITED",
  "retryable": true,
  "request_id": "a1b2c3d4e5f67890abcdef1234567890"
}
Rate-limited responses include a Retry-After header indicating how many seconds to wait before retrying. All successful responses include x-ratelimit-limit, x-ratelimit-remaining, and x-ratelimit-reset headers showing the limit for the tier that applies to that endpoint (extraction or general). The Retry-After header is only present on 429 responses.

Server Errors

INTERNAL_ERROR (500/502/503)
{
  "error": "Internal server error",
  "detail": "An unexpected error occurred while processing your request. Please try again or contact support.",
  "error_code": "INTERNAL_ERROR",
  "retryable": true,
  "reference_id": "err_1234",
  "request_id": "a1b2c3d4e5f67890abcdef1234567890"
}
GATEWAY_TIMEOUT (504)
{
  "error": "Gateway timeout",
  "detail": "The backend did not respond in time. Please try again.",
  "error_code": "GATEWAY_TIMEOUT",
  "retryable": true,
  "request_id": "a1b2c3d4e5f67890abcdef1234567890"
}
Server errors (500) include a reference_id field. Use this ID along with the request_id when contacting support to help identify and resolve the specific issue.

Error Handling Best Practices

1. Check HTTP Status Codes

import requests

response = requests.get(
    "https://api.anyformat.ai/v2/workflows/",
    headers={"Authorization": "Bearer your-api-key"}
)

if response.status_code == 200:
    workflows = response.json()
elif response.status_code == 401:
    print("Authentication failed. Check your API key.")
elif response.status_code == 403:
    print("Access denied. API key may be missing.")
elif response.status_code == 404:
    print("Resource not found.")
elif response.status_code == 412:
    print("Results not ready yet. Retry after a delay.")
elif response.status_code == 429:
    retry_after = response.headers.get("Retry-After", "5")
    print(f"Rate limited. Retry after {retry_after}s.")
elif response.status_code >= 500:
    print("Server error. Retry with backoff.")
else:
    error = response.json()
    print(f"Error: {error.get('detail')}")

2. Use Error Codes for Logic

def handle_api_error(response):
    if response.status_code >= 400:
        error_data = response.json()
        error_code = error_data.get('error_code')

        if error_code == 'AUTH_FAILED':
            refresh_authentication()
        elif error_code == 'ACCESS_DENIED':
            print("Missing or invalid API key.")
        elif error_code == 'VALIDATION_ERROR':
            show_validation_error(error_data.get('detail'))
        elif error_code == 'NOT_FOUND':
            handle_missing_resource()
        elif error_code == 'PRECONDITION_FAILED':
            # Still processing — retry
            return retry_after_delay()
        elif error_code == 'RATE_LIMITED':
            retry_after = int(response.headers.get('Retry-After', 5))
            time.sleep(retry_after)
            return retry_request()
        elif error_code == 'INTERNAL_ERROR':
            retry_with_backoff()
        elif error_code == 'GATEWAY_TIMEOUT':
            retry_with_backoff()
        else:
            log_unexpected_error(error_data)

3. Implement Retry Logic

For retryable errors (5xx, 429, 412), implement exponential backoff:
import time
import random

def api_request_with_retry(url, headers, max_retries=3):
    for attempt in range(max_retries + 1):
        try:
            response = requests.get(url, headers=headers)

            # Don't retry non-retryable client errors
            if 400 <= response.status_code < 500 and response.status_code not in (412, 429):
                return response

            # Handle rate limiting
            if response.status_code == 429:
                delay = int(response.headers.get('Retry-After', 5))
                time.sleep(delay)
                continue

            # Handle results polling
            if response.status_code == 412 and attempt < max_retries:
                time.sleep(5)
                continue

            # Retry server errors (5xx)
            if response.status_code >= 500 and attempt < max_retries:
                delay = (2 ** attempt) + random.uniform(0, 1)
                time.sleep(delay)
                continue

            return response

        except requests.exceptions.RequestException as e:
            if attempt < max_retries:
                delay = (2 ** attempt) + random.uniform(0, 1)
                time.sleep(delay)
                continue
            raise e

4. Validate Before Sending

def create_workflow(name, description, fields):
    # Validate required fields locally first
    if not name:
        raise ValueError("Workflow name is required")
    if not fields or len(fields) == 0:
        raise ValueError("At least one field is required")

    for field in fields:
        if not field.get('name'):
            raise ValueError("Field name is required")
        if not field.get('data_type'):
            raise ValueError("Field data_type is required")

    # Make API request
    response = requests.post(
        "https://api.anyformat.ai/v2/workflows/",
        headers={"Authorization": "Bearer your-api-key", "Content-Type": "application/json"},
        json={"name": name, "description": description, "fields": fields}
    )

    return handle_response(response)

5. Log Errors for Debugging

import logging

def log_api_error(response, context=""):
    try:
        error_data = response.json()
        error_code = error_data.get('error_code', 'UNKNOWN')
        request_id = error_data.get('request_id', 'N/A')
        reference_id = error_data.get('reference_id', 'N/A')

        logging.error(
            f"API Error {response.status_code}: {error_code} - "
            f"{error_data.get('detail', 'No detail')} "
            f"[Context: {context}] [Request: {request_id}] [Reference: {reference_id}]"
        )
    except ValueError:
        logging.error(f"API Error {response.status_code}: {response.text}")

Testing Error Scenarios

When building your integration, test these common scenarios:
ScenarioHow to Test
Missing API keyOmit the Authorization header
Invalid API keyUse a fake or expired key
Missing required fieldsCreate workflow without name or fields
Invalid JSONSend malformed JSON in request body
Nonexistent resourceRequest a workflow or file ID that doesn’t exist
Results not readyPoll GET /v2/files/{id}/extraction/ before processing completes (expect 412)
Rate limit exceededSend rapid requests to trigger 429 with Retry-After header
Server errorsTest retry logic (mock 500 responses)

Framework-Level Errors

While most errors use the structured { "error", "detail", "error_code", "retryable", "request_id" } format, some framework-level responses (e.g., unknown URL path) may return a simpler format:
{ "detail": "Not found." }