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: long window (rolling 24-hour) that caps total volume
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.makegov.com/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
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.makegov.com/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.makegov.com/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.