Pagination, errors and limits

Pagination

Every list endpoint takes limit (1–100, default 50; sessions allow up to 200) and offset (default 0) and returns:

"pagination": { "total": 128, "limit": 50, "offset": 0, "has_more": true }

Page with offset += limit while has_more is true. total is the count across all pages for the filters you sent. Lists have a fixed order (classes by name, sessions by date, seasons newest first), so pages are stable between calls unless the studio changes something in between.

Errors

Every non-2xx response is:

{ "error": { "code": "invalid_request", "message": "limit must be between 1 and 100" } }

Branch on code. message is for humans and logs and may change.

StatuscodeWhen
400invalid_requestA parameter is missing, malformed or not allowed (limit=0, status=draft, category_id=abc)
401unauthorizedNo X-API-Key, unknown key, or revoked key
403forbiddenThe key lacks the scope this endpoint needs
404not_foundNo such record for this studio (another studio's id is a 404, not a 403), or no such endpoint
405method_not_allowedAnything other than GET or OPTIONS. The API is read-only
429rate_limitedToo many requests from your IP. Wait and retry
500internal_errorOur fault. Retry with backoff; tell us if it persists

Every response under /api/v1/, including unknown paths and write attempts, is JSON in this envelope, so await res.json() is always safe.

Unknown query parameters are ignored. Known parameters with bad values are rejected, never silently defaulted, so a typo shows up in development rather than as wrong data in production.

Null means blank

Optional text (description, address, and so on) is null when the studio left it blank, never an empty string. Optional objects (category, location, instructor) are null when unset, never {}.

Rate limit

300 requests per minute per IP address, across all endpoints. Over that, requests get 429 until the minute rolls over; there is no Retry-After header, so wait a minute. For a nightly sync or a schedule page this is far more than enough. If you are polling, poll slowly: the catalog changes a few times a week, not a few times a minute.

Caching

The API never caches between callers. Every response carries Cache-Control: private, no-store, so a CDN or shared proxy will not serve one key's data to another. Cache on your side if you call often: a few minutes is plenty for a catalog.

Versioning

The path carries the version (/api/v1/). Within v1 we add fields and endpoints but never remove or rename them, change a type, or change a status code. A breaking change would ship as /api/v2/ with v1 kept running. New fields can appear at any time, so parse what you need and ignore the rest.