Skip to content

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

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 window
  • X-RateLimit-Remaining: requests remaining in that window
  • X-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-Reset
  • X-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
}
  • Stop retrying immediately after a 429.
  • Sleep for at least wait_in_seconds (preferred) or X-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.