Errors & Limits

Error codes, rate limit behavior, and retry strategies for the edgar.tools API.

Error response format

All API errors return JSON with a consistent structure. The error field is always present. Other fields vary by error type:

{
  "error": "Human-readable error message",
  "detail": "Additional context (when available)"
}

Some errors include extra fields like your_tier, upgrade_url, or retry_after depending on the context.

HTTP status codes

The API uses standard HTTP status codes.

Success

CodeNameDescription
200OKRequest succeeded

Client errors

CodeNameCommon cause
400Bad RequestInvalid parameters or malformed request
401UnauthorizedMissing or invalid API key
403ForbiddenValid key but insufficient tier for this endpoint
404Not FoundResource does not exist (unknown CIK, accession, etc.)
429Too Many RequestsRate limit exceeded

Server errors

CodeNameDescription
500Internal Server ErrorSomething went wrong on our side. Retry or contact support.
502Bad GatewayUpstream SEC or data source temporarily unavailable

Error examples

401 — Missing API key

{
  "error": "Authentication required",
  "message": "Valid API key required. Use 'Authorization: Bearer etk_...' header."
}

403 — Insufficient tier

{
  "error": "Insufficient tier",
  "message": "Requires professional or higher",
  "your_tier": "free"
}

404 — Resource not found

{
  "error": "Company not found",
  "identifier": "9999999999"
}

429 — Rate limit exceeded

{
  "error": "quota_exceeded",
  "window": "monthly",
  "limit": 100000,
  "used": 100000,
  "reset": "2026-05-01T00:00:00.000Z",
  "upgrade_url": "https://app.edgar.tools/pricing",
  "user_id": "u_..."
}

Rate limits

Every API request counts against a per-user daily and monthly quota tied to your subscription tier. Exceeding either window returns 429 quota_exceeded until the window resets.

Tier quotas

TierRequests / dayRequests / monthAPI keys
Free1001,0001
Professional10,000100,0005
Analyst50,0001,500,00010
Enterprise100,0001,000,00020

Windows are UTC. The daily window resets at 00:00 UTC; the monthly window resets at 00:00 UTC on the 1st of each month. The reset field in the 429 body is ISO 8601, and the Retry-After header is the matching duration in seconds.

Reading the meter

Every response (not just 429s) includes rate-limit headers:

X-RateLimit-Limit:     100000
X-RateLimit-Remaining: 99843
X-RateLimit-Reset:     1777852800

X-RateLimit-Limit reflects the monthly cap. Remaining is monthly requests remaining. Reset is the Unix timestamp of the next monthly reset. When a daily 429 fires, the 429 response's headers + body switch to the daily window so clients don't get misleading wait times.

The /settings page at app.edgar.tools shows your live monthly usage and reset time.

Auth endpoints

Separate from API quotas, auth endpoints (/login, /signup, /forgot-password, /resend-verification) are rate-limited per IP/email to prevent brute-force. They return 429 Too Many Requests with Retry-After in seconds.

Retry strategy

When you receive a 429, always respect the Retry-After header. For 500/502 errors, use exponential backoff. Never retry 400, 401, or 403 — they indicate a problem with your request, not a transient issue.

import time
import requests

def api_call_with_retry(url, headers, max_retries=3):
    for attempt in range(max_retries):
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            return response.json()
        if response.status_code == 429:
            # Always use the server's Retry-After value
            wait = int(response.headers.get("Retry-After", 60))
            time.sleep(wait)
            continue
        if response.status_code >= 500:
            # Exponential backoff for server errors
            time.sleep(2 ** attempt)
            continue
        # 4xx errors (except 429) — don't retry
        response.raise_for_status()
    raise Exception("Max retries exceeded")
async function apiCallWithRetry(url, headers, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch(url, { headers });
    if (response.ok) return response.json();
    if (response.status === 429) {
      const wait = parseInt(response.headers.get("Retry-After") || "60");
      await new Promise(r => setTimeout(r, wait * 1000));
      continue;
    }
    if (response.status >= 500) {
      await new Promise(r => setTimeout(r, (2 ** attempt) * 1000));
      continue;
    }
    throw new Error(`API error: ${response.status}`);
  }
  throw new Error("Max retries exceeded");
}

Best practices

  • Respect `Retry-After`. Always wait the number of seconds specified in the header before retrying.
  • Cache responses. SEC filings don't change once published. Cache company profiles and filing data to reduce API calls.
  • Use pagination. Fetch only what you need with limit and offset parameters.
  • Handle errors gracefully. Check the error field in every non-200 response. Don't assume a shape for the response body.

To summarize

  • All errors return JSON with an error field. Use the HTTP status code to classify the problem.
  • 429 quota_exceeded means you hit a per-tier daily or monthly cap. The body's window field tells you which; reset tells you when.
  • Every response carries X-RateLimit-Limit/Remaining/Reset headers — use them to self-throttle before you hit the wall.
  • Auth-endpoint rate-limits are separate and per-IP.
  • Never retry 400, 401, or 403. Use exponential backoff for 500/502.