Errors & limits
What went wrong, what it means, and what to do — plus every number the API will enforce on you.
Every error is JSON with the same two keys. error is a stable machine-readable code — safe to branch on, it will not change wording. message is for a human reading a log.
{
"error": "invalid_request",
"message": "Invalid option: expected one of \"deferred\"|\"immediate\"",
"issues": [
{ "code": "invalid_value", "path": ["quizFeedback"], "message": "…" }
]
}issues appears on validation failures only, and comes straight from the schema — path is the field that was wrong. It is the fastest way to fix a 400 and most people never look at it.
Every error code
invalid_request400unauthorized401not_found404rate_limited429insufficient_scope403plan_required403 or 402internal_error500Rate limits
120 requests per key per rolling minute. Going over returns 429 with a Retry-After header in seconds — respect it rather than retrying immediately, which just spends the next window too.
The limit is per key, so splitting a bulk job across two keys doubles it. That is not a loophole we mind; it is what separate keys are for.
What each plan unlocks
POST /v1/generate and the generate_course MCP tool.Anything above your plan returns 403 plan_required naming the plan it needs. Nothing is silently degraded — you never get a partial result because of your tier.
Monthly allowances
Metering is by course view: one learner opening a published course, counted per calendar month in UTC. One learner opening six courses is six views, and opening the same course six times is six views — it is traffic served, not people reached, which is why learners are unlimited on every plan.
Only Sandbox is hard-capped for usage: past its allowance every endpoint returns 402 until the month turns over or the workspace upgrades. A paid plan is never cut off for going over — extra views are charged at the published rate instead, and we say so before they are.
A paid plan can still be stopped for non-payment. A failed charge starts a grace period counted down on the billing page; if it runs out, published courses stop serving and the API returns 402 until the invoice is settled. Nothing is deleted or downgraded, and paying restores it immediately.
Sizes and counts
limit / offset/completions, /issued-certificates and /identities — the three lists that grow without bound. Default 50 and no maximum: ask for all four thousand of your learners in one call if that is what you want. The ceiling is how much data you have, and pagination.hasMore is there if you would rather page.promptscreensTargetstackCards413./identities/bulkPOST and DELETE. That is a batch size, not a quota — nothing limits how many calls follow.Things that look like errors and aren't
A draft course’s embed link 404s. That is the same response as a course that never existed, on purpose — an unpublished course shouldn’t be discoverable by trying ids. Publish it.
A certificate request can 403. The learner completed the course but didn’t meet its passingScore. That is the feature working.
A block rendering blank is almost always a misspelled field name. Nothing rejects unknown fields, so "lable" saves happily and shows nothing. Check it against the block reference.
A completion with a null score means the course has no scored blocks. It isn’t zero — it was never a test.