init.Tasks openapi.json inittasks.com

the buckets, the headers, and how to behave

Rate limits

Every response carries the state of the bucket it charged:

http
RateLimit-Limit: 600
RateLimit-Remaining: 587
RateLimit-Reset: 1789459620

Every 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

  1. Read RateLimit-Remaining and slow down. Do not wait for the 429.
  2. On a 429, sleep Retry-After. It is a number the server gives you, not a guess.
  3. Back off exponentially on a 5xx, with jitter, so a restart does not gather every client into one retry.
  4. Use updated_since rather 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.
  5. 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.