Ovatio
Open Ovatio

External API

Connect trusted tools using API keys, queue reads, and playback controls.

Create an API key

Open Configuration > Integrations on your own channel. Give the key a name, select its permissions and expiry, then copy the secret. It is shown once. Store it in a trusted script, desktop tool, or server. Keep keys out of public overlays, repositories, URLs, and chat.

Keys are tied to your account, regardless of which channel you select in the dashboard. During an active shared stream, queue and playback operations use your active shared queue. Request availability remains a setting on your own channel. Moderator delegation is not supported by this API.

If creation fails because Clerk API keys are unavailable, the application operator must enable User API keys in the Clerk instance. A key can be revoked from Integrations. Revocation blocks subsequent requests; it does not cancel work already in progress.

You can create and revoke keys right here too:

Live

Authenticate

Use /api/v1 on the same Ovatio domain you use for the app. Integrations displays the full branded URL. The app forwards requests to its configured backend.

Send the key in the Authorization header. The examples below use this site’s domain directly. Supply your key securely as OVATIO_API_KEY in your process environment. Code samples are available in cURL, JavaScript (Node.js 18 or later, or Deno), Python (with requests) and Go; pick a language once and every sample on the page follows.

/me returns the key ID, channel identifier, and granted scopes. Download the generated API specification from /api/v1/openapi.json on this Ovatio domain. It describes every supported route, input, response, and required scope.

Permissions

  • queue:read: read queue pages and now playing.
  • queue:write: move an upcoming request to the front or advance playback.
  • requests:read: read request availability.
  • requests:write: open or close requests.

A write permission does not automatically include read access. Keys cannot create or manage other keys. Missing permissions return HTTP 403.

Read queue and playback

Queue responses contain items and a nullable cursor. Each item contains its request id, public song metadata (id, name, artist), requester display name, and request time. The first item on the first page is the current song. Playback returns that request or null.

Pass a returned cursor with --data-urlencode and --get to fetch another page. Pages contain at most 100 requests, and multiple pages are not a snapshot when the queue changes. Private notes, lyrics, and account details are omitted.

Control playback

This archives the current request as played and advances to the next song, just like the dashboard. It returns the next request or null when the queue is empty. To move an upcoming request to the front, use POST /queue/<requestId>/play. That operation reorders the queue; it does not archive the previous current request. The current request itself cannot be selected with this operation.

Playback commands have no request body. Each command requires a new Idempotency-Key of 1–128 printable ASCII characters without spaces. If a response is lost, retry with the same key and arguments: the operation is not repeated for 24 hours. Reusing the value for a different command returns HTTP 409. After 24 hours, the value may execute again, so do not retry stale commands.

Open or close requests

Use GET /requests/settings to read availability. Setting it repeatedly to the same value is safe. Only the enabled boolean is accepted in the body.

Errors and limits

Errors contain error.code, error.message, and a requestId for support. HTTP 400 indicates invalid input, 401 an invalid/expired/revoked key, 403 missing access, 404 an unavailable resource, and 409 a conflicting command. HTTP 503 means the key service could not be reached or configured; retry later.

Limits are 60 requests per minute per key and 300 per minute per owner, with a shared admission limit before key verification. HTTP 429 includes a Retry-After header in seconds. Bodies are limited to 4096 bytes. Clerk verifies every key on each request, so avoid rapid polling. This release has no live event stream or outgoing webhooks.

The API is intended for trusted tools. It does not enable cross-origin browser access. Keep API keys in your tool's backend rather than exposing them in browser JavaScript.

Endpoint reference

Every endpoint with its parameters, code in your language, and a sample response. Command examples generate a fresh Idempotency-Key for each run.

API base URL: https://ovatio-evlot7ltx-iterno.vercel.app/api/v1 · Download the OpenAPI specification

GET/api/v1/me

Test the connection and identify the owner channel. Any key.

curl \
  "https://ovatio-evlot7ltx-iterno.vercel.app/api/v1/me" \
  -H "Authorization: Bearer $OVATIO_API_KEY"
Sample response
{
  "keyId": "ak_31Xq8mZ2",
  "channelId": "user_2xK9pLq",
  "scopes": [
    "queue:read",
    "queue:write",
    "requests:read",
    "requests:write"
  ]
}

GET/api/v1/queue

Read an ordered queue page. The first request is now playing. Pages are not a snapshot. Requires queue:read.

ParameterInDescription
limit
integer
queryRequests per page, 1–100. Defaults to 50.
cursor
string
queryThe cursor from the previous page. Omit for the first page.
curl \
  "https://ovatio-evlot7ltx-iterno.vercel.app/api/v1/queue?limit=50" \
  -H "Authorization: Bearer $OVATIO_API_KEY"
Sample response
{
  "items": [
    {
      "id": "k57f2rbe9c3qvh8d0s9j2p4n6x7c",
      "song": {
        "id": "j97f2rbe9c3qtq1z5m8w3e0b",
        "name": "Wonderwall",
        "artist": "Oasis"
      },
      "requesterName": "mira_plays",
      "requestedAt": 1760464800000
    },
    {
      "id": "k57a0x1dn4htvh8d0s9j2p4n6x7c",
      "song": {
        "id": "j97a0x1dn4httq1z5m8w3e0b",
        "name": "Mr. Brightside",
        "artist": "The Killers"
      },
      "requesterName": "tobi",
      "requestedAt": 1760464860000
    }
  ],
  "cursor": "eyJvZmZzZXQiOjJ9"
}

