Rate limits
Limits are applied per credential, not per IP address, because AI agents tend to burst and people often share a network. The credential is your token, or your connection if you signed in to connect; a connection counts as a single bucket no matter how often it refreshes in the background.
The limits
Section titled “The limits”| Surface | Per minute | Per hour |
|---|---|---|
| Most endpoints | 120 | 3,000 |
Search (/v1/search) |
30 | 600 |
The member directory (/v1/members, /v1/members/progress, /v1/members/{id}, /v1/invitations, /v1/reporting-lines) |
30 | 600 |
Learning queues (/v1/me/learning-queue, /v1/people/{id}/learning-queue) |
20 | 300 |
| Drafting an announcement | 60 | 600 |
| Planning or previewing an announcement | 10 | 60 |
| Sending or withdrawing an announcement | 5 | 30 |
| Creating a document or adding a YouTube video | 10 | 100 |
| Assigning or unassigning a lesson | 10 | 60 |
| Sending a pop quiz | 5 | 30 |
| Other training changes (obligations, retraining, a retraining policy) | 30 | 300 |
These are generous for interactive use. They exist to stop a runaway loop, not to throttle normal work.
Each of the member directory reads hands back names, addresses and, for an
organization admin, phone numbers a page at a time, so the cost that matters is how
fast a credential could copy the whole directory. They are held to a lower limit
than the other reads, and the same numbers apply to the MCP tools list_members,
get_member, list_pending_invitations, list_member_progress and
list_reporting_lines. The budget is per route and per tool, counted separately,
and not one pool across them.
A learning queue is not stored: every request works it out from the person’s roles,
duties, mastery and obligations, and every page of it is worked out again, so it is
held to a lower limit than the other reads. The same numbers apply to the MCP tools
get_my_learning_queue and get_user_learning_queue.
Assigning a lesson and sending a pop quiz reach people by email and text, and
each has questions written for every person it reaches; unassigning can take a
lesson and its progress back from hundreds of people at once. So they are held
lower than the rest of the training changes, with
the same numbers for the MCP tools assign_training, unassign_training and
create_pop_quiz.
The announcement limits are lower because those acts cost something: planning and
previewing each spend a model call, and sending or withdrawing reaches real
people. The same numbers apply to the MCP tools, counted per tool call and not per
request, so send_announcement is held to five calls a minute however the
requests are made. The MCP endpoint takes one JSON-RPC message per request and
refuses a batch.
Handling a 429
Section titled “Handling a 429”When you exceed a limit, the API responds with HTTP 429 Too Many Requests and
a Retry-After header telling you how many seconds to wait. Respect it:
async function withRetry(makeRequest, maxRetries = 5) { for (let attempt = 0; attempt <= maxRetries; attempt++) { const res = await makeRequest(); if (res.status !== 429) return res; const wait = Number(res.headers.get('Retry-After') ?? 5); await new Promise((r) => setTimeout(r, wait * 1000)); } throw new Error('Still rate-limited after the maximum number of retries');}