Rate limits¶
Tango enforces rate limits to keep the API fast and reliable for everyone. Limits apply at the account level: requests made via API keys and OAuth2 tokens for the same account all draw from the same quotas.
Where to see your limits and usage¶
- Your current plan limits + near-real-time usage: Account profile
- Upgrade pricing / higher limits: Pricing
How rate limits work (burst + daily)¶
Most plans have more than one rate limit window:
- Burst: short window (e.g. per minute) that protects the API from sudden spikes
- Daily: fixed window that resets at midnight UTC, caps total volume per day
You may be “fine” on daily usage but still hit burst limits (or vice versa).
Rate limit headers¶
Every /api/* response includes rate limit headers.
Overall headers (most restrictive window)¶
These headers summarize the most restrictive window (the one you’re closest to hitting):
X-RateLimit-Limit: total requests allowed for that windowX-RateLimit-Remaining: requests remaining in that windowX-RateLimit-Reset: seconds until reset for that window
Per-window headers (daily, burst, etc.)¶
For each configured window (commonly Daily and Burst), you’ll also see:
X-RateLimit-Daily-Limit,X-RateLimit-Daily-Remaining,X-RateLimit-Daily-ResetX-RateLimit-Burst-Limit,X-RateLimit-Burst-Remaining,X-RateLimit-Burst-Reset
Each *-Reset value is seconds until that specific window resets.
Quick header check (curl)¶
curl -s -D - -o /dev/null \
-H "X-API-KEY: your-api-key-here" \
"https://tango.dev/api/contracts/?limit=1"
Example response headers:
X-RateLimit-Daily-Limit: 2400
X-RateLimit-Daily-Remaining: 2350
X-RateLimit-Daily-Reset: 86400
X-RateLimit-Burst-Limit: 100
X-RateLimit-Burst-Remaining: 95
X-RateLimit-Burst-Reset: 45
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 45
X-Execution-Time: 0.045s
Premium query limits (select endpoints)¶
Some endpoint families carry their own premium query budget instead of your account-wide limits. A request to one of those endpoints draws from that budget and does not count against your daily or per-minute allowance.
How premium limits work¶
- Separate budget: The premium budget exists only for designated endpoint families. Standard endpoints use your account-wide quotas.
- Replaces your standard limits: on these endpoints the premium budget is the only limit that applies — your daily and per-minute quotas are neither checked nor spent.
- Shared across endpoint family: Multiple endpoints may share a single premium counter. For example, the CALC labor-rate endpoints (
GET /api/idvs/{key}/lcats/andGET /api/entities/{uei}/lcats/) share one premium budget. - Counts requests, not rows: Each API call costs one request, regardless of pagination or the number of rows returned.
- Fixed daily window: The premium budget resets daily at 00:00 UTC.
Premium headers¶
On responses from endpoints with a premium budget (including 429s), you'll see:
X-RateLimit-Premium-Limit: total requests allowed in the premium budgetX-RateLimit-Premium-Remaining: requests remaining in the premium budgetX-RateLimit-Premium-Reset: seconds until reset for the premium budget
These headers appear only on requests to endpoints with a premium budget — they are absent everywhere else.
Finding your premium limits¶
Check your account profile to see your current premium usage and limits per endpoint family. To request a higher limit, email [email protected].
What happens when you exceed a limit (HTTP 429)¶
When you hit a rate limit, Tango responds with HTTP 429 and a JSON body like:
{
"detail": "Rate limit exceeded for burst. Please try again in 45 seconds.",
"wait_in_seconds": 45
}
Recommended client behavior¶
- Stop retrying immediately after a 429.
- Sleep for at least
wait_in_seconds(preferred) orX-RateLimit-Reset. - Then retry with exponential backoff + jitter to avoid a thundering herd.
Python example¶
import random
import time
import httpx
url = "https://tango.dev/api/contracts/?limit=1"
headers = {"X-API-KEY": "your-api-key-here"}
backoff = 1.0
for _ in range(10):
r = httpx.get(url, headers=headers)
if r.status_code != 429:
r.raise_for_status()
break
body = r.json()
wait = body.get("wait_in_seconds")
if wait is not None:
time.sleep(float(wait))
continue
reset = r.headers.get("X-RateLimit-Reset")
if reset is not None:
time.sleep(float(reset))
continue
time.sleep(backoff + random.random())
backoff = min(backoff * 2, 60)
JavaScript example¶
This example assumes fetch is available (modern browsers or Node 18+).
(async () => {
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const url = "https://tango.dev/api/contracts/?limit=1";
const headers = { "X-API-KEY": "your-api-key-here" };
let backoffMs = 1000;
for (let i = 0; i < 10; i++) {
const r = await fetch(url, { headers });
if (r.status !== 429) {
if (!r.ok) throw new Error(`HTTP ${r.status}`);
break;
}
const body = await r.json();
if (body.wait_in_seconds != null) {
await sleep(body.wait_in_seconds * 1000);
continue;
}
const reset = r.headers.get("X-RateLimit-Reset");
if (reset != null) {
await sleep(Number(reset) * 1000);
continue;
}
await sleep(backoffMs + Math.random() * 250);
backoffMs = Math.min(backoffMs * 2, 60_000);
}
})().catch((err) => {
console.error("Request failed:", err);
});
Reduce calls (and avoid limits) in practice¶
- Use response shaping (
shape=) to avoid extra “follow-up” requests. See the Response Shaping Guide. - Paginate responsibly; avoid re-fetching the same pages repeatedly.
- Cache hot lookups on your side when appropriate (e.g. “entity by UEI”).
- Prefer webhooks for event-driven updates instead of polling where possible.