YTMDesktop2
API

Routes

Local HTTP and WebSocket API reference

Base URL (default): http://127.0.0.1:13091

When Require authorization is on, protected routes need:

Authorization: Bearer <token>

(Token alone without Bearer is also accepted.)

Auth endpoints (/auth/*) never require a token. GET / is always public.

Quick reference

MethodPathAuthDescription
GET/NoAPI info + route list
POST/auth/requestcodeNoStart pairing — get a code
POST/auth/requestNoExchange code for token (blocks until approved)
GET/trackYesCurrent track payload
GET/track/stateYesPlayback state
POST/track/playYesPlay
POST/track/pauseYesPause
POST/track/toggle-play-stateYesToggle play/pause
POST/track/nextYesNext track
POST/track/prevYesPrevious track
POST/track/seekYesSeek to absolute time
POST/track/forwardYesSeek forward by seconds
POST/track/backwardYesSeek backward by seconds
POST/track/repeatYesCycle repeat mode
POST/track/shuffleYesToggle shuffle
POST/track/likeYesSet like
POST/track/dislikeYesSet dislike
POST/track/volumeYesSet / read volume
POST/track/volume-upYesRaise volume
POST/track/volume-downYesLower volume
POST/track/accentYesDominant color from artwork
GET/socketYes*WebSocket track events
GET/socket/pingNoWebSocket ping/pong

* Auth via Authorization header or ?token= query when required.


Info

GET /

Returns server metadata and registered internal route keys.

curl http://127.0.0.1:13091/
{
  "name": "YTMDesktop2 Api",
  "beta": false,
  "player": { "...": "player settings object" },
  "authRequired": true,
  "routes": [
    "api/routes",
    "api/track",
    "api/track/state",
    "api/track/next",
    "api/auth/requestcode",
    "api/auth/request"
  ]
}

Authentication

See also Authentication settings.

POST /auth/requestcode

Create a pairing code for a client. appId must be 2–32 lowercase alphanumeric characters.

curl -X POST http://127.0.0.1:13091/auth/requestcode \
  -H "Content-Type: application/json" \
  -d '{"appId":"myapp","appName":"My App","appVersion":"1.0.0"}'
{ "code": "AB12CD" }

Error (400):

{ "error": "appId must be 2-32 lowercase alphanumeric characters" }

POST /auth/request

Exchange the code for a token. Blocks until the user approves (or denies / times out) in Settings → Authentication.

curl -X POST http://127.0.0.1:13091/auth/request \
  -H "Content-Type: application/json" \
  -d '{"appId":"myapp","code":"AB12CD"}'
{ "token": "eyJ…" }

Error (400):

{ "error": "Invalid or expired auth code" }
{ "error": "Authorization timed out — user did not approve in time" }

Use the token on later requests:

curl http://127.0.0.1:13091/track \
  -H "Authorization: Bearer eyJ…"

Unauthorized (401):

{ "error": "unauthorized" }

Track — read

GET /track

Current track, or null if nothing is loaded.

curl http://127.0.0.1:13091/track \
  -H "Authorization: Bearer <token>"
{
  "id": "dQw4w9WgXcQ",
  "video": {
    "videoId": "dQw4w9WgXcQ",
    "title": "Never Gonna Give You Up",
    "lengthSeconds": "213",
    "channelId": "UCuAXFkgsw1L7xaCfnd5JJOw",
    "author": "Rick Astley",
    "thumbnail": {
      "thumbnails": [
        { "url": "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg", "width": 480, "height": 360 }
      ]
    },
    "musicVideoType": "MUSIC_VIDEO_TYPE_ATV"
  },
  "context": {
    "title": "Never Gonna Give You Up",
    "urlCanonical": "https://music.youtube.com/watch?v=dQw4w9WgXcQ",
    "videoDetails": {
      "externalVideoId": "dQw4w9WgXcQ",
      "durationSeconds": "213"
    },
    "thumbnail": { "thumbnails": [] }
  },
  "meta": {
    "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg",
    "isAudioExclusive": true,
    "startedAt": 1710000000.123,
    "duration": 213,
    "isAlbum": true
  },
  "music": { "album": "Whenever You Need Somebody" }
}

GET /track/state

Playback / progress snapshot.

curl http://127.0.0.1:13091/track/state \
  -H "Authorization: Bearer <token>"
{
  "id": "dQw4w9WgXcQ",
  "playing": true,
  "progress": 42.5,
  "uiProgress": 42.5,
  "duration": 213,
  "liked": false,
  "disliked": false,
  "startedAt": 1710000000.123,
  "percentage": 19.95,
  "eventType": "progress",
  "accent": "#c41e3a"
}

null when no active track state exists yet.


Track — controls

All control routes: POST, JSON body optional unless noted. Auth required when enabled.

Shared playback control response:

{ "isPlaying": true, "time": 12.4 }

POST /track/play

curl -X POST http://127.0.0.1:13091/track/play \
  -H "Authorization: Bearer <token>"

POST /track/pause

curl -X POST http://127.0.0.1:13091/track/pause \
  -H "Authorization: Bearer <token>"

POST /track/toggle-play-state

curl -X POST http://127.0.0.1:13091/track/toggle-play-state \
  -H "Authorization: Bearer <token>"

POST /track/next / POST /track/prev

curl -X POST http://127.0.0.1:13091/track/next \
  -H "Authorization: Bearer <token>"

POST /track/repeat / POST /track/shuffle

Cycles / toggles YouTube Music UI controls. Same { isPlaying, time } shape.

POST /track/seek

Absolute seek. Body:

{ "time": 90, "type": "seek" }

time is required (seconds). type optional ("seek").

curl -X POST http://127.0.0.1:13091/track/seek \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"time":90}'

POST /track/forward / POST /track/backward

Relative seek. Body:

{ "time": 10 }

time must be a non-zero number (seconds). Forward adds; backward subtracts.

curl -X POST http://127.0.0.1:13091/track/forward \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"time":15}'

POST /track/like / POST /track/dislike

Body is a JSON boolean (true / false).

curl -X POST http://127.0.0.1:13091/track/like \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d 'true'

Response: true / false / null (current like/dislike result from the player).

Volume

POST /track/volume

{ "volume": 50 }
{ "volume": 50 }

Omit volume to read/update via player defaults.

POST /track/volume-up / POST /track/volume-down

{ "amount": 5 }
{ "volume": 55 }

POST /track/accent

Extracts a dominant color from the current track artwork (hex string or null).

curl -X POST http://127.0.0.1:13091/track/accent \
  -H "Authorization: Bearer <token>"
"#c41e3a"

WebSocket

GET /socket

Upgrade to WebSocket. When auth is required, pass Authorization: Bearer <token> or ?token=<token>.

On connect, the server pushes the current track (or "null"). Later broadcasts use:

{
  "event": "track:change",
  "data": [{ "video": { "videoId": "…" }, "meta": { "…" }, "…" }]
}

Unauthorized sockets close with code 1008.

GET /socket/ping

Simple ping socket — any message gets Pong! (no auth).


Errors

StatusBodyWhen
401{ "error": "unauthorized" }Missing/invalid token on protected route
400{ "error": "…" }Auth validation / pairing failures
404{ "error": "not found" }Unknown path
500{ "error": "failed to do requested operation (…)" }Track command threw
500{ "error": "internal error" }Unhandled server error

CORS allows GET / POST / OPTIONS with Authorization and Content-Type from any origin (local tools / overlays).

On this page