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_request400 (413 for an oversized SCORM package)unauthorized401not_found404rate_limited429insufficient_scope403plan_required403 or 402conflict409internal_error500Rate limits
120 requests per key per clock minute, on every plan — a fixed window that resets on the 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), the embed and the embedded builder — and, in the dashboard, themes, translations, certificates and SCORM.Scale and Enterprise unlock no further endpoints; they add volume — course views, AI generations, webhook endpoints and storage, in the numbers below.
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. The same identified learner opening the same course again within 30 days is still that one view — resuming is not a new view — while anonymous opens have no learner to match on, so each one counts. Learners themselves 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, /completions/:id/attempts, /issued-certificates and /identities — the four lists that grow without bound. Default 50, maximum 10,000: ask for all four thousand of your learners in one call if that is what you want. A larger request is served at the maximum rather than refused, and pagination.limit says what you actually got with pagination.hasMore beside it.promptscreensTarget403 plan_required. Generation checks before the model runs, so a refusal never costs an allowance.403 plan_required; endpoints a workspace already has keep delivering after a downgrade.403 plan_required; what is already stored stays.403 plan_required; a course already over the line, after a downgrade, stays editable so you can edit your way back under it.stackCards413./identities/bulkPOST and DELETE. That is a batch size, not a quota — nothing limits how many calls follow.POST /v1/media as a multipart upload. Fonts up to 5MB, dashboard only. The same on every plan.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.