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:
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.
| Parameter | In | Description |
|---|---|---|
limitinteger | query | Requests per page, 1–100. Defaults to 50. |
cursorstring | query | The 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.
| Parameter | In | Description |
|---|---|---|
requestIdstring, required | path | The id of an upcoming request from the queue. Not the current one. |
Idempotency-Keystring, required | header | A 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.
| Parameter | In | Description |
|---|---|---|
Idempotency-Keystring, required | header | A 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.
| Parameter | In | Description |
|---|---|---|
enabledboolean, required | body | true 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
doneAdvance 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