GET/api/v1/playback

Read the first request in the queue, or null. Requires queue:read.

curl \
  "https://ovatio-evlot7ltx-iterno.vercel.app/api/v1/playback" \
  -H "Authorization: Bearer $OVATIO_API_KEY"
Sample response
{
  "id": "k57f2rbe9c3qvh8d0s9j2p4n6x7c",
  "song": {
    "id": "j97f2rbe9c3qtq1z5m8w3e0b",
    "name": "Wonderwall",
    "artist": "Oasis"
  },
  "requesterName": "mira_plays",
  "requestedAt": 1760464800000
}

POST/api/v1/queue/:id/play

Move an upcoming request to the front. Does not archive the previous song. Requires Idempotency-Key. Requires queue:write.

ParameterInDescription
requestId
string, required
pathThe id of an upcoming request from the queue. Not the current one.
Idempotency-Key
string, required
headerA new value for each command, 1–128 printable characters. Reuse it only to retry the same command.
curl \
  -X POST \
  "https://ovatio-evlot7ltx-iterno.vercel.app/api/v1/queue/k579mz6q2wdevh8d0s9j2p4n6x7c/play" \
  -H "Authorization: Bearer $OVATIO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
Sample response
{
  "id": "k579mz6q2wdevh8d0s9j2p4n6x7c",
  "song": {
    "id": "j979mz6q2wdetq1z5m8w3e0b",
    "name": "Dreams",
    "artist": "Fleetwood Mac"
  },
  "requesterName": null,
  "requestedAt": 1760464920000
}

POST/api/v1/playback/next

Archive the current request and advance to the next song. Requires Idempotency-Key. Requires queue:write.

ParameterInDescription
Idempotency-Key
string, required
headerA new value for each command, 1–128 printable characters. Reuse it only to retry the same command.
curl \
  -X POST \
  "https://ovatio-evlot7ltx-iterno.vercel.app/api/v1/playback/next" \
  -H "Authorization: Bearer $OVATIO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
Sample response
{
  "id": "k57a0x1dn4htvh8d0s9j2p4n6x7c",
  "song": {
    "id": "j97a0x1dn4httq1z5m8w3e0b",
    "name": "Mr. Brightside",
    "artist": "The Killers"
  },
  "requesterName": "tobi",
  "requestedAt": 1760464860000
}

GET/api/v1/requests/settings

Read owner-channel request availability. Requires requests:read.

curl \
  "https://ovatio-evlot7ltx-iterno.vercel.app/api/v1/requests/settings" \
  -H "Authorization: Bearer $OVATIO_API_KEY"
Sample response
{
  "enabled": true
}

PATCH/api/v1/requests/settings

Set owner-channel request availability. Requires requests:write.

ParameterInDescription
enabled
boolean, required
bodytrue opens requests, false closes them.
curl \
  -X PATCH \
  "https://ovatio-evlot7ltx-iterno.vercel.app/api/v1/requests/settings" \
  -H "Authorization: Bearer $OVATIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"enabled":false}'
Sample response
{
  "enabled": false
}

Recipes

Complete programs for common jobs: reading the whole queue page by page, and advancing playback with retries that reuse the same Idempotency-Key and honour Retry-After.

Read the whole queue

Follow cursor until it is null. The first request is now playing. The cURL version needs jq.

cursor=""
while :; do
  args=(--data-urlencode "limit=100")
  [ -n "$cursor" ] && args+=(--data-urlencode "cursor=$cursor")
  page=$(curl -sf --get "https://ovatio-evlot7ltx-iterno.vercel.app/api/v1/queue" \
    -H "Authorization: Bearer $OVATIO_API_KEY" "${args[@]}") || exit 1
  echo "$page" | jq -r '.items[] | "\(.song.artist) – \(.song.name)"'
  cursor=$(echo "$page" | jq -r '.cursor // empty')
  [ -z "$cursor" ] && break
done

Advance playback with safe retries

Create one Idempotency-Key per command and reuse it on every retry, so a lost response never skips two songs. Waits for Retry-After on HTTP 429 and backs off on 5xx; other errors stop.

key=$(uuidgen) # One key per command, reused on every retry.
for attempt in 1 2 3 4 5; do
  rm -f headers.txt
  status=$(curl -s -o response.json -D headers.txt -w "%{http_code}" \
    -X POST "https://ovatio-evlot7ltx-iterno.vercel.app/api/v1/playback/next" \
    -H "Authorization: Bearer $OVATIO_API_KEY" \
    -H "Idempotency-Key: $key")
  if [ "$status" = 200 ]; then cat response.json; exit 0; fi
  # Other 4xx errors won't succeed on retry.
  if [ "$status" != 000 ] && [ "$status" != 429 ] && [ "$status" -lt 500 ]; then
    cat response.json >&2; exit 1
  fi
  wait=$(grep -i '^retry-after:' headers.txt 2>/dev/null | tr -dc '0-9')
  sleep "${wait:-$((2 ** attempt))}"
done
echo "Gave up advancing playback." >&2; exit 1