Error Handling

How errors are structured, what each error code means, and how to handle them in your integration.

Every error response from the AskEdgar API follows the same JSON structure, regardless of the error type. Once you've built handling for this envelope, you can handle every error the API throws.

Error response format

{
  "status": "error",
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded.",
    "details": {
      "limit": 200,
      "window": "1m",
      "retry_after": 42
    }
  },
  "request_id": "a1b2c3d4-5678-90ab-cdef-1234567890ab"
}
FieldDescription
statusAlways "error" for error responses, "success" for successful ones
error.codeStable, machine-readable string. Safe to match on programmatically
error.messageHuman-readable description. May change over time — don't parse it
error.detailsAdditional context specific to the error type. May be {} when no extra info applies
request_idUUID for tracing. Also returned as the X-Request-ID response header. Include this when contacting support

Error code reference

Authentication errors (401)

CodeMeaningWhat to do
missing_api_keyNo API-KEY header in the requestAdd the API-KEY header. Note: it's not Authorization or X-API-Key
invalid_api_keyKey not recognized after lookupCheck for typos, extra whitespace, or a missing ask-live- prefix
api_key_disabledKey exists but has been disabledCheck the API Platform to re-enable it, or generate a new key

Authorization errors (403)

CodeMeaningWhat to do
endpoint_not_allowedYour organization or key doesn't have access to this endpointCheck your plan's endpoint access. Some endpoints are Enterprise-only. Contact us to upgrade

Payment errors (402)

CodeMeaningWhat to do
insufficient_creditsNot enough credits to process the requestAdd credits via the billing page. The response includes your current balance and the estimated cost
{
  "status": "error",
  "error": {
    "code": "insufficient_credits",
    "message": "Insufficient credits to process this request.",
    "details": {
      "balance_dollars": "$0.50",
      "estimated_cost_dollars": "$1.20",
      "unit": "dollars"
    }
  }
}

You're never charged for a failed request. Use the free cost estimate endpoint to check costs before making a call.

Validation errors (422)

CodeMeaningWhat to do
validation_errorOne or more query parameters failed validationCheck the details.fields array for which parameters are invalid and why
attribute_errorAttribute error during request processingReview your request parameters against the API reference
{
  "status": "error",
  "error": {
    "code": "validation_error",
    "message": "Request validation failed.",
    "details": {
      "fields": [
        {
          "field": "ticker",
          "reason": "Value error, Field 'ticker' must contain only uppercase letters, numbers, dots, hyphens, or carets."
        }
      ]
    }
  }
}

Common validation issues: ticker must be uppercase (AAPL not aapl), dates must be YYYY-MM-DD format, and numeric range filters must have min ≤ max.

Rate limit errors (429)

CodeMeaningWhat to do
rate_limit_exceededToo many requests in the current 1-minute windowWait the number of seconds in the Retry-After header, then retry
trial_ticker_limitTrial account exceeded 50 unique tickers per endpoint per dayWait until midnight Central Time for the counter to reset, or upgrade your plan

See the rate limits page for full details on limits by plan, response headers, and retry strategies.

Server errors (500, 503)

CodeMeaningWhat to do
internal_errorUnhandled server errorRetry after a short delay. If persistent, contact support with the request_id
upstream_errorAn upstream dependency failedRetry after a short delay
database_errorDatabase error occurredRetry after a short delay
service_unavailableInfrastructure temporarily unavailableRetry after the number of seconds in the Retry-After header (typically 5s)

Server errors are rare and almost always transient. A simple retry with a short delay will resolve most of them.

Not found (404)

CodeMeaningWhat to do
not_foundThe requested resource doesn't existVerify the endpoint path. Check for typos or removed endpoints in the changelog

Error priority

Errors are checked in a specific order. If multiple issues exist, you'll only see the first one that fails:

  1. Authentication (401) → 2. Authorization (403) → 3. Rate limits (429) → 4. Credits (402) → 5. Validation (422) → 6. Processing

This means if you're seeing a 429, your authentication is fine. If you're seeing a 402, you're authenticated, authorized, and within your rate limit — you just need more credits.

Handling errors in code

Here's a robust error handler that covers every category:

import time
import requests

API_KEY = "your_api_key"
BASE_URL = "https://api.askedgar.com"
HEADERS = {"API-KEY": API_KEY}

class AskEdgarError(Exception):
    def __init__(self, status_code, code, message, details=None, request_id=None):
        self.status_code = status_code
        self.code = code
        self.message = message
        self.details = details or {}
        self.request_id = request_id
        super().__init__(f"[{status_code}] {code}: {message}")

def api_request(endpoint, params, max_retries=3):
    for attempt in range(max_retries):
        resp = requests.get(f"{BASE_URL}{endpoint}", params=params, headers=HEADERS)

        # Success
        if resp.status_code == 200:
            return resp.json()

        # Parse the error envelope
        try:
            body = resp.json()
            error = body.get("error", {})
            code = error.get("code", "unknown")
            message = error.get("message", "Unknown error")
            details = error.get("details", {})
            request_id = body.get("request_id")
        except ValueError:
            code, message, details, request_id = "unknown", resp.text, {}, None

        # Retryable: rate limit
        if resp.status_code == 429:
            wait = int(resp.headers.get("Retry-After", 2 ** attempt))
            print(f"Rate limited. Retrying in {wait}s... (attempt {attempt + 1})")
            time.sleep(wait)
            continue

        # Retryable: server errors
        if resp.status_code in (500, 503):
            wait = int(resp.headers.get("Retry-After", 5))
            print(f"Server error ({code}). Retrying in {wait}s... (attempt {attempt + 1})")
            time.sleep(wait)
            continue

        # Non-retryable: raise immediately
        raise AskEdgarError(resp.status_code, code, message, details, request_id)

    raise AskEdgarError(429, "max_retries", f"Max retries ({max_retries}) exceeded")

Usage:

try:
    data = api_request("/v1/dilution-rating", {"ticker": "AAPL"})
    print(data["results"])

except AskEdgarError as e:
    if e.code == "insufficient_credits":
        print(f"Need more credits. Balance: {e.details.get('balance_dollars')}")
    elif e.code == "endpoint_not_allowed":
        print("This endpoint requires an Enterprise plan.")
    elif e.code == "validation_error":
        for field in e.details.get("fields", []):
            print(f"Invalid field '{field['field']}': {field['reason']}")
    else:
        print(f"API error: {e}")
        print(f"Request ID for support: {e.request_id}")

Best practices

  • Match on error.code, not error.message. Codes are stable and machine-readable. Messages may change.
  • Always capture request_id. It's the fastest way for support to find your request in our logs.
  • Retry 429s and 5xx errors. These are transient. Use the Retry-After header when available, exponential backoff otherwise.
  • Don't retry 401, 402, 403, or 422 errors. These require you to fix something (auth, credits, permissions, or parameters) before the request can succeed.
  • Use the cost estimate endpoint to avoid insufficient_credits errors entirely by checking costs upfront.

Next steps