the buckets, the headers, and how to behave
Rate limits
Every response carries the state of the bucket it charged:
RateLimit-Limit: 600
RateLimit-Remaining: 587
RateLimit-Reset: 1789459620Every authenticated request is charged to its credential's all requests bucket, and every request that is not a GET (creates, edits, deletes and the action routes such as /complete, /move and /trash) is also charged to its writes bucket. A write's headers report the writes bucket, the one that runs out first.
RateLimit-Reset is the moment the window ends, in Unix seconds. On a refusal you also get Retry-After in seconds, and a rate_limited problem body.
The headers are on every response, not just refusals. A client that can see its remaining budget can slow down before it is cut off. One that only learns at the 429 has no way to behave well.
The buckets
| bucket | limit |
|---|---|
| per credential, all requests | 600 / 5 min |
| per credential, writes | 120 / 5 min |
GET /oauth/authorize |
100 / 15 min / IP |
POST /oauth/token |
60 / min / IP |
POST /oauth/register |
10 / hour / IP |
POST /oauth/revoke |
50 / 15 min / IP |
| approval code lookup | 10 / min / user, 30 / min / IP |
POST /v1/keys (first-party only) |
10 / day / user |
POST /v1/webhooks |
20 created / hour / user, sliding; ping, redeliver and URL checks: 60 / hour / user each (details) |
POST /v1/link-title (first-party only) |
120 / 5 min / user |
| hosted MCP, per connection: tool calls | 600 / 5 min, and the tools that change something 120 / 5 min |
| per IP, at the edge | 20 req/s, burst 100 |
POST /v1/keys is reached from the app, not from a token, so its daily limit is the app's rather than your integration's. See authentication.
The per-credential, OAuth, approval, keys and link-title windows are fixed rather than sliding: a burst that straddles a boundary can spend two windows' worth. The /webhooks hourly limits slide: a slot frees one hour after it was used.
The /oauth/* endpoints answer with the same three headers, but their RateLimit-Reset counts the seconds left in the window rather than naming a moment, and they add RateLimit-Policy (60;w=60 is 60 per 60 seconds). A refusal there is a 429 with an OAuth error body, not problem+json. "IP" is your address as it reaches us: each client is counted on its own, and an IPv6 address is counted with the rest of its /64.
A client registration is at most 8 KB, with a client_name of at most 100 characters and at most 5 redirect_uris of at most 512 characters each. Anything larger is a 400 invalid_client_metadata.
On the hosted MCP each tools/call is charged to the connection's two buckets, the same numbers as a REST credential: every call to the all-requests bucket, a tool that changes something to the writes bucket too. Listing tools, initialize and ping are free. The HTTP response carries the RateLimit-* headers; a refusal is HTTP 429 with Retry-After and a JSON-RPC error with code -32029.
Behaving well
- Read
RateLimit-Remainingand slow down. Do not wait for the 429. - On a 429, sleep
Retry-After. It is a number the server gives you, not a guess. - Back off exponentially on a 5xx, with jitter, so a restart does not gather every client into one retry.
- Use
updated_sincerather than re-reading everything. A full re-read on a timer is the usual reason an integration hits a limit at all. See pagination and filtering. - Prefer a webhook to a tight poll, and keep a slow reconciliation poll anyway, because inbound delivery is best-effort.
If you need more
The limits are tunable and currently generous against what real integrations use. If you are hitting one legitimately, write to hello@inittasks.com and say what you are building. There is also a #tasks-ideas channel on the Discord.