Rate limits & spend caps
Two independent controls protect you and us: a rate limit on how fast you may call, and a spend cap on how much a billing period may cost. They are unrelated - you can be well under your rate limit and still hit your spend cap, and vice versa.
Rate limits
Requests are metered against a token bucket that refills continuously, so a short burst is fine and a sustained flood is not. Which bucket a request draws from depends on how much the endpoint costs us to serve:
| Bucket | Applies to |
|---|---|
default | Reads and ordinary writes. This is your plan's headline requests-per-minute. |
expensive | Endpoints that dispatch real research spend: POST /claim, POST /media/probe, the upload-* endpoints, POST /session/start, POST /session/client-start. A small fraction of the same budget - pace these deliberately. |
provision | /provision/*. Its own, much tighter budget: creating accounts is not something a healthy integration does in a loop. |
Every response tells you where you stand, so you never have to discover the limit by hitting it:
HTTP/1.1 200 OK
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 417
X-RateLimit-Reset: 1788306408Over the limit you get 429 with Retry-After (whole seconds) and the same three headers:
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded - slow down and retry",
"details": {
"tier": "expensive",
"limit_per_minute": 60,
"retry_after_seconds": 4
},
"request_id": "req_82f93a9f45154f57a061e5ea8e2609c1"
}
}Retry-After is authoritative - honour it rather than retrying on a fixed schedule. X-RateLimit-Reset is a unix timestamp.
Need a higher limit? Rate limits are not self-service - contact us. An integrator running a device fleet or a high-volume backend can be granted a far higher ceiling (or exempted entirely) on their account alone, without changing anyone else's plan.
Spend cap
Your plan carries a maximum spend per billing period. You can read it, and how much of it you have used, at any time:
curl -s $RTFC/spend-limit -H "X-Auth-Token: $TOKEN"{
"effective_cap_usd": 200.0,
"cap_source": "self",
"self_cap_usd": 200.0,
"plan_cap_usd": 750.0,
"period_start": "2026-09-01T00:00:00",
"period_end": "2026-10-01T00:00:00",
"spent_usd": 41.72,
"remaining_usd": 158.28,
"paused": false
}You can also set your own, lower cap. This is the control to reach for if you are running an automated integration and want a guaranteed maximum for the month:
curl -s -X PUT $RTFC/spend-limit -H "X-Auth-Token: $TOKEN" \
-H 'Content-Type: application/json' -d '{"monthly_cap_usd": 200}'
# Remove it (back to your plan's own ceiling)
curl -s -X PUT $RTFC/spend-limit -H "X-Auth-Token: $TOKEN" \
-H 'Content-Type: application/json' -d '{"monthly_cap_usd": null}'A cap you set can only ever lower your ceiling - the limit in force is the smallest of your plan's and your own - so setting one can never grant you more budget. Asking for a value above your plan's cap returns 422 cap_above_plan_limit. cap_source in the response tells you which one is currently binding.
What happens at the cap
Reaching the cap is a hard stop, not a warning:
- Research pauses on every debate you own that could still cost money - not just live sessions, but any debate whose ingest is unfinished or that still has a fact-check in flight.
- Endpoints that would dispatch new spend return
402witherror.codequota_exceededorhard_cap_exceeded, anddetailscarrying your usage, your allowance and the reset time. - Claims already detected are held, never dropped. Nothing you have paid to transcribe is lost.
- A
quota.threshold_reachedwebhook fires at 80% and again at 100% (once each per period), alongside an email. Subscribe to it if you want to react before the stop rather than after.
Everything resumes automatically - held claims are re-queued oldest first - when the billing period rolls over, when you raise or clear your own cap, or when your plan changes.
Other ceilings
| Limit | Behaviour |
|---|---|
| Concurrent debates | 402 concurrency_limit when starting a session or media ingest beyond your plan's maximum. Stop a session or wait. |
| Live (SSE) connections | 429 past your plan's maximum concurrent streams. Close streams you no longer read - one per debate you are actually watching. |
| Media probes per hour | 429 with Retry-After. A probe is a real fetch of the platform's page, so it is metered separately from the rate limit. |
| Video duration | POST /media/probe returns reject_reason: "too_long" and an upload returns 422 too_long. Check the probe before uploading and you will never hit this mid-flow. |
| Machine users | 402 machine_user_limit when a provisioner account is at its maximum. It counts active accounts, so deleting a device frees a slot. |
Queueing is fair, not first-come-first-served. Claims are dispatched with a per-account cap and a round-robin release, so a large batch ingest never blocks another account's work - and never blocks its own later claims indefinitely either. A burst is queued and released as capacity frees; nothing is dropped.
Handling limits well
Read X-RateLimit-Remaining and slow down before it reaches zero rather than retrying into a wall. On a 429, wait exactly Retry-After seconds. On a 402, stop and surface it - retrying will not help until the period rolls over or the cap changes, and the details object tells you when that is. Branch on error.code, never on the status alone.