Skip to content

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.

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.

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');
}