Base URL and versioning
All requests go to a versioned base path:v1) is in the path. We add fields and endpoints without changing the version. A breaking change would ship under a new version path, so your integration keeps working.
Deprecation and sunset
Nothing inv1 is retiring today. When something does, this is how you hear about it, at least 90 days before it stops:
- REST: every answer of a retiring route carries
Deprecation: @<unix seconds>(when it was marked, RFC 9745),Sunset: <HTTP date>(when it stops, RFC 8594) andLink: <this section>; rel="deprecation", and the route is markeddeprecatedin/v1/openapi.jsonwith what replaces it in its description. - GraphQL: a retiring field or argument is marked
@deprecated(reason: "…")in the schema, with what replaces it; introspection shows it. - This page lists what is retiring and when.
410 with code GONE after its sunset date. A whole version (/v1) would get the same notice, a year rather than 90 days.
Requests and responses
Requests and responses are JSON (Content-Type: application/json). Send a JSON body on POST, PATCH and PUT, and query parameters on GET. The Scenes API also takes code as the body (text/jsx, text/tsx, text/typescript) and answers pictures as image/png.
A single-resource response returns the resource itself:
Workspaces
Everything you create belongs to a workspace. Name the workspace in theX-Workspace-Id header:
- With an API key, the header is required. A key carries no workspace, so a request without it answers
400 WORKSPACE_REQUIRED. - With an OAuth token it defaults to the workspace the token was issued for.
- If you aren’t a current member of the workspace, the answer is
403 WORKSPACE_FORBIDDEN. It never falls back to another workspace.
GET /v1/workspaces lists your workspaces, with your role in each. It’s the one route that doesn’t need the header.
Pagination
List endpoints are cursor-paginated. Passlimit to set the page size and cursor to get the next page:
data and paging details in page:
nextCursor back as cursor for the next page. When hasMore is false, nextCursor is null and you’ve reached the end.
Idempotent retries
Send anIdempotency-Key header (any unique string, for example a UUID) on POST and PATCH requests. If you repeat a request with the same key within 24 hours, you get the original response again, with Idempotency-Replayed: true, and nothing runs twice. Server errors (5xx) aren’t stored, so a retry after one runs again.
A key belongs to one request. If you reuse a key with a different body, the API refuses it with 422 IDEMPOTENCY_KEY_REUSED and doesn’t run the request. Use a new key for each new request, and the same key only to retry.
Rate limits
Each caller can make 600 requests a minute. Every response carries the current state:
Over the limit, the answer is
429 with code RATE_LIMITED and a Retry-After header in seconds.
The GraphQL endpoint draws on the same allowance for a call with an API key or a bearer token, with the same headers; its 429 is a GraphQL errors entry with extensions.code RATE_LIMITED.
Long-running requests
A request whose work goes on after the answer returns202 Accepted: the body has the resource (its id), and the Location header is where to poll, GET /v1/uploads/{id} for an import from a link. When the answer is a list, Location is the first item’s and the body has every id. Poll until the resource says it is done (uploaded true, or externalSource.state.status of COMPLETED or FAILED).
Concurrency
Some edits take the version you read, so you never overwrite a newer change:- Playlists: pass
basedOnVersiontoPOST /playlists/{id}/operations. If the playlist moved on, the batch is refused with409 CONFLICT. - Scenes: send the resource’s
ETagasIf-Match. If it changed, the answer is412 PRECONDITION_FAILED. See Scenes API.
Errors
Successful requests return2xx. Errors return the matching HTTP status and an RFC 9457 problem document (the successor of RFC 7807, same shape) (Content-Type: application/problem+json):
Some problems carry more fields, such as a
reason, or errors and currentRevision in the Scenes API.
Codes and statuses
Both APIs use the same split:
400 BAD_REQUEST only when the request can’t be read at all (a body that isn’t JSON), 422 INVALID_INPUT for one that can be read but doesn’t fit, with detail naming the field. An unknown query parameter is 422 INVALID_INPUT too.
A code not in this table follows its suffix: …_NOT_FOUND is 404, …_FORBIDDEN is 403, …_UNAVAILABLE is 503, and anything else is 422. The Scenes API has its own codes as well. See Scenes API errors.
Batches
POST /v1/batch runs up to 20 REST operations in order, in one request. Results keep the input order. A failed operation doesn’t stop the ones after it, and the batch isn’t a transaction.
Service status
GET /v1/status is an unauthenticated health check. GET /v1/openapi.json returns the REST API’s OpenAPI document, and GET /v1/scenes/openapi.json the Scenes API’s.