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: 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 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.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/ and GET /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 budget
  • X-RateLimit-Premium-Remaining: requests remaining in the premium budget
  • X-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
}
  • 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.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.