Skip to main content

Error envelope

Every error response uses the same JSON shape:
{
  "detail": {
    "error": {
      "type": "not_found_error",
      "message": "course not found",
      "code": "course_not_found"
    }
  }
}
FieldRequiredDescription
typeYesHigh-level category. Stable across versions.
messageYesHuman-readable description. Subject to change. Do NOT pattern-match it programmatically.
codeNoMachine-readable sub-code. Use this for branching logic.
paramNoField name for parameter-specific errors

Error types

typeMeaning
authentication_errorMissing, invalid, or expired API key
invalid_request_errorMalformed body, missing required field, wrong shape
not_found_errorResource does not exist or is not owned by the caller
rate_limit_errorPer-minute rate limit or regen cap
quota_exceededMonthly generation quota exhausted
server_errorInternal error

Full code reference

HTTPtypecodeMeaning
400invalid_request_erroridempotency_key_requiredIdempotency-Key header missing on launch
400invalid_request_error(varies)Malformed request body or invalid field
401authentication_error(none)Missing or invalid API key
404not_found_errorcourse_not_foundCourse ID not found or not owned by caller
404not_found_errorpreset_not_foundPreset ID not found or not owned by caller
409invalid_request_erroralready_launched (varies)Conflict, e.g. course already launched or in wrong state for the action
429rate_limit_errorretry_after_<n>Per-minute rate limit exceeded. <n> is seconds to wait.
429rate_limit_errorregen_cap_reachedAccount artifact regen cap exhausted; note still recorded
429quota_exceededquota_exceededMonthly generation quota exhausted
500server_error(none)Internal server error; safe to retry

Handling

Always branch on type first, then code

type is stable, code is more specific. Pattern:
err = response.json()["detail"]["error"]

if err["type"] == "rate_limit_error":
    if err.get("code") == "regen_cap_reached":
        # Regen cap, do not retry, surface to user
        pass
    else:
        # Plain rate limit, honor Retry-After
        retry_after = response.headers.get("Retry-After", 60)
        time.sleep(int(retry_after))
        retry()

Never pattern-match message

The text is subject to change. Use type and code.

Retry policy

  • 429 rate_limit_error with Retry-After: wait the indicated seconds, then retry.
  • 500 server_error: exponential backoff with jitter, max 3 retries.
  • 400, 401, 404, 409, 429 quota_exceeded: do NOT retry. Surface to your caller.

Privacy: 404 instead of 403

If you request a course ID that exists but belongs to a different account, the API returns 404 not_found_error / course_not_found, not 403. This is intentional, to avoid leaking the existence of resources you do not own.