The public API rate-limits only key-authenticated traffic. The web UI (JWT) is never throttled by our limiter. So your integration’s traffic budget is independent of what your users do in the web app.Documentation Index
Fetch the complete documentation index at: https://teardowns.aero/docs/llms.txt
Use this file to discover all available pages before exploring further.
The four buckets
| Bucket | Scope | Limit | What it protects |
|---|---|---|---|
| A | per API key, JSON endpoints | 600 req/min (10/s sustained, burst 60) | Runaway loops, retry storms. |
| B | per source IP, failed auth on /public/v1/* | 30 fails/min | Brute-force keyspace scanning. |
| C | per source IP, /org/api-keys mint endpoint | 10 req/min | Abuse of the issuance UI. |
| D | per API key, upload endpoints | 60 req/min (burst 5) | Supabase Storage egress. 60 × 50 MB = 3 GB/min cap. |
What happens on overflow
Response:Retry-After header is in seconds. Wait at least that long before
retrying. Treat the value as authoritative our backend recalculates
it from the actual window state.
Designing your ERP around the limits
A few patterns that work in practice:Batch jobs that need a higher budget
Batch jobs that need a higher budget
Mint a second key just for the batch process. Each key has its own
600 rpm bucket. A nightly bulk-import script with its own key
doesn’t impair the day-to-day key.
Retries with backoff
Retries with backoff
On any 5xx or 429, retry with exponential backoff. Capped at, say,
5 retries with a base of 1 second and a max of 60.
Always respect
Retry-After when present it’s better than your
backoff math.Bursting safely
Bursting safely
Bucket A allows bursts up to 60 req. If you’ve been idle, you can
fire 60 in a second. After that the leaky bucket refills at 10/s.
So a
for loop firing 100 requests will burst the first 60, then
receive 429s for the next 40 until enough time has passed.Cleaner: just paced the loop at one request per 100 ms. Predictable
and never trips the limiter.Parallel uploads
Parallel uploads
Bucket D is 60/min with a tighter burst of 5. Parallel uploads in
excess of 5 will start receiving 429s. Cap your upload concurrency
at ~5 and queue the rest. The math:
- 5 simultaneous uploads, each 30 seconds → cycle every ~30 s.
- In one minute you can clear ~10 uploads at 5x concurrency.
- 60 rpm bucket gives you headroom for 60 uploads in a minute, provided you don’t spike the burst.
Polling vs. webhooks
Polling vs. webhooks
There are no public-API webhooks today, so partners sometimes poll.
For a status-check loop:
- Poll every 5 minutes for slow-moving state (e.g., counter-offers).
- Poll every 30 seconds for state that should change quickly.
- Never poll faster than 1/sec.
Visibility
- Every 429 includes a
Retry-Afterheader. That’s your primary signal. - Every failed auth (401s on
/public/v1/*) consumes bucket B. If your ERP is hammering with a wrong key, you’ll start getting 429s on top of the 401s. Fix the key. - Mint and rotation calls hit bucket C. A misbehaving CI job that re-mints keys in a loop will trip this within a minute.
What’s NOT rate-limited
- Anything authenticated with a JWT. Web app, internal scripts that use a Supabase JWT, etc. Out of scope for this limiter.
- Successful requests by an org’s other keys. Bucket A is per individual key. If your org has three keys, each gets its own 600 rpm.
When the platform behavior changes
The numbers above are the production defaults. We may raise the limits for specific partners on request emailsupport@teardowns.aero with
the integration name and a sense of the traffic you expect. We’re
unlikely to lower the limits without notice; if we ever did, you’d
hear about it via the changelog and via direct email if you’re an
active integration.
Implementation note
We useslowapi (a FastAPI rate-limit middleware) with a custom
key-func that reads the bearer token directly from the Authorization
header. UI traffic without a tdao_live_ prefix is exempt. Counts are
held in-memory per process with N worker processes the effective
limit per key per minute is N × the configured value (worst case). For
the 600 rpm default that’s not a meaningful difference in practice.
