Base URL
Content type
All request and response bodies are JSON. SetContent-Type: application/json on every POST, PUT, and PATCH.
Error envelope
Every error response uses this shape:
See the errors page for the full code reference.
Rate limits
Default: 60 requests per minute per account. Exceeding the limit returns429:
Retry-After response header gives the number of seconds to wait. Implement exponential backoff with jitter for production traffic.
Idempotency
POST /api/v1/courses/{course_id}/launch requires an Idempotency-Key header. The key is a unique string per intended launch (UUIDs work well).
- First call with a given key starts the generation.
- Subsequent calls with the same key while the course is already past
awaiting_briefreturn the existing course without re-launching. - A missing key returns
400withcode: idempotency_key_required.
Pagination
List endpoints currently return arrays without cursor pagination:GET /api/v1/coursesaccepts?external_ref=to narrow results.GET /api/v1/presets/{id}/coursestakes no query parameters and returns all courses for the preset.
Timestamps
All timestamps are ISO-8601 strings in UTC, e.g.2026-06-23T10:30:00Z. Do not assume a local timezone in your parser.
external_ref idempotency
external_ref is an optional caller-supplied string (max 255 chars) on course create. When provided, it acts as an idempotency key scoped to your account: if you POST a course with the same external_ref a second time, the API returns the existing course instead of creating a duplicate. The response shape is identical to a fresh create.
Use external_ref to tie a course to an entity in your own system (a company ID, an onboarding session, a campaign run). It also powers per-ref breakdowns in the billing report.
null or absent external_ref disables idempotency: each call creates a new course.
Cancel SLA
POST /api/v1/courses/{id}/cancel returns 200 immediately after setting the cancel signal. The worker picks up the signal within ~30 seconds (its heartbeat cycle), then finalizes the course to cancelled at the next pipeline-stage boundary. A long in-flight stage (for example video assembly) can push the actual transition well past 30 seconds, so do not assume a fixed deadline. Always poll GET /api/v1/courses/{id}/status and wait for status: "cancelled" before treating the course’s quota as freed.
Do not reuse the course’s external_ref for a fresh course while the original is still in a non-terminal state.
Concurrency
Accounts have a per-account parallel generation cap. Submitting a launch when the cap is reached returns429 quota_exceeded. Poll status on your in-flight courses and retry after one reaches a terminal state (ready, error, or cancelled).
Completion order is not guaranteed: two courses launched at the same time may finish in any order depending on source complexity and module count. Drive your downstream logic from per-course status, not from submission order.
Source limits
The following hard limits apply to file and URL sources on course and preset create:
Accepted file types for course sources:
.pdf, .pptx, .docx, .txt.
Accepted file types for preset brand sources (superset): .pdf, .pptx, .docx, .txt, .md, .png, .jpg, .jpeg, .webp, .bmp, .gif.
Exceeding any limit returns 400 invalid_request_error.
Skipping optional brief questions
Brief questions can beblocking (must be answered to proceed) or optional. To skip an optional question without providing an answer, set "skip": true in the answers payload:
422. Optional questions that are skipped do not re-appear in subsequent brief/next calls.
Versioning
The current API version is v1 (the/api/v1/... prefix). The conceptual model is sometimes referred to as “v2” because it succeeded an earlier preset-less v1; the URL path is v1 for historical reasons.
Breaking changes to existing endpoints will introduce a new path prefix (/api/v2/...) and the old prefix will remain operational for at least 12 months.