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:| Field | Description |
|---|---|
type | High-level category. One of: authentication_error, invalid_request_error, not_found_error, rate_limit_error, quota_exceeded, server_error |
message | Human-readable description |
code | Machine-readable sub-code (optional). Examples: course_not_found, preset_not_found, idempotency_key_required, retry_after_60, regen_cap_reached |
param | Field name if the error is parameter-specific (optional) |
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.
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.