Errors & rate limits
The error envelope
Every error uses the same shape and a correct HTTP status:
{ "success": false, "error": { "code": "not_found", "message": "unknown series" } }
message is written to be shown to a person. code is stable and meant for your code to branch on.
Status codes
| HTTP | code |
When | What to do |
|---|---|---|---|
| 401 | unauthorized |
Missing or invalid API key or session | Check the X-API-Key header; create a new key if it was revoked |
| 402 | credits_exhausted |
The free plan has used this month's 500 calls | Wait for the reset on the 1st, or upgrade |
| 402 | subscription_inactive |
Subscription cancelled, expired or past_due beyond the 3-day grace |
Update billing in the dashboard |
| 403 | tier_forbidden |
The feature needs a higher plan: vintage= needs Pro or Business, webhooks need a paid plan, or you're at the 3-active-key or per-plan webhook cap |
Upgrade, or free up a slot |
| 404 | not_found |
Unknown series id, fund, security, AMC or commodity | Check the id with /v1/search |
| 409 | - | Signup with an email that already has an account | Sign in instead |
| 422 | invalid_params |
Bad parameter: malformed date, bad dims token, unknown transform, or a transform that doesn't fit the frequency |
Fix the request; message says what's wrong |
| 429 | rate_limited |
Per-minute or per-day limit hit | Wait the Retry-After seconds |
Examples:
{ "success": false, "error": { "code": "invalid_params",
"message": "unknown transform 'bogus'; choose one of 3ma, index, mom, pct_change, yoy" } }
{ "success": false, "error": { "code": "unauthorized", "message": "missing API key" } }
Rate limits
Limits apply per account, across all its keys. Every keyed response carries:
X-RateLimit-Limit: your per-minute ceiling.X-RateLimit-Remaining: requests left right now (the lower of your minute and day budgets).
The per-minute limit is a true sliding 60-second window. The daily limit resets at midnight.
| Plan | Per minute | Per day |
|---|---|---|
| Free | 10 | 500 calls a month (resets on the 1st) |
| Developer | 60 | 10,000 |
| Pro | 300 | 100,000 |
| Business | 600 | 500,000 |
Check your live usage with GET /v1/usage.
Handling 429
import time, httpx
def get(url, key):
while True:
r = httpx.get(url, headers={"X-API-Key": key}, timeout=30)
if r.status_code != 429:
return r
time.sleep(int(r.headers.get("Retry-After", "5")))
Tips to use fewer requests
- Use
/v1/series/{id}/latestfor tickers instead of pulling full history. - Pull a long history in one call (
limitup to 10,000) rather than many small windows. - Use
/v1/funds/screenerfor every fund in one request instead of one call per fund. - Subscribe to webhooks instead of polling for updates (paid plans).
- Cache responses: most series update daily, weekly or monthly, never intraday.