Rate limits
Rate limits cap how often you can call the API. They are enforced as fixed-window counters in Redis and are completely separate from credits, which meter the cost of each call. Exceeding a limit returns HTTP 429 — and a 429 never deducts credits.
Authenticated limits (per API key)
Section titled “Authenticated limits (per API key)”Each API key is tracked independently against three rolling windows — per minute, per hour, and per day — plus a short burst allowance for momentary spikes. The limits are set by your plan:
| Plan | Per minute | Per hour | Per day | Burst |
|---|---|---|---|---|
| Free | 10 | 100 | 500 | 15 |
| Starter | 60 | 1,000 | 10,000 | 100 |
| Professional | 300 | 10,000 | 100,000 | 500 |
| Enterprise | 1,000 | 50,000 | 500,000 | 2,000 |
| Custom | 1,000 | 50,000 | 500,000 | 5,000 |
All three windows must pass; the tightest one that trips is the one that returns 429. Limits are per key, so splitting traffic across multiple keys on the same account multiplies your effective ceiling up to your plan’s key cap.
Demo key limits (shared)
Section titled “Demo key limits (shared)”Every endpoint requires a key — there is no anonymous access. The free public demo key (mtk_demo) is shared across all demo callers, so it draws from a single common bucket rather than a per-account one:
Demo key (mtk_demo) | |
|---|---|
| Requests / minute | ~240 (shared across everyone using the demo) |
| Credit budget | ~100 credits / hour, pooled across all demo callers, auto-refilling |
| Result cap | 3 per list (400 if you request more) |
| Pagination | Not available (cursor/offset/page return 400) |
Because the demo bucket is shared, it can be throttled (429) or run dry (402) when many people are using it at once. When the hourly credit budget is exhausted, demo requests return 402 INSUFFICIENT_CREDITS until the bucket refills. For predictable headroom and your own balance, create your own API key.
Response headers
Section titled “Response headers”On every successful request the API reports your current standing against the primary (per-minute) window:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | The limit for the current window. |
X-RateLimit-Remaining | Requests left in the current window. |
X-RateLimit-Plan | The plan label the limit was derived from. |
When you are throttled, the response is 429 and carries recovery hints instead:
| Header | Meaning |
|---|---|
Retry-After | Seconds to wait before retrying (the tripped window’s length). |
X-RateLimit-Limit | The limit that was exceeded. |
X-RateLimit-Remaining | Always 0 on a 429. |
X-RateLimit-Reset | Which window tripped: minute, hour, or day. |
A 429 body uses the standard error envelope:
{ "success": false, "error": { "message": "Rate limit exceeded. Please try again later.", "code": "RATE_LIMIT_EXCEEDED" }, "statusCode": 429, "timestamp": "2026-06-05T12:00:00.000Z"}Handling 429: back off and retry
Section titled “Handling 429: back off and retry”429 is a transient, retryable condition. Honour Retry-After when present, and otherwise fall back to exponential backoff with jitter. Never retry in a tight loop — that only deepens the throttle.
async function requestWithBackoff(url, options = {}, maxRetries = 5) { for (let attempt = 0; attempt <= maxRetries; attempt++) { const res = await fetch(url, options);
if (res.status !== 429) { return res; }
// Prefer the server's hint; otherwise exponential backoff with jitter. const retryAfter = Number(res.headers.get('Retry-After')); const wait = Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1000 : Math.min(2 ** attempt * 1000, 60_000) + Math.random() * 1000;
await new Promise((resolve) => setTimeout(resolve, wait)); }
throw new Error('Rate limited: retries exhausted');}
const res = await requestWithBackoff('https://api.quantconomy.com/api/v1/entries', { headers: { Authorization: 'Bearer mtk_your_key_here' },});import randomimport timeimport requests
def request_with_backoff(url, headers=None, max_retries=5): for attempt in range(max_retries + 1): res = requests.get(url, headers=headers)
if res.status_code != 429: return res
# Prefer the server's hint; otherwise exponential backoff with jitter. retry_after = res.headers.get("Retry-After") if retry_after and retry_after.isdigit(): wait = int(retry_after) else: wait = min(2 ** attempt, 60) + random.random()
time.sleep(wait)
raise RuntimeError("Rate limited: retries exhausted")
res = request_with_backoff( "https://api.quantconomy.com/api/v1/entries", headers={"Authorization": "Bearer mtk_your_key_here"},)Rate limits vs credits
Section titled “Rate limits vs credits”These are two independent systems, and it is worth being explicit about how they interact:
- A rate limit caps request frequency (per key; the demo key shares one bucket). Hitting it returns
429and costs no credits. - Credits cap request volume by cost. Running out returns
402(for the demo key, when its shared hourly budget is empty). - Both checks run on every request. A call can be rejected by either, and the rate-limit check happens first — so a throttled request is never billed.
- Buying more credits does not raise your rate limit, and a higher rate limit does not grant credits. To raise both, upgrade your plan.