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
| Code | Name | Description |
|---|---|---|
| 200 | OK | Request succeeded |
Client errors
| Code | Name | Common cause |
|---|---|---|
| 400 | Bad Request | Invalid parameters or malformed request |
| 401 | Unauthorized | Missing or invalid API key |
| 403 | Forbidden | Valid key but insufficient tier for this endpoint |
| 404 | Not Found | Resource does not exist (unknown CIK, accession, etc.) |
| 429 | Too Many Requests | Rate limit exceeded |
Server errors
| Code | Name | Description |
|---|---|---|
| 500 | Internal Server Error | Something went wrong on our side. Retry or contact support. |
| 502 | Bad Gateway | Upstream 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
| Tier | Requests / day | Requests / month | API keys |
|---|---|---|---|
| Free | 100 | 1,000 | 1 |
| Professional | 10,000 | 100,000 | 5 |
| Analyst | 50,000 | 1,500,000 | 10 |
| Enterprise | 100,000 | 1,000,000 | 20 |
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: 1777852800X-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
limitandoffsetparameters. - Handle errors gracefully. Check the
errorfield in every non-200 response. Don't assume a shape for the response body.
To summarize
- All errors return JSON with an
errorfield. Use the HTTP status code to classify the problem. 429 quota_exceededmeans you hit a per-tier daily or monthly cap. The body'swindowfield tells you which;resettells you when.- Every response carries
X-RateLimit-Limit/Remaining/Resetheaders — use them to self-throttle before you hit the wall. - Auth-endpoint rate-limits are separate and per-IP.
- Never retry
400,401, or403. Use exponential backoff for500/502.