Every request to /api/v2 spends units from rate buckets. You can work out what a request costs before you send it, and every response tells you how much budget is left.
Ceilings
Every bucket counts over a window of 60 seconds.
| Bucket | Read units | Write units |
|---|---|---|
| Per token | 600 | 120 |
| Per organisation, shared by all its tokens and users | 3000 | 600 |
- Each token has its own budget, but every token in your organisation also spends from the organisation's bucket. Another token can therefore lower what you have left.
- For an app that signs in with OAuth, the per-token budget belongs to the app and the user who signed in. It carries over when the access token is refreshed.
Before the token is checked, a per-IP limit applies as well: 600 requests per minute per IP address and host, and 1200 requests per minute per IP address. It counts requests, not units, and it never shows in the RateLimit headers.
These ceilings are the current budget. Read RateLimit-Policy at runtime instead of hard-coding them.
Read and write buckets
- A
GETspends its cost from the read buckets. - A
POST,PATCHorDELETEspends its cost from the write buckets, plus 1 unit from the read buckets. A write therefore needs room in both, and an empty read bucket blocks writes too.
Each operation page in the reference shows the bucket it spends from.
What a request costs
cost = base 1, or 3 for view=detail on a collection
+ each include path +1, or +3 when the path is a list
+ q +2 when a search term is sent
+ with_total=1 +2
| Part | Units | When it applies |
|---|---|---|
| Base | 1 | Every request. |
Base with view=detail | 3 instead of 1 | Only on a collection read. On a single record and on a write, view=detail costs nothing extra. |
| Include path, to-one | +1 per path | The path inlines one related record, such as assignee. |
| Include path, to-many | +3 per path | The path inlines a list of records, such as labels: up to 25, or 50 for tags. |
q | +2 | A search term is sent. |
with_total=1 | +2 | A collection read asks for the total. |
A DELETE always costs 1.
Name each include path once. A path you repeat, such as include=labels,labels, is loaded once but charged every time it appears.
Worked example
GET /work-items takes the to-many include paths comments, drive_items, labels and subtasks, and to-one paths such as assignee and sprint.
| Request | Cost |
|---|---|
GET | 1 |
?view=detail | 3 |
?include=assignee | 1 + 1 = 2 |
?include=labels | 1 + 3 = 4 |
?include=assignee,sprint,labels | 1 + 1 + 1 + 3 = 6 |
?q=invoice&with_total=1 | 1 + 2 + 2 = 5 |
?view=detail&q=invoice&with_total=1&include=comments,drive_items,labels,subtasks,assignee | 3 + 2 + 2 + 4 × 3 + 1 = 20 |
With each path named once, the last request is as expensive as GET /work-items gets: it names as many include paths as one request may, and every to-many path is among them. Limits at a glance has the maximum.
What costs exactly one unit, and what costs nothing
The extra units are charged only on a successful 2xx response that was not replayed. So these cost exactly 1 unit, whatever the request asked for:
- a
304 Not Modified - a replayed
POSTthat returns the stored response for itsIdempotency-Key - an error response, such as a
400or a422
These cost no units at all:
- a
429 - a path or method that does not exist
- a request refused before or at the token check: an address that serves no API, a switched-off REST API, or a missing or invalid token. It still counts towards the per-IP limit.
The headers
Every response carries the current state of your budget, errors and 429 included:
RateLimit: limit=600, remaining=598, reset=42
RateLimit-Policy: 600;w=60
| Header | Means |
|---|---|
RateLimit | limit is the ceiling of the tightest bucket this request spent from: the one with the fewest units left. remaining is what is left in it, after this request's own cost. reset is the number of seconds until it refills. |
RateLimit-Policy | The same bucket's ceiling and window in seconds, as <limit>;w=<seconds>. |
These headers use the syntax of an earlier draft of the IETF RateLimit header fields. Later drafts changed the syntax, so parse the form shown here.
Three older headers are still sent, but they are deprecated and stop on 2027-09-01:
X-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-Reset
Move to RateLimit now. Note that X-RateLimit-Reset is a Unix timestamp, while the reset in RateLimit counts seconds from now.
When you run out
You get 429 rate-limit-exceeded:
{
"type": "https://developers.plnnrly.com/api/v2/problems/rate-limit-exceeded",
"title": "Rate limit exceeded",
"status": 429,
"detail": "The rate limit for this caller has been exhausted. Retry after the interval in the Retry-After header.",
"instance": "/api/v2/work-items",
"request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S",
"rate_limit": {
"limit": 600,
"retry_after": 42
}
}
rate_limit.limit is the ceiling that ran out. rate_limit.retry_after and the Retry-After header give the seconds until it refills. Wait that long: a retry that arrives earlier is refused again.
A 429 is always safe to retry, because the request did not run. On a POST, retry with the same Idempotency-Key anyway.
MCP and OAuth
- MCP. Calls through the MCP server do not spend these units. The MCP server allows 60 requests per minute per user, and every MCP request counts, not only tool calls. See Tools.
- OAuth. Registering an app and getting or refreshing its tokens have limits of their own. See Sign in with OAuth.
Spending less
- Ask for full pages. Send
per_page=100: a page costs the same whether it holds 1 row or 100. With a to-many include, the maximum is 25 rows per page. - Do not ask for a total you will not read.
with_total=1adds 2 units, andlinks.nextalready tells you whether there is another page. - Prefer an include over follow-up reads. A to-one path adds 1 unit for a whole page of rows. A to-many path adds 3 and limits the page to 25 rows, so compare it with reading that list separately.
- Stay on the summary view while paging.
view=detailtriples the cost of a collection read. - Honour the cache lifetime. A few configuration reads, such as labels and work types, declare
Cache-Control: private, max-age=60, and discovery declaresprivate, max-age=300. The operation page says so. Re-reading them sooner spends units for nothing. - Revalidate instead of reading again. Send
If-None-Match: a304costs 1 unit, even for a request that would otherwise cost more. See Concurrency and ETags.