Skip to main content

Native API walkthrough

Worked curl examples for the native Immerle extensions (/api/v1/*). Each section here pairs with an operator-facing page that explains why you'd use the feature: On-demand catalog, Social features, Playlist import and Federation. For exact request/response schemas, see the API reference (generated from the same handlers as these examples).

All examples below use Authorization: Bearer <token> — either a device JWT (from POST /auth/sessions) or a personal API token (gsk_…). Admin-only endpoints need a token belonging to an admin account.

Sessions & tokens

# log in → a device JWT (store it; send as Authorization: Bearer)
curl -X POST "http://host:4533/api/v1/auth/sessions" -H 'Content-Type: application/json' \
-d '{"username":"me","password":"pw","device":"MacBook"}'
# → { "token": "eyJ…", "device": { "id": "<jti>", ... } }

# see / revoke your device sessions
curl -H "Authorization: Bearer eyJ…" "http://host:4533/api/v1/devices"
curl -X DELETE -H "Authorization: Bearer eyJ…" "http://host:4533/api/v1/devices/<jti>"

# mint a personal API token (secret returned ONCE — store it now)
curl -X POST "http://host:4533/api/v1/tokens" -H "Authorization: Bearer eyJ…" \
-H 'Content-Type: application/json' -d '{"name":"my-cli"}'
# → { "token": "gsk_…", "id": "…", "name": "my-cli", "prefix": "gsk_…" }

# use it — as a Bearer header or ?apiKey, on BOTH the Subsonic and native APIs
curl -H "Authorization: Bearer gsk_…" "http://host:4533/rest/getArtists?c=app&f=json"
curl "http://host:4533/rest/getArtists?c=app&f=json&apiKey=gsk_…"

curl -H "Authorization: Bearer gsk_…" "http://host:4533/api/v1/tokens"
curl -X DELETE -H "Authorization: Bearer gsk_…" "http://host:4533/api/v1/tokens/<tokenId>"

Runtime settings (admin)

curl "http://host:4533/api/v1/admin/settings" -H 'Authorization: Bearer <admin>'

# hot: tune provider behaviour (applies now)
curl -X POST "http://host:4533/api/v1/admin/settings" -H 'Authorization: Bearer <admin>' \
-H 'Content-Type: application/json' \
-d '{"providers":{"autoDownloadOnPlay":true,"searchTimeoutSeconds":8}}'

# restart-required: toggling the scan watcher → response has restartRequired:true
curl -X POST "http://host:4533/api/v1/admin/settings" -H 'Authorization: Bearer <admin>' \
-H 'Content-Type: application/json' -d '{"scan":{"watch":false}}'

POST is a partial update — only fields present in the body change. When a change needs a restart, the response sets restartRequired: true and lists the affected fields in pendingRestart.

On-demand providers

# list all providers with enabled/active/builtin/deletable/sortOrder/version
curl "http://host:4533/api/v1/admin/providers" -H 'Authorization: Bearer <admin>'

# fill in a built-in's credentials and enable it
curl -X POST "http://host:4533/api/v1/admin/providers" -H 'Authorization: Bearer <admin>' \
-H 'Content-Type: application/json' \
-d '{"name":"jamendo","config":"{\"params\":{\"client_id\":\"<JAMENDO_TOKEN>\",\"audioformat\":\"mp32\"}}"}'
curl -X PUT "http://host:4533/api/v1/admin/providers/jamendo/enabled" -H 'Authorization: Bearer <admin>' \
-H 'Content-Type: application/json' -d '{"enabled":true}'

# add a dynamic HTTP provider by URL — name + config skeleton come from its /capabilities
curl -X POST "http://host:4533/api/v1/admin/providers" -H 'Authorization: Bearer <admin>' \
-H 'Content-Type: application/json' -d '{"endpoint":"https://my-service.internal"}'

curl -X PUT "http://host:4533/api/v1/admin/providers/order" -H 'Authorization: Bearer <admin>' \
-H 'Content-Type: application/json' -d '{"order":["free-music-archive","internet-archive","jamendo"]}'
curl -X DELETE "http://host:4533/api/v1/admin/providers/my-service" -H 'Authorization: Bearer <admin>'

# unused-download cleanup sweep
curl "http://host:4533/api/v1/admin/cleanup" -H 'Authorization: Bearer <admin>'
curl -X POST "http://host:4533/api/v1/admin/cleanup" -H 'Authorization: Bearer <admin>' \
-H 'Content-Type: application/json' -d '{"enabled":true}'
curl -X POST "http://host:4533/api/v1/admin/cleanup/run" -H 'Authorization: Bearer <admin>'

See Building a custom content provider for the exact /capabilities//search//resolve//download contract a dynamic provider must implement.

Activity & profiles

# someone's profile (activity, public playlists, isSelf) — omit username for your own
curl "http://host:4533/api/v1/profile?username=alex" -H 'Authorization: Bearer <token>'

# your own editable account (email, display name) — never exposed on public profiles
curl "http://host:4533/api/v1/account" -H 'Authorization: Bearer <token>'
curl -X POST "http://host:4533/api/v1/account" -H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' -d '{"displayName":"Kilian"}'

Playlists: public, collaborative, shared

curl "http://host:4533/api/v1/playlists/public" -H 'Authorization: Bearer <token>'
curl -X PUT "http://host:4533/api/v1/playlists/<id>/subscription" -H 'Authorization: Bearer <token>'
curl -X DELETE "http://host:4533/api/v1/playlists/<id>/subscription" -H 'Authorization: Bearer <token>'
curl -X POST "http://host:4533/api/v1/playlists/<id>/collaborators" -H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' -d '{"username":"alex"}'

# public, unauthenticated share link (expiresAt is epoch millis, optional)
curl -X POST "http://host:4533/api/v1/shares" -H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{"itemId":"<id>","description":"check this out","expiresAt":1780000000000}'
curl -X PATCH "http://host:4533/api/v1/shares/<id>" -H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' -d '{"description":"updated"}'
curl -X DELETE "http://host:4533/api/v1/shares/<id>" -H 'Authorization: Bearer <token>'

Jam sessions

# host creates a session with a starting queue
curl -X POST "http://host:4533/api/v1/jam" -H 'Authorization: Bearer <host-token>' \
-H 'Content-Type: application/json' -d '{"name":"Friday night","trackIds":["t1","t2","t3"]}'

# anyone joins/leaves freely
curl -X POST "http://host:4533/api/v1/jam/<id>/participants" -H 'Authorization: Bearer <token>'
curl -X DELETE "http://host:4533/api/v1/jam/<id>/participants/me" -H 'Authorization: Bearer <token>'

# live state over SSE — a `state` event immediately, then on every change, heartbeat every 20s
curl -N "http://host:4533/api/v1/jam/<id>/events" -H 'Authorization: Bearer <token>'

# host-only: control playback / end the session
curl -X PATCH "http://host:4533/api/v1/jam/<id>" -H 'Authorization: Bearer <host-token>' \
-H 'Content-Type: application/json' -d '{"currentTrackId":"t2","position":45.2,"state":"playing"}'
curl -X DELETE "http://host:4533/api/v1/jam/<id>" -H 'Authorization: Bearer <host-token>'

Only the host can change playback state or end the session (403 otherwise).

Playlist import

curl "http://host:4533/api/v1/imports/sources" -H 'Authorization: Bearer <token>'

curl -X POST "http://host:4533/api/v1/imports" -H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{"source":"deezer","ref":"https://www.deezer.com/playlist/1234567890"}'
# → { "id": "<importId>", "status": "queued", ... }

curl "http://host:4533/api/v1/imports/<importId>" -H 'Authorization: Bearer <token>' # progress + items[]
curl "http://host:4533/api/v1/imports" -H 'Authorization: Bearer <token>' # list, no items

# resolve a doubtful/missing/failed item — no body validates the flagged candidate as-is
curl -X POST "http://host:4533/api/v1/imports/<importId>/items/<itemId>/resolve" -H 'Authorization: Bearer <token>'
# with a body, re-searches and uses the best match instead
curl -X POST "http://host:4533/api/v1/imports/<importId>/items/<itemId>/resolve" \
-H 'Authorization: Bearer <token>' -H 'Content-Type: application/json' \
-d '{"query":"Radiohead Karma Police"}'

Federation (admin)

# 1. point at your hub account
curl -X POST "http://host:4533/api/v1/admin/settings" -H 'Authorization: Bearer <admin>' \
-H 'Content-Type: application/json' -d '{"federation":{"userId":"<your-hub-user-uuid>"}}'

# 2. link — the server does the whole exchange with the hub and persists the issued identity
curl -X POST "http://host:4533/api/v1/admin/federation/register" -H 'Authorization: Bearer <admin>'

curl "http://host:4533/api/v1/admin/federation" -H 'Authorization: Bearer <admin>' # profile
curl -X PATCH "http://host:4533/api/v1/admin/federation" -H 'Authorization: Bearer <admin>' \
-H 'Content-Type: application/json' -d '{"name":"My instance","sqid":"my-instance"}'
curl -X DELETE "http://host:4533/api/v1/admin/federation" -H 'Authorization: Bearer <admin>' # unlink

curl "http://host:4533/api/v1/admin/federation/instances?query=" -H 'Authorization: Bearer <admin>'
curl "http://host:4533/api/v1/admin/federation/subscriptions" -H 'Authorization: Bearer <admin>'
curl -X POST "http://host:4533/api/v1/admin/federation/subscriptions" -H 'Authorization: Bearer <admin>' \
-H 'Content-Type: application/json' -d '{"instanceId":"..."}'
curl -X DELETE "http://host:4533/api/v1/admin/federation/subscriptions/<id>" -H 'Authorization: Bearer <admin>'

Toggle syncPlaylists/exportScrobbles the same way as any other runtime setting, under {"federation": {...}} — all hot, no restart.

Concert discovery (admin)

See Configuration for what each field means and which sources cover which countries.

curl "http://host:4533/api/v1/admin/concerts" -H 'Authorization: Bearer <admin>'
curl -X PUT "http://host:4533/api/v1/admin/concerts" -H 'Authorization: Bearer <admin>' \
-H 'Content-Type: application/json' \
-d '{"enabled":true,"country":"FR","ticketmasterApiKey":"...","skiddleApiKey":"..."}'

# force an immediate sync instead of waiting for the daily one
curl -X POST "http://host:4533/api/v1/admin/concerts/sync" -H 'Authorization: Bearer <admin>'

API keys are write-only — GET /admin/concerts reports ticketmasterConfigured/ skiddleConfigured booleans, never the keys themselves.

# a user's own upcoming, non-dismissed matches, soonest first
curl "http://host:4533/api/v1/me/concerts" -H 'Authorization: Bearer <token>'
curl -X PUT "http://host:4533/api/v1/me/concerts/<id>/dismiss" -H 'Authorization: Bearer <token>'

Bandcamp purchase import

See Purchase import for how a user gets their session cookie. The cookie is write-only — nothing ever echoes it back.

curl -X POST "http://host:4533/api/v1/me/purchases/bandcamp/connect" -H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' -d '{"cookie":"<identity cookie value>"}'

curl "http://host:4533/api/v1/me/purchases/bandcamp" -H 'Authorization: Bearer <token>' # connection status
curl "http://host:4533/api/v1/me/purchases/bandcamp/collection" -H 'Authorization: Bearer <token>' # live purchase list

curl -X POST "http://host:4533/api/v1/me/purchases/bandcamp/items/p/123456789/import" \
-H 'Authorization: Bearer <token>' -H 'Content-Type: application/json' \
-d '{"itemType":"album","artistName":"Pinkfong","itemTitle":"Baby Shark"}'
# → { "id": "<jobId>", "status": "queued", ... }

curl "http://host:4533/api/v1/me/purchases/bandcamp/jobs" -H 'Authorization: Bearer <token>' # job statuses

curl -X DELETE "http://host:4533/api/v1/me/purchases/bandcamp" -H 'Authorization: Bearer <token>' # disconnect

UI theme

Each account stores its own theme (currently just an accent colour), applied client-side and persisted server-side so it follows the user across devices.

curl "http://host:4533/api/v1/theme" -H 'Authorization: Bearer <token>'
# → { "theme": { "accentColor": "#3b82f6" } }

curl -X POST "http://host:4533/api/v1/theme" -H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' -d '{"accentColor":"#3b82f6"}'

POST is a partial update — an empty accentColor clears it. Invalid colours (anything that isn't a CSS hex #RGB/#RRGGBB/#RRGGBBAA) are rejected with 400.