[GH-ISSUE #813] oauth authentication: Request contains an invalid argument #506

Open
opened 2026-02-27 23:01:10 +03:00 by kerem · 35 comments
Owner

Originally created by @sigma67 on GitHub (Sep 2, 2025).
Original GitHub issue: https://github.com/sigma67/ytmusicapi/issues/813

Recommended workaround: use browser based auth instead of oauth.

Discussed in https://github.com/sigma67/ytmusicapi/discussions/812

Originally posted by rawinkler September 1, 2025

  • I confirm that I have read the FAQ

Bug description

Since last Friday, August 29th 2025, suddenly my code stopped working. I use ytmusicapi version 1.11.0.
I get the error:

File "/usr/local/lib/python3.13/site-packages/ytmusicapi/mixins/search.py", line 182, in search
response = self._send_request(endpoint, body)
File "/usr/local/lib/python3.13/site-packages/ytmusicapi/ytmusic.py", line 241, in _send_request
raise YTMusicServerError(message + error)
ytmusicapi.exceptions.YTMusicServerError: Server returned HTTP 400: Bad Request.
Request contains an invalid argument.

To Reproduce

Minimal example:

YT_CLIENT = \
    YTMusic("/usr/src/app/oauth.json", 
            oauth_credentials=OAuthCredentials(
                client_id=environ["GOOGLE_YOUTUBE_API_CLIENT_ID"],
                client_secret=environ["GOOGLE_YOUTUBE_API_CLIENT_SECRET"]
            )
    )

search_results = YT_CLIENT.search('Oasis Wonderwall')

Is it possible that YouTube changed something that causes ytmusicapi to break?

Thanks for any help!

Originally created by @sigma67 on GitHub (Sep 2, 2025). Original GitHub issue: https://github.com/sigma67/ytmusicapi/issues/813 **Recommended workaround**: use browser based auth instead of oauth. ### Discussed in https://github.com/sigma67/ytmusicapi/discussions/812 <div type='discussions-op-text'> <sup>Originally posted by **rawinkler** September 1, 2025</sup> - [x] I confirm that I have read the [FAQ](https://ytmusicapi.readthedocs.io/en/stable/faq.html#why-is-ytmusicapi-returning-more-results-than-requested-with-the-limit-parameter) **Bug description** Since last Friday, August 29th 2025, suddenly my code stopped working. I use ytmusicapi version 1.11.0. I get the error: File "/usr/local/lib/python3.13/site-packages/ytmusicapi/mixins/search.py", line 182, in search response = self._send_request(endpoint, body) File "/usr/local/lib/python3.13/site-packages/ytmusicapi/ytmusic.py", line 241, in _send_request raise YTMusicServerError(message + error) ytmusicapi.exceptions.YTMusicServerError: Server returned HTTP 400: Bad Request. Request contains an invalid argument. **To Reproduce** Minimal example: ``` YT_CLIENT = \ YTMusic("/usr/src/app/oauth.json", oauth_credentials=OAuthCredentials( client_id=environ["GOOGLE_YOUTUBE_API_CLIENT_ID"], client_secret=environ["GOOGLE_YOUTUBE_API_CLIENT_SECRET"] ) ) search_results = YT_CLIENT.search('Oasis Wonderwall') ``` Is it possible that YouTube changed something that causes ytmusicapi to break? Thanks for any help! </div>
Author
Owner

@sigma67 commented on GitHub (Sep 2, 2025):

PR welcome

<!-- gh-comment-id:3244366420 --> @sigma67 commented on GitHub (Sep 2, 2025): PR welcome
Author
Owner

@Goldenfreddy0703 commented on GitHub (Sep 4, 2025):

PR welcome

Hey @sigma67 , Ok so im not entirely sure if this pr will help but I've forked your project and looked into it and i was able to test everything there is.

If you want, please feel free to take a look at this pr, Hopefully this helps but if not, please feel free to delete it.
Thanks

https://github.com/sigma67/ytmusicapi/pull/815

<!-- gh-comment-id:3251503061 --> @Goldenfreddy0703 commented on GitHub (Sep 4, 2025): > PR welcome Hey @sigma67 , Ok so im not entirely sure if this pr will help but I've forked your project and looked into it and i was able to test everything there is. If you want, please feel free to take a look at this pr, Hopefully this helps but if not, please feel free to delete it. Thanks https://github.com/sigma67/ytmusicapi/pull/815
Author
Owner

@KoljaWindeler commented on GitHub (Sep 6, 2025):

Thanks for trying @Goldenfreddy0703
The issue is certainly affecting lots of users. It also breaks the entire setup for me in Germany.

<!-- gh-comment-id:3261007026 --> @KoljaWindeler commented on GitHub (Sep 6, 2025): Thanks for trying @Goldenfreddy0703 The issue is certainly affecting lots of users. It also breaks the entire setup for me in Germany.
Author
Owner

@Goldenfreddy0703 commented on GitHub (Sep 6, 2025):

Oh hey anytime @KoljaWindeler , hey so I actually got some good news, today i decided to try again and i was actually doing some coding for a whole day and what I've been doing is I've been working on the ytmusic api and getting everything to work using the ios client. I have about a third of it completed but I need to get Playlist, podcast, and uploads done and then I probably need to test every function. So far, all the other stuff is working and getting the correct params and thanks to this documentation, it's been a huge help.

https://ytmusicapi.readthedocs.io/en/stable/index.html

Hopefully by tomorrow or the next few days, I can make a pr that will pass almost every test there is.

<!-- gh-comment-id:3261055925 --> @Goldenfreddy0703 commented on GitHub (Sep 6, 2025): Oh hey anytime @KoljaWindeler , hey so I actually got some good news, today i decided to try again and i was actually doing some coding for a whole day and what I've been doing is I've been working on the ytmusic api and getting everything to work using the ios client. I have about a third of it completed but I need to get Playlist, podcast, and uploads done and then I probably need to test every function. So far, all the other stuff is working and getting the correct params and thanks to this documentation, it's been a huge help. https://ytmusicapi.readthedocs.io/en/stable/index.html Hopefully by tomorrow or the next few days, I can make a pr that will pass almost every test there is.
Author
Owner

@king-millez commented on GitHub (Sep 9, 2025):

FWIW I just used Goldenfreddy's branch to replicate some playlists on YTM and it's working nicely. Thanks!

<!-- gh-comment-id:3269093771 --> @king-millez commented on GitHub (Sep 9, 2025): FWIW I just used Goldenfreddy's branch to replicate some playlists on YTM and it's working nicely. Thanks!
Author
Owner

@sigma67 commented on GitHub (Sep 9, 2025):

Not sure if related, but the Authorization header has changed as well apparently: https://github.com/sigma67/ytmusicapi/issues/813

<!-- gh-comment-id:3269277598 --> @sigma67 commented on GitHub (Sep 9, 2025): Not sure if related, but the Authorization header has changed as well apparently: https://github.com/sigma67/ytmusicapi/issues/813
Author
Owner

@Aetrocles commented on GitHub (Sep 26, 2025):

FWIW I just used Goldenfreddy's branch to replicate some playlists on YTM and it's working nicely. Thanks!

Any update on this? I use the API to extract my playlist metadata... Curious if it would be duplicated effort for me to try to learn the API and find a solution... A bit overwhelmed I'm a C# .net front end developer with data model and data manipulation experience.

<!-- gh-comment-id:3338196293 --> @Aetrocles commented on GitHub (Sep 26, 2025): > FWIW I just used Goldenfreddy's branch to replicate some playlists on YTM and it's working nicely. Thanks! Any update on this? I use the API to extract my playlist metadata... Curious if it would be duplicated effort for me to try to learn the API and find a solution... A bit overwhelmed I'm a C# .net front end developer with data model and data manipulation experience.
Author
Owner

@kikaDev404 commented on GitHub (Sep 27, 2025):

I am also facing the same issue.
ytmusicapi.exceptions.YTMusicServerError: Server returned HTTP 400: Bad Request.

I am trying to fetch the user library

<!-- gh-comment-id:3342041120 --> @kikaDev404 commented on GitHub (Sep 27, 2025): I am also facing the same issue. \ `ytmusicapi.exceptions.YTMusicServerError: Server returned HTTP 400: Bad Request.` I am trying to fetch the user library
Author
Owner

@sigma67 commented on GitHub (Sep 28, 2025):

Hi all, the current solution attempt by Goldenfreddy does not work reproducibly for my account.

I still get the same error noted in the issue title. Therefore we cannot merge this right now.

Other attempts at solving this problem are in no way duplicate effort as I do not consider this problem solved right now.

Furthermore we do not know if there is currently a region-specific rollout or if the problem is the same for all regions.

<!-- gh-comment-id:3342701954 --> @sigma67 commented on GitHub (Sep 28, 2025): Hi all, the current solution attempt by Goldenfreddy does not work reproducibly for my account. I still get the same error noted in the issue title. Therefore we cannot merge this right now. Other attempts at solving this problem are in no way duplicate effort as I do not consider this problem solved right now. Furthermore we do not know if there is currently a region-specific rollout or if the problem is the same for all regions.
Author
Owner

@atjacobs commented on GitHub (Sep 29, 2025):

I am also experiencing this issue.

<!-- gh-comment-id:3344754922 --> @atjacobs commented on GitHub (Sep 29, 2025): I am also experiencing this issue.
Author
Owner

@robdupre commented on GitHub (Oct 1, 2025):

Im UK based and have the same issue unauthenticated works:

ytmusic = YTMusic()
search_results = ytmusic.search('Oasis Wonderwall')

Fails:

ytmusic = YTMusic('oauth.json', oauth_credentials=OAuthCredentials(client_id=client_id, client_secret=client_secret))
search_results = ytmusic.search('Oasis Wonderwall')
ytmusicapi.exceptions.YTMusicServerError: Server returned HTTP 400: Bad Request.
Request contains an invalid argument.
<!-- gh-comment-id:3357908141 --> @robdupre commented on GitHub (Oct 1, 2025): Im UK based and have the same issue unauthenticated works: ``` ytmusic = YTMusic() search_results = ytmusic.search('Oasis Wonderwall') ``` Fails: ``` ytmusic = YTMusic('oauth.json', oauth_credentials=OAuthCredentials(client_id=client_id, client_secret=client_secret)) search_results = ytmusic.search('Oasis Wonderwall') ytmusicapi.exceptions.YTMusicServerError: Server returned HTTP 400: Bad Request. Request contains an invalid argument. ```
Author
Owner

@jorbig commented on GitHub (Oct 1, 2025):

Same problem here in The Netherlands. What is the best workaround?

<!-- gh-comment-id:3357930433 --> @jorbig commented on GitHub (Oct 1, 2025): Same problem here in The Netherlands. What is the best workaround?
Author
Owner

@sigma67 commented on GitHub (Oct 2, 2025):

There is no solution unfortunately: https://github.com/sigma67/ytmusicapi/pull/817#issuecomment-3358576031

You must use the browser based auth variant.

You can try sending a note to Google support that they should provide some way for us to use their oauth API with YouTube Music.

But so far they've only made life harder for us over the past months, so I'm not even hoping for anything at this point.

<!-- gh-comment-id:3359280790 --> @sigma67 commented on GitHub (Oct 2, 2025): There is no solution unfortunately: https://github.com/sigma67/ytmusicapi/pull/817#issuecomment-3358576031 You must use the browser based auth variant. You can try sending a note to Google support that they should provide some way for us to use their oauth API with YouTube Music. But so far they've only made life harder for us over the past months, so I'm not even hoping for anything at this point.
Author
Owner

@Goldenfreddy0703 commented on GitHub (Oct 2, 2025):

One solution I'm thinking of doing for my app is having the user connecting to a local HTTP server via localhost and having an oath of some kind to generate the cookie and all so maybe that's something I can work on tomorrow.

EDIT: Apparently, its not that simple thanks to browser security and google. ughhhhhhh.....

<!-- gh-comment-id:3359501909 --> @Goldenfreddy0703 commented on GitHub (Oct 2, 2025): One solution I'm thinking of doing for my app is having the user connecting to a local HTTP server via localhost and having an oath of some kind to generate the cookie and all so maybe that's something I can work on tomorrow. EDIT: Apparently, its not that simple thanks to browser security and google. ughhhhhhh.....
Author
Owner

@DanielWeigl commented on GitHub (Oct 2, 2025):

I have tested it a bit and noticed following - if I change the clientName/version to

YT_CLIENT.context["context"]["client"]["clientName"] = "TVHTML5"
YT_CLIENT.context["context"]["client"]["clientVersion"] = "6.20240925.00.00"

the oauth login works and we get a result, but its always the same and has no search results:

{
  "responseContext": {
    "serviceTrackingParams": [
      {
        "service": "GFEEDBACK",
        "params": [
          {
            "key": "has_unlimited_entitlement",
            "value": "True"
          },
          {
            "key": "has_premium_lite_entitlement",
            "value": "False"
          },
          {
            "key": "logged_in",
            "value": "1"
          }
        ]
      }
    ],
    "maxAgeSeconds": 7200
  },
  "estimatedResults": "4826902",
  "trackingParams": "CAAQxxxxIU0="
}

Changing it to

YT_CLIENT.context["context"]["client"]["clientName"] = "TVHTML5"
YT_CLIENT.context["context"]["client"]["clientVersion"] = "7.20240925.00.00"

also works and even returns results, but in a different format than the current lib is expecting:

search_results = YT_CLIENT.search('Oasis Wonderwall')

returns an empty array, but the underlying response from youtube has results:

grep -A5 -i oasis response.txt 
                            "simpleText": "Oasis - Wonderwall (Official Video)"
                          },
                          "lines": [
                            {
                              "lineRenderer": {
                                "items": [
--
                                        "simpleText": "Oasis"
                                      }
                                    }
                                  }
                                ]
                              }
--
                            "simpleText": "Oasis - Wonderwall (Official Video)"
                          },
                          "subtitle": {
                            "simpleText": "Oasis"
                          },
                          "menu": {
                            "menuRenderer": {
                              "items": [
                                {
--
                                        "simpleText": "Oasis"
                                      }
                                    }
                                  }
                                ]
                              }
--
                            "simpleText": "Oasis"
...

Im not sure what needs to be adapted to correctly parse this format

<!-- gh-comment-id:3359537317 --> @DanielWeigl commented on GitHub (Oct 2, 2025): I have tested it a bit and noticed following - if I change the clientName/version to ``` YT_CLIENT.context["context"]["client"]["clientName"] = "TVHTML5" YT_CLIENT.context["context"]["client"]["clientVersion"] = "6.20240925.00.00" ``` the oauth login works and we get a result, but its always the same and has no search results: ``` { "responseContext": { "serviceTrackingParams": [ { "service": "GFEEDBACK", "params": [ { "key": "has_unlimited_entitlement", "value": "True" }, { "key": "has_premium_lite_entitlement", "value": "False" }, { "key": "logged_in", "value": "1" } ] } ], "maxAgeSeconds": 7200 }, "estimatedResults": "4826902", "trackingParams": "CAAQxxxxIU0=" } ``` Changing it to ``` YT_CLIENT.context["context"]["client"]["clientName"] = "TVHTML5" YT_CLIENT.context["context"]["client"]["clientVersion"] = "7.20240925.00.00" ``` also works and even returns results, but in a different format than the current lib is expecting: ``` search_results = YT_CLIENT.search('Oasis Wonderwall') ``` returns an empty array, but the underlying response from youtube has results: ``` grep -A5 -i oasis response.txt "simpleText": "Oasis - Wonderwall (Official Video)" }, "lines": [ { "lineRenderer": { "items": [ -- "simpleText": "Oasis" } } } ] } -- "simpleText": "Oasis - Wonderwall (Official Video)" }, "subtitle": { "simpleText": "Oasis" }, "menu": { "menuRenderer": { "items": [ { -- "simpleText": "Oasis" } } } ] } -- "simpleText": "Oasis" ... ``` Im not sure what needs to be adapted to correctly parse this format
Author
Owner

@sigma67 commented on GitHub (Oct 2, 2025):

@DanielWeigl thanks for checking and confirming that the API is different. It was already noted here by @sgvictorino : https://github.com/sigma67/ytmusicapi/pull/817#issuecomment-3349552129

It seems the API is more limited than what we need so I'm not sure it's worth implementing the parsers for a limited API just for the sake of having oauth at all.

<!-- gh-comment-id:3359695835 --> @sigma67 commented on GitHub (Oct 2, 2025): @DanielWeigl thanks for checking and confirming that the API is different. It was already noted here by @sgvictorino : https://github.com/sigma67/ytmusicapi/pull/817#issuecomment-3349552129 It seems the API is more limited than what we need so I'm not sure it's worth implementing the parsers for a limited API just for the sake of having oauth at all.
Author
Owner

@Goldenfreddy0703 commented on GitHub (Oct 2, 2025):

Yeah TVHTML5 is basically the music section from the main youtube so wouldn't be so good. When IOS_MUSIC was working, i was actually working on the parser for it but than Google decided to be dumb so yeah.

<!-- gh-comment-id:3362485339 --> @Goldenfreddy0703 commented on GitHub (Oct 2, 2025): Yeah TVHTML5 is basically the music section from the main youtube so wouldn't be so good. When IOS_MUSIC was working, i was actually working on the parser for it but than Google decided to be dumb so yeah.
Author
Owner

@Aetrocles commented on GitHub (Oct 8, 2025):

I know it doesn't help with this particular API or bug but for me it was really important to get my YouTube music metadata so I can keep track of my songs and my playlists...

If you go to Google takeout you can download your YouTube data and it'll have a list of all your playlists and another file of all your library songs/videos. You can cross reference with the video ID from the two files and parse the content into a clean data structure...

You can't really automate the extraction of Google take out and I wouldn't recommend abusing it because if they remove album or artist info it would suck major... But it helps in a pinch.

<!-- gh-comment-id:3382993827 --> @Aetrocles commented on GitHub (Oct 8, 2025): I know it doesn't help with this particular API or bug but for me it was really important to get my YouTube music metadata so I can keep track of my songs and my playlists... If you go to Google takeout you can download your YouTube data and it'll have a list of all your playlists and another file of all your library songs/videos. You can cross reference with the video ID from the two files and parse the content into a clean data structure... You can't really automate the extraction of Google take out and I wouldn't recommend abusing it because if they remove album or artist info it would suck major... But it helps in a pinch.
Author
Owner

@carvaofficial commented on GitHub (Oct 9, 2025):

Hi! I'm having the same problem when requesting my YouTube Music account history:
ytmusic = YTMusic(auth='oauth.json', oauth_credentials=OAuthCredentials(client_id=os.getenv("YTMUSIC_CLIENT_ID"), client_secret=os.getenv("YTMUSIC_CLIENT_SECRET"))) history = ytmusic.get_history()

The log output:
ytmusic-dev | [2025-10-09 14:39:16,572] (INFO): Script start
ytmusic-dev | [2025-10-09 14:39:17,112] (ERROR): Error getting song from YouTube Music: Server returned HTTP 400: Bad Request.
ytmusic-dev | Request contains an invalid argument.
ytmusic-dev | [2025-10-09 14:39:17,112] (INFO): No song data to send.
ytmusic-dev | [2025-10-09 14:39:17,112] (INFO): Script end

<!-- gh-comment-id:3386188071 --> @carvaofficial commented on GitHub (Oct 9, 2025): Hi! I'm having the same problem when requesting my YouTube Music account history: `ytmusic = YTMusic(auth='oauth.json', oauth_credentials=OAuthCredentials(client_id=os.getenv("YTMUSIC_CLIENT_ID"), client_secret=os.getenv("YTMUSIC_CLIENT_SECRET"))) history = ytmusic.get_history()` The log output: ytmusic-dev | [2025-10-09 14:39:16,572] (INFO): Script start ytmusic-dev | [2025-10-09 14:39:17,112] (ERROR): Error getting song from YouTube Music: Server returned HTTP 400: Bad Request. ytmusic-dev | Request contains an invalid argument. ytmusic-dev | [2025-10-09 14:39:17,112] (INFO): No song data to send. ytmusic-dev | [2025-10-09 14:39:17,112] (INFO): Script end
Author
Owner

@davidpelayo commented on GitHub (Nov 25, 2025):

After I would spend a few hours trying to fix this I came up with reading this Github issue. What would you say would be the alternative to try to workaround it? Or it does not have to do with the auth flow you use but rather the API being changed on Youtube's side?

<!-- gh-comment-id:3575078211 --> @davidpelayo commented on GitHub (Nov 25, 2025): After I would spend a few hours trying to fix this I came up with reading this Github issue. What would you say would be the alternative to try to workaround it? Or it does not have to do with the auth flow you use but rather the API being changed on Youtube's side?
Author
Owner

@thosch6 commented on GitHub (Nov 27, 2025):

I am having the same problem, I cannot use my app to generate playlists through the ytmusicapi anymore. I'm getting:
"Failed to create playlist: Server returned HTTP 400: Bad Request. Request contains an invalid argument."

My app was working fine until recently, and the authentication itself succeeds - but playlist creation fails.

<!-- gh-comment-id:3587309924 --> @thosch6 commented on GitHub (Nov 27, 2025): I am having the same problem, I cannot use my app to generate playlists through the ytmusicapi anymore. I'm getting: "Failed to create playlist: Server returned HTTP 400: Bad Request. Request contains an invalid argument." My app was working fine until recently, and the authentication itself succeeds - but playlist creation fails.
Author
Owner

@sigma67 commented on GitHub (Dec 4, 2025):

Hi @thosch6 , have you tried the workaround to use browser-based authentication? It should work just fine.

On the oauth front we are unfortunately blocked due to YouTube Music server not accepting our requests.

<!-- gh-comment-id:3611477726 --> @sigma67 commented on GitHub (Dec 4, 2025): Hi @thosch6 , have you tried the workaround to use browser-based authentication? It should work just fine. On the oauth front we are unfortunately blocked due to YouTube Music server not accepting our requests.
Author
Owner

@thosch6 commented on GitHub (Dec 4, 2025):

The workaround to use browser-based authentication does not work either. I tried this approach twice, the first time with the AI help of the new Gemini 3.0, a few days later with Claude Sonnet 4.5. Even though authentication poses no problem, generating playlists fails always.
"Failed to create playlist: Server returned HTTP 400: Bad Request. Request contains an invalid argument."

<!-- gh-comment-id:3611636008 --> @thosch6 commented on GitHub (Dec 4, 2025): The workaround to use browser-based authentication does not work either. I tried this approach twice, the first time with the AI help of the new Gemini 3.0, a few days later with Claude Sonnet 4.5. Even though authentication poses no problem, generating playlists fails always. "Failed to create playlist: Server returned HTTP 400: Bad Request. Request contains an invalid argument."
Author
Owner

@sigma67 commented on GitHub (Dec 4, 2025):

That is unlikely, you might be using the wrong file by accident. That error message only occurs when using oauth (or your browser credentials are corrupt).

Did you follow the browser instructions in the documentation? Could you elaborate?

<!-- gh-comment-id:3612080166 --> @sigma67 commented on GitHub (Dec 4, 2025): That is unlikely, you might be using the wrong file by accident. That error message only occurs when using oauth (or your browser credentials are corrupt). Did you follow the browser instructions in the documentation? Could you elaborate?
Author
Owner

@thosch6 commented on GitHub (Dec 10, 2025):

So I tried the 3rd time creating a playlist with the latest YouTubeMusicApi version 1.11.3. It fails with error 401. Here's the detailed report assembled with Anthropic Opus 4.5:

Report: Playlist Creation Failing with Browser Authentication

Summary

Playlist creation via ytmusicapi with browser authentication returns HTTP 401 (Unauthorized), even though search and other read operations work fine with the same credentials.

Environment

  • macOS 15.5
  • Python 3.14
  • ytmusicapi (latest version 1.11.3)
  • Chrome 143

Observed Behavior

Operation | Result
-- | --
Search (songs, albums) | ✅ Works
Browse library | ✅ Works
Create playlist | ❌ 401 Unauthorized
Add items to playlist | ❌ 401 Unauthorized

Root Cause Analysis

After extensive debugging, I found that direct HTTP requests to YouTube Music's API work perfectly when using headers copied directly from Chrome DevTools. This indicates the issue is in how ytmusicapi handles or regenerates the authorization header.

Key Finding 1: Authorization Header Format Changed

YouTube Music now uses a 3-part authorization header with _u suffix:

SAPISIDHASH <timestamp>_<hash>_u SAPISID1PHASH <timestamp>_<hash>_u SAPISID3PHASH <timestamp>_<hash>_u

Instead of the previous single-part format:

SAPISIDHASH <timestamp>_<hash>

Key Finding 2: Hash Generation Algorithm Differs

I tested the documented hash formula:

python
hash_input = f"{timestamp} {SAPISID} {origin}"
hash_value = hashlib.sha1(hash_input.encode()).hexdigest()

Result: The generated hash does NOT match what Chrome produces.

I tested multiple variations (different separators, different orders, SHA-256, etc.) - none matched Chrome's hash. This suggests YouTube may have changed the hash algorithm or is using additional inputs.

Key Finding 3: Cookies Rotate Frequently

These cookies change with every few requests and must be fresh:

  • SIDCC
  • __Secure-1PSIDCC
  • __Secure-3PSIDCC
  • __Secure-1PSIDTS
  • __Secure-3PSIDTS

Working Workaround

Direct API calls work when using the exact headers copied from a successful Chrome request (e.g. when creating a playlist manually while logged into the YouTube Music account) :

python
import requests

headers = {
    "authorization": "<copied from Chrome>",
    "cookie": "<copied from Chrome>",
    "content-type": "application/json",
    "origin": "https://music.youtube.com",
    "x-goog-authuser": "1",
    "x-youtube-client-name": "67",
    "x-youtube-client-version": "1.20251203.02.00",
}

response = requests.post(
    "https://music.youtube.com/youtubei/v1/playlist/create?prettyPrint=false",
    headers=headers,
    json={
        "context": {
            "client": {
                "clientName": "WEB_REMIX",
                "clientVersion": "1.20251203.02.00",
            }
        },
        "title": "Test Playlist",
        "privacyStatus": "PRIVATE"
    }
)
# Returns 200 OK with playlistId

Suggested Investigation Areas

  1. Authorization header generation - The 3-part format with _u suffix may need to be implemented
  2. Hash algorithm - Chrome may be using a different input format or algorithm than documented
  3. Cookie handling - Some session cookies may need to be refreshed or passed through differently for write operations
  4. Endpoint differences - Read endpoints (search, browse) may have different auth requirements than write endpoints (playlist/create, edit_playlist)

How to Reproduce

  1. Set up browser authentication with ytmusicapi
  2. Verify search works: ytmusic.search("test")
  3. Try creating a playlist: ytmusic.create_playlist("Test") 401
<!-- gh-comment-id:3635535245 --> @thosch6 commented on GitHub (Dec 10, 2025): So I tried the 3rd time creating a playlist with the latest YouTubeMusicApi version 1.11.3. It fails with error 401. Here's the detailed report assembled with Anthropic Opus 4.5: <h2 class="font-claude-response-heading text-text-100 mt-1 -mb-0.5">Report: Playlist Creation Failing with Browser Authentication</h2> <h3 class="font-claude-response-subheading text-text-100 mt-1 -mb-1.5">Summary</h3> <p class="font-claude-response-body break-words whitespace-normal ">Playlist creation via <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">ytmusicapi</code> with browser authentication returns <strong>HTTP 401 (Unauthorized)</strong>, even though search and other read operations work fine with the same credentials.</p> <h3 class="font-claude-response-subheading text-text-100 mt-1 -mb-1.5">Environment</h3> <ul class="[&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-disc space-y-2.5 pl-7"> <li class="whitespace-normal break-words">macOS 15.5</li> <li class="whitespace-normal break-words">Python 3.14</li> <li class="whitespace-normal break-words">ytmusicapi (latest version 1.11.3)</li> <li class="whitespace-normal break-words">Chrome 143</li> </ul> <h3 class="font-claude-response-subheading text-text-100 mt-1 -mb-1.5">Observed Behavior</h3> <pre class="font-ui border-border-100/50 overflow-x-scroll w-full rounded border-[0.5px] shadow-[0_2px_12px_hsl(var(--always-black)/5%)]"> Operation | Result -- | -- Search (songs, albums) | ✅ Works Browse library | ✅ Works Create playlist | ❌ 401 Unauthorized Add items to playlist | ❌ 401 Unauthorized </pre> <h3 class="font-claude-response-subheading text-text-100 mt-1 -mb-1.5">Root Cause Analysis</h3> <p class="font-claude-response-body break-words whitespace-normal ">After extensive debugging, I found that <strong>direct HTTP requests</strong> to YouTube Music's API work perfectly when using headers copied directly from Chrome DevTools. This indicates the issue is in how <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">ytmusicapi</code> handles or regenerates the authorization header.</p> <h4 class="font-claude-response-body-bold text-text-100 mt-1">Key Finding 1: Authorization Header Format Changed</h4> <p class="font-claude-response-body break-words whitespace-normal ">YouTube Music now uses a <strong>3-part authorization header</strong> with <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">_u</code> suffix:</p> <div class="relative group/copy bg-bg-000/50 border-0.5 border-border-400 rounded-lg"><div class="sticky opacity-0 group-hover/copy:opacity-100 top-2 py-2 h-12 w-0 float-right"><div class="absolute right-0 h-8 px-2 items-center inline-flex z-10"><button class="inline-flex items-center justify-center relative shrink-0 can-focus select-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none disabled:drop-shadow-none border-transparent transition font-base duration-300 ease-[cubic-bezier(0.165,0.85,0.45,1)] h-8 w-8 rounded-md active:scale-95 backdrop-blur-md Button_ghost__BUAoh" type="button" aria-label="Copy to clipboard" data-state="closed"><div class="relative"><div class="flex items-center justify-center transition-all opacity-100 scale-100" style="width: 20px; height: 20px;"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="shrink-0 transition-all opacity-100 scale-100" aria-hidden="true"><path d="M12.5 3C13.3284 3 14 3.67157 14 4.5V6H15.5C16.3284 6 17 6.67157 17 7.5V15.5C17 16.3284 16.3284 17 15.5 17H7.5C6.67157 17 6 16.3284 6 15.5V14H4.5C3.67157 14 3 13.3284 3 12.5V4.5C3 3.67157 3.67157 3 4.5 3H12.5ZM14 12.5C14 13.3284 13.3284 14 12.5 14H7V15.5C7 15.7761 7.22386 16 7.5 16H15.5C15.7761 16 16 15.7761 16 15.5V7.5C16 7.22386 15.7761 7 15.5 7H14V12.5ZM4.5 4C4.22386 4 4 4.22386 4 4.5V12.5C4 12.7761 4.22386 13 4.5 13H12.5C12.7761 13 13 12.7761 13 12.5V4.5C13 4.22386 12.7761 4 12.5 4H4.5Z"></path></svg></div><div class="flex items-center justify-center absolute top-0 left-0 transition-all opacity-0 scale-50" style="width: 20px; height: 20px;"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="shrink-0 absolute top-0 left-0 transition-all opacity-0 scale-50" aria-hidden="true"><path d="M15.1883 5.10908C15.3699 4.96398 15.6346 4.96153 15.8202 5.11592C16.0056 5.27067 16.0504 5.53125 15.9403 5.73605L15.8836 5.82003L8.38354 14.8202C8.29361 14.9279 8.16242 14.9925 8.02221 14.9989C7.88203 15.0051 7.74545 14.9526 7.64622 14.8534L4.14617 11.3533L4.08172 11.2752C3.95384 11.0811 3.97542 10.817 4.14617 10.6463C4.31693 10.4755 4.58105 10.4539 4.77509 10.5818L4.85321 10.6463L7.96556 13.7586L15.1161 5.1794L15.1883 5.10908Z"></path></svg></div></div></button></div></div><div><pre class="code-block__code !my-0 !rounded-lg !text-sm !leading-relaxed" style="background: transparent; color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px; font-family: var(--font-mono); direction: ltr; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; line-height: 1.5; tab-size: 2; hyphens: none; padding: 1em; margin: 0.5em 0px; overflow: auto; border-radius: 0.3em;"><code style="background: transparent; color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px; font-family: var(--font-mono); direction: ltr; text-align: left; white-space: pre-wrap; word-spacing: normal; word-break: normal; line-height: 1.5; tab-size: 2; hyphens: none;"><span><span>SAPISIDHASH &lt;timestamp&gt;_&lt;hash&gt;_u SAPISID1PHASH &lt;timestamp&gt;_&lt;hash&gt;_u SAPISID3PHASH &lt;timestamp&gt;_&lt;hash&gt;_u</span></span></code></pre></div></div> <p class="font-claude-response-body break-words whitespace-normal ">Instead of the previous single-part format:</p> <div class="relative group/copy bg-bg-000/50 border-0.5 border-border-400 rounded-lg"><div class="sticky opacity-0 group-hover/copy:opacity-100 top-2 py-2 h-12 w-0 float-right"><div class="absolute right-0 h-8 px-2 items-center inline-flex z-10"><button class="inline-flex items-center justify-center relative shrink-0 can-focus select-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none disabled:drop-shadow-none border-transparent transition font-base duration-300 ease-[cubic-bezier(0.165,0.85,0.45,1)] h-8 w-8 rounded-md active:scale-95 backdrop-blur-md Button_ghost__BUAoh" type="button" aria-label="Copy to clipboard" data-state="closed"><div class="relative"><div class="flex items-center justify-center transition-all opacity-100 scale-100" style="width: 20px; height: 20px;"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="shrink-0 transition-all opacity-100 scale-100" aria-hidden="true"><path d="M12.5 3C13.3284 3 14 3.67157 14 4.5V6H15.5C16.3284 6 17 6.67157 17 7.5V15.5C17 16.3284 16.3284 17 15.5 17H7.5C6.67157 17 6 16.3284 6 15.5V14H4.5C3.67157 14 3 13.3284 3 12.5V4.5C3 3.67157 3.67157 3 4.5 3H12.5ZM14 12.5C14 13.3284 13.3284 14 12.5 14H7V15.5C7 15.7761 7.22386 16 7.5 16H15.5C15.7761 16 16 15.7761 16 15.5V7.5C16 7.22386 15.7761 7 15.5 7H14V12.5ZM4.5 4C4.22386 4 4 4.22386 4 4.5V12.5C4 12.7761 4.22386 13 4.5 13H12.5C12.7761 13 13 12.7761 13 12.5V4.5C13 4.22386 12.7761 4 12.5 4H4.5Z"></path></svg></div><div class="flex items-center justify-center absolute top-0 left-0 transition-all opacity-0 scale-50" style="width: 20px; height: 20px;"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="shrink-0 absolute top-0 left-0 transition-all opacity-0 scale-50" aria-hidden="true"><path d="M15.1883 5.10908C15.3699 4.96398 15.6346 4.96153 15.8202 5.11592C16.0056 5.27067 16.0504 5.53125 15.9403 5.73605L15.8836 5.82003L8.38354 14.8202C8.29361 14.9279 8.16242 14.9925 8.02221 14.9989C7.88203 15.0051 7.74545 14.9526 7.64622 14.8534L4.14617 11.3533L4.08172 11.2752C3.95384 11.0811 3.97542 10.817 4.14617 10.6463C4.31693 10.4755 4.58105 10.4539 4.77509 10.5818L4.85321 10.6463L7.96556 13.7586L15.1161 5.1794L15.1883 5.10908Z"></path></svg></div></div></button></div></div><div><pre class="code-block__code !my-0 !rounded-lg !text-sm !leading-relaxed" style="background: transparent; color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px; font-family: var(--font-mono); direction: ltr; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; line-height: 1.5; tab-size: 2; hyphens: none; padding: 1em; margin: 0.5em 0px; overflow: auto; border-radius: 0.3em;"><code style="background: transparent; color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px; font-family: var(--font-mono); direction: ltr; text-align: left; white-space: pre-wrap; word-spacing: normal; word-break: normal; line-height: 1.5; tab-size: 2; hyphens: none;"><span><span>SAPISIDHASH &lt;timestamp&gt;_&lt;hash&gt;</span></span></code></pre></div></div> <h4 class="font-claude-response-body-bold text-text-100 mt-1">Key Finding 2: Hash Generation Algorithm Differs</h4> <p class="font-claude-response-body break-words whitespace-normal ">I tested the documented hash formula:</p> <div class="relative group/copy bg-bg-000/50 border-0.5 border-border-400 rounded-lg"><div class="sticky opacity-0 group-hover/copy:opacity-100 top-2 py-2 h-12 w-0 float-right"><div class="absolute right-0 h-8 px-2 items-center inline-flex z-10"><button class="inline-flex items-center justify-center relative shrink-0 can-focus select-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none disabled:drop-shadow-none border-transparent transition font-base duration-300 ease-[cubic-bezier(0.165,0.85,0.45,1)] h-8 w-8 rounded-md active:scale-95 backdrop-blur-md Button_ghost__BUAoh" type="button" aria-label="Copy to clipboard" data-state="closed"><div class="relative"><div class="flex items-center justify-center transition-all opacity-100 scale-100" style="width: 20px; height: 20px;"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="shrink-0 transition-all opacity-100 scale-100" aria-hidden="true"><path d="M12.5 3C13.3284 3 14 3.67157 14 4.5V6H15.5C16.3284 6 17 6.67157 17 7.5V15.5C17 16.3284 16.3284 17 15.5 17H7.5C6.67157 17 6 16.3284 6 15.5V14H4.5C3.67157 14 3 13.3284 3 12.5V4.5C3 3.67157 3.67157 3 4.5 3H12.5ZM14 12.5C14 13.3284 13.3284 14 12.5 14H7V15.5C7 15.7761 7.22386 16 7.5 16H15.5C15.7761 16 16 15.7761 16 15.5V7.5C16 7.22386 15.7761 7 15.5 7H14V12.5ZM4.5 4C4.22386 4 4 4.22386 4 4.5V12.5C4 12.7761 4.22386 13 4.5 13H12.5C12.7761 13 13 12.7761 13 12.5V4.5C13 4.22386 12.7761 4 12.5 4H4.5Z"></path></svg></div><div class="flex items-center justify-center absolute top-0 left-0 transition-all opacity-0 scale-50" style="width: 20px; height: 20px;"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="shrink-0 absolute top-0 left-0 transition-all opacity-0 scale-50" aria-hidden="true"><path d="M15.1883 5.10908C15.3699 4.96398 15.6346 4.96153 15.8202 5.11592C16.0056 5.27067 16.0504 5.53125 15.9403 5.73605L15.8836 5.82003L8.38354 14.8202C8.29361 14.9279 8.16242 14.9925 8.02221 14.9989C7.88203 15.0051 7.74545 14.9526 7.64622 14.8534L4.14617 11.3533L4.08172 11.2752C3.95384 11.0811 3.97542 10.817 4.14617 10.6463C4.31693 10.4755 4.58105 10.4539 4.77509 10.5818L4.85321 10.6463L7.96556 13.7586L15.1161 5.1794L15.1883 5.10908Z"></path></svg></div></div></button></div></div><div class="text-text-500 font-small p-3.5 pb-0">python</div><div><pre class="code-block__code !my-0 !rounded-lg !text-sm !leading-relaxed" style="background: transparent; color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px; font-family: var(--font-mono); direction: ltr; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; line-height: 1.5; tab-size: 2; hyphens: none; padding: 1em; margin: 0.5em 0px; overflow: auto; border-radius: 0.3em;"><code class="language-python" style="background: transparent; color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px; font-family: var(--font-mono); direction: ltr; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; line-height: 1.5; tab-size: 2; hyphens: none;"><span><span>hash_input </span><span class="token" style="color: rgb(97, 175, 239);">=</span><span> </span><span class="token string-interpolation" style="color: rgb(152, 195, 121);">f"</span><span class="token string-interpolation interpolation" style="color: rgb(171, 178, 191);">{</span><span class="token string-interpolation interpolation">timestamp</span><span class="token string-interpolation interpolation" style="color: rgb(171, 178, 191);">}</span><span class="token string-interpolation" style="color: rgb(152, 195, 121);"> </span><span class="token string-interpolation interpolation" style="color: rgb(171, 178, 191);">{</span><span class="token string-interpolation interpolation">SAPISID</span><span class="token string-interpolation interpolation" style="color: rgb(171, 178, 191);">}</span><span class="token string-interpolation" style="color: rgb(152, 195, 121);"> </span><span class="token string-interpolation interpolation" style="color: rgb(171, 178, 191);">{</span><span class="token string-interpolation interpolation">origin</span><span class="token string-interpolation interpolation" style="color: rgb(171, 178, 191);">}</span><span class="token string-interpolation" style="color: rgb(152, 195, 121);">"</span><span> </span></span><span><span>hash_value </span><span class="token" style="color: rgb(97, 175, 239);">=</span><span> hashlib</span><span class="token" style="color: rgb(171, 178, 191);">.</span><span>sha1</span><span class="token" style="color: rgb(171, 178, 191);">(</span><span>hash_input</span><span class="token" style="color: rgb(171, 178, 191);">.</span><span>encode</span><span class="token" style="color: rgb(171, 178, 191);">(</span><span class="token" style="color: rgb(171, 178, 191);">)</span><span class="token" style="color: rgb(171, 178, 191);">)</span><span class="token" style="color: rgb(171, 178, 191);">.</span><span>hexdigest</span><span class="token" style="color: rgb(171, 178, 191);">(</span><span class="token" style="color: rgb(171, 178, 191);">)</span></span></code></pre></div></div> <p class="font-claude-response-body break-words whitespace-normal "><strong>Result:</strong> The generated hash does NOT match what Chrome produces.</p> <p class="font-claude-response-body break-words whitespace-normal ">I tested multiple variations (different separators, different orders, SHA-256, etc.) - none matched Chrome's hash. This suggests YouTube may have changed the hash algorithm or is using additional inputs.</p> <h4 class="font-claude-response-body-bold text-text-100 mt-1">Key Finding 3: Cookies Rotate Frequently</h4> <p class="font-claude-response-body break-words whitespace-normal ">These cookies change with every few requests and must be fresh:</p> <ul class="[&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-disc space-y-2.5 pl-7"> <li class="whitespace-normal break-words"><code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">SIDCC</code></li> <li class="whitespace-normal break-words"><code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">__Secure-1PSIDCC</code></li> <li class="whitespace-normal break-words"><code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">__Secure-3PSIDCC</code></li> <li class="whitespace-normal break-words"><code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">__Secure-1PSIDTS</code></li> <li class="whitespace-normal break-words"><code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">__Secure-3PSIDTS</code></li> </ul> <h3 class="font-claude-response-subheading text-text-100 mt-1 -mb-1.5">Working Workaround</h3> <p class="font-claude-response-body break-words whitespace-normal ">Direct API calls work when using the <strong>exact headers</strong> copied from a successful Chrome request (e.g. when creating a playlist manually while logged into the YouTube Music account) :</p> <div class="relative group/copy bg-bg-000/50 border-0.5 border-border-400 rounded-lg"><div class="sticky opacity-0 group-hover/copy:opacity-100 top-2 py-2 h-12 w-0 float-right"><div class="absolute right-0 h-8 px-2 items-center inline-flex z-10"><button class="inline-flex items-center justify-center relative shrink-0 can-focus select-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none disabled:drop-shadow-none border-transparent transition font-base duration-300 ease-[cubic-bezier(0.165,0.85,0.45,1)] h-8 w-8 rounded-md active:scale-95 backdrop-blur-md Button_ghost__BUAoh" type="button" aria-label="Copy to clipboard" data-state="closed"><div class="relative"><div class="flex items-center justify-center transition-all opacity-100 scale-100" style="width: 20px; height: 20px;"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="shrink-0 transition-all opacity-100 scale-100" aria-hidden="true"><path d="M12.5 3C13.3284 3 14 3.67157 14 4.5V6H15.5C16.3284 6 17 6.67157 17 7.5V15.5C17 16.3284 16.3284 17 15.5 17H7.5C6.67157 17 6 16.3284 6 15.5V14H4.5C3.67157 14 3 13.3284 3 12.5V4.5C3 3.67157 3.67157 3 4.5 3H12.5ZM14 12.5C14 13.3284 13.3284 14 12.5 14H7V15.5C7 15.7761 7.22386 16 7.5 16H15.5C15.7761 16 16 15.7761 16 15.5V7.5C16 7.22386 15.7761 7 15.5 7H14V12.5ZM4.5 4C4.22386 4 4 4.22386 4 4.5V12.5C4 12.7761 4.22386 13 4.5 13H12.5C12.7761 13 13 12.7761 13 12.5V4.5C13 4.22386 12.7761 4 12.5 4H4.5Z"></path></svg></div><div class="flex items-center justify-center absolute top-0 left-0 transition-all opacity-0 scale-50" style="width: 20px; height: 20px;"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="shrink-0 absolute top-0 left-0 transition-all opacity-0 scale-50" aria-hidden="true"><path d="M15.1883 5.10908C15.3699 4.96398 15.6346 4.96153 15.8202 5.11592C16.0056 5.27067 16.0504 5.53125 15.9403 5.73605L15.8836 5.82003L8.38354 14.8202C8.29361 14.9279 8.16242 14.9925 8.02221 14.9989C7.88203 15.0051 7.74545 14.9526 7.64622 14.8534L4.14617 11.3533L4.08172 11.2752C3.95384 11.0811 3.97542 10.817 4.14617 10.6463C4.31693 10.4755 4.58105 10.4539 4.77509 10.5818L4.85321 10.6463L7.96556 13.7586L15.1161 5.1794L15.1883 5.10908Z"></path></svg></div></div></button></div></div><div class="text-text-500 font-small p-3.5 pb-0">python</div><div><pre class="code-block__code !my-0 !rounded-lg !text-sm !leading-relaxed" style="background: transparent; color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px; font-family: var(--font-mono); direction: ltr; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; line-height: 1.5; tab-size: 2; hyphens: none; padding: 1em; margin: 0.5em 0px; overflow: auto; border-radius: 0.3em;"><code class="language-python" style="background: transparent; color: rgb(171, 178, 191); text-shadow: rgba(0, 0, 0, 0.3) 0px 1px; font-family: var(--font-mono); direction: ltr; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; line-height: 1.5; tab-size: 2; hyphens: none;"><span><span class="token" style="color: rgb(198, 120, 221);">import</span><span> requests </span></span><span> </span><span><span>headers </span><span class="token" style="color: rgb(97, 175, 239);">=</span><span> </span><span class="token" style="color: rgb(171, 178, 191);">{</span><span> </span></span><span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"authorization"</span><span class="token" style="color: rgb(171, 178, 191);">:</span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"&lt;copied from Chrome&gt;"</span><span class="token" style="color: rgb(171, 178, 191);">,</span><span> </span></span><span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"cookie"</span><span class="token" style="color: rgb(171, 178, 191);">:</span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"&lt;copied from Chrome&gt;"</span><span class="token" style="color: rgb(171, 178, 191);">,</span><span> </span></span><span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"content-type"</span><span class="token" style="color: rgb(171, 178, 191);">:</span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"application/json"</span><span class="token" style="color: rgb(171, 178, 191);">,</span><span> </span></span><span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"origin"</span><span class="token" style="color: rgb(171, 178, 191);">:</span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"https://music.youtube.com"</span><span class="token" style="color: rgb(171, 178, 191);">,</span><span> </span></span><span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"x-goog-authuser"</span><span class="token" style="color: rgb(171, 178, 191);">:</span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"1"</span><span class="token" style="color: rgb(171, 178, 191);">,</span><span> </span></span><span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"x-youtube-client-name"</span><span class="token" style="color: rgb(171, 178, 191);">:</span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"67"</span><span class="token" style="color: rgb(171, 178, 191);">,</span><span> </span></span><span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"x-youtube-client-version"</span><span class="token" style="color: rgb(171, 178, 191);">:</span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"1.20251203.02.00"</span><span class="token" style="color: rgb(171, 178, 191);">,</span><span> </span></span><span><span></span><span class="token" style="color: rgb(171, 178, 191);">}</span><span> </span></span><span> </span><span><span>response </span><span class="token" style="color: rgb(97, 175, 239);">=</span><span> requests</span><span class="token" style="color: rgb(171, 178, 191);">.</span><span>post</span><span class="token" style="color: rgb(171, 178, 191);">(</span><span> </span></span><span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"https://music.youtube.com/youtubei/v1/playlist/create?prettyPrint=false"</span><span class="token" style="color: rgb(171, 178, 191);">,</span><span> </span></span><span><span> headers</span><span class="token" style="color: rgb(97, 175, 239);">=</span><span>headers</span><span class="token" style="color: rgb(171, 178, 191);">,</span><span> </span></span><span><span> json</span><span class="token" style="color: rgb(97, 175, 239);">=</span><span class="token" style="color: rgb(171, 178, 191);">{</span><span> </span></span><span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"context"</span><span class="token" style="color: rgb(171, 178, 191);">:</span><span> </span><span class="token" style="color: rgb(171, 178, 191);">{</span><span> </span></span><span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"client"</span><span class="token" style="color: rgb(171, 178, 191);">:</span><span> </span><span class="token" style="color: rgb(171, 178, 191);">{</span><span> </span></span><span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"clientName"</span><span class="token" style="color: rgb(171, 178, 191);">:</span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"WEB_REMIX"</span><span class="token" style="color: rgb(171, 178, 191);">,</span><span> </span></span><span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"clientVersion"</span><span class="token" style="color: rgb(171, 178, 191);">:</span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"1.20251203.02.00"</span><span class="token" style="color: rgb(171, 178, 191);">,</span><span> </span></span><span><span> </span><span class="token" style="color: rgb(171, 178, 191);">}</span><span> </span></span><span><span> </span><span class="token" style="color: rgb(171, 178, 191);">}</span><span class="token" style="color: rgb(171, 178, 191);">,</span><span> </span></span><span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"title"</span><span class="token" style="color: rgb(171, 178, 191);">:</span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"Test Playlist"</span><span class="token" style="color: rgb(171, 178, 191);">,</span><span> </span></span><span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"privacyStatus"</span><span class="token" style="color: rgb(171, 178, 191);">:</span><span> </span><span class="token" style="color: rgb(152, 195, 121);">"PRIVATE"</span><span> </span></span><span><span> </span><span class="token" style="color: rgb(171, 178, 191);">}</span><span> </span></span><span><span></span><span class="token" style="color: rgb(171, 178, 191);">)</span><span> </span></span><span><span></span><span class="token" style="color: rgb(92, 99, 112); font-style: italic;"># Returns 200 OK with playlistId</span></span></code></pre></div></div> <h3 class="font-claude-response-subheading text-text-100 mt-1 -mb-1.5">Suggested Investigation Areas</h3> <ol class="[&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-decimal space-y-2.5 pl-7"> <li class="whitespace-normal break-words"><strong>Authorization header generation</strong> - The 3-part format with <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">_u</code> suffix may need to be implemented</li> <li class="whitespace-normal break-words"><strong>Hash algorithm</strong> - Chrome may be using a different input format or algorithm than documented</li> <li class="whitespace-normal break-words"><strong>Cookie handling</strong> - Some session cookies may need to be refreshed or passed through differently for write operations</li> <li class="whitespace-normal break-words"><strong>Endpoint differences</strong> - Read endpoints (search, browse) may have different auth requirements than write endpoints (playlist/create, edit_playlist)</li> </ol> <h3 class="font-claude-response-subheading text-text-100 mt-1 -mb-1.5">How to Reproduce</h3> <ol class="[&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-decimal space-y-2.5 pl-7"> <li class="whitespace-normal break-words">Set up browser authentication with <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">ytmusicapi</code></li> <li class="whitespace-normal break-words">Verify search works: <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">ytmusic.search("test")</code> → ✅</li> <li class="whitespace-normal break-words">Try creating a playlist: <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">ytmusic.create_playlist("Test")</code> → ❌ 401</li></ol>
Author
Owner

@tschamp31 commented on GitHub (Dec 31, 2025):

EDIT: I was wrong, upon further review. Browser AUTH works it's that the library call structure requires one iteration of a continuation upon initial call. It does not provide song list off the bat as it appears it use to.

<!-- gh-comment-id:3702926370 --> @tschamp31 commented on GitHub (Dec 31, 2025): EDIT: I was wrong, upon further review. Browser AUTH works it's that the library call structure requires one iteration of a continuation upon initial call. It does not provide song list off the bat as it appears it use to.
Author
Owner

@ryansc0tt commented on GitHub (Dec 31, 2025):

A humble note here that corresponds with recent comments: I switched my script for fetching (then updating) playlists to browser auth a couple of weeks ago. Running it again a couple of days ago, get_library_playlists()requests were returning no results. I haven't debugged at all, but manually refreshing the headers (via Firefox 164.0.1) resolved the issue.

<!-- gh-comment-id:3702971179 --> @ryansc0tt commented on GitHub (Dec 31, 2025): A humble note here that corresponds with recent comments: I switched my script for fetching (then updating) playlists to browser auth a couple of weeks ago. Running it again a couple of days ago, `get_library_playlists()`requests were returning no results. I haven't debugged at all, but manually refreshing the headers (via Firefox 164.0.1) resolved the issue.
Author
Owner

@tschamp31 commented on GitHub (Jan 1, 2026):

A humble note here that corresponds with recent comments: I switched my script for fetching (then updating) playlists to browser auth a couple of weeks ago. Running it again a couple of days ago, get_library_playlists()requests were returning no results. I haven't debugged at all, but manually refreshing the headers (via Firefox 164.0.1) resolved the issue.

That's actually a good sanity check as it seems that the web behavior is that it will trigger a reloadContinuationToken + cookie change to minimize automated scraping.

<!-- gh-comment-id:3703133367 --> @tschamp31 commented on GitHub (Jan 1, 2026): > A humble note here that corresponds with recent comments: I switched my script for fetching (then updating) playlists to browser auth a couple of weeks ago. Running it again a couple of days ago, `get_library_playlists()`requests were returning no results. I haven't debugged at all, but manually refreshing the headers (via Firefox 164.0.1) resolved the issue. That's actually a good sanity check as it seems that the web behavior is that it will trigger a reloadContinuationToken + cookie change to minimize automated scraping.
Author
Owner

@tschamp31 commented on GitHub (Jan 3, 2026):

I have great news, so it appears they just depreciated the endpoint for either the APIKey or UserAgent or Payload, not sure which but modifying the existing code to just simply utilize TVHTML, client version 7, and a useragent that is WEBTV based allowed for oauth to work again for the endpoint. Main issue is now the parser needs to be updated to allow for tvRender paths 😂

<!-- gh-comment-id:3706690033 --> @tschamp31 commented on GitHub (Jan 3, 2026): I have great news, so it appears they just depreciated the endpoint for either the APIKey or UserAgent or Payload, not sure which but modifying the existing code to just simply utilize TVHTML, client version 7, and a useragent that is WEBTV based allowed for oauth to work again for the endpoint. Main issue is now the parser needs to be updated to allow for tvRender paths 😂
Author
Owner

@sgvictorino commented on GitHub (Jan 3, 2026):

I have great news, so it appears they just depreciated the endpoint for either the APIKey or UserAgent or Payload, not sure which but modifying the existing code to just simply utilize TVHTML, client version 7, and a useragent that is WEBTV based allowed for oauth to work again for the endpoint. Main issue is now the parser needs to be updated to allow for tvRender paths 😂

@tschamp31 see https://github.com/sigma67/ytmusicapi/issues/813#issuecomment-3359695835

<!-- gh-comment-id:3706695325 --> @sgvictorino commented on GitHub (Jan 3, 2026): > I have great news, so it appears they just depreciated the endpoint for either the APIKey or UserAgent or Payload, not sure which but modifying the existing code to just simply utilize TVHTML, client version 7, and a useragent that is WEBTV based allowed for oauth to work again for the endpoint. Main issue is now the parser needs to be updated to allow for tvRender paths 😂 @tschamp31 see https://github.com/sigma67/ytmusicapi/issues/813#issuecomment-3359695835
Author
Owner

@blastbeng commented on GitHub (Jan 22, 2026):

I am having the same issue, i was using ytmusic api in my own project: https://github.com/blastbeng/spotdl-ytm-service

As a workaround for now i am using yubal, looks promising https://github.com/guillevc/yubal, but i can't automatize it.

Maybe we can try to debug yubal code and understand where is the problem in ytmusicapi?

<!-- gh-comment-id:3783771138 --> @blastbeng commented on GitHub (Jan 22, 2026): I am having the same issue, i was using ytmusic api in my own project: https://github.com/blastbeng/spotdl-ytm-service As a workaround for now i am using yubal, looks promising https://github.com/guillevc/yubal, but i can't automatize it. Maybe we can try to debug yubal code and understand where is the problem in ytmusicapi?
Author
Owner

@foobarth commented on GitHub (Jan 26, 2026):

Sadly it seems browser cookie authentication is no longer a viable workaround as it lasts only a few hours. For me, at least. Google is trying really hard to sabotage free software used by paying customers.

<!-- gh-comment-id:3800628992 --> @foobarth commented on GitHub (Jan 26, 2026): Sadly it seems browser cookie authentication is no longer a viable workaround as it lasts only a few hours. For me, at least. Google is trying really hard to sabotage free software used by paying customers.
Author
Owner

@sigma67 commented on GitHub (Jan 26, 2026):

Please open a separate issue if you are having issues with browser authentication

<!-- gh-comment-id:3801166081 --> @sigma67 commented on GitHub (Jan 26, 2026): Please open a separate issue if you are having issues with browser authentication
Author
Owner

@apastel commented on GitHub (Jan 26, 2026):

Maybe we can try to debug yubal code and understand where is the problem in ytmusicapi?

@blastbeng
The problem isn't in ytmusicapi. The yubal project you linked uses browser authentication/cookies too, not OAuth.

<!-- gh-comment-id:3801747168 --> @apastel commented on GitHub (Jan 26, 2026): > Maybe we can try to debug yubal code and understand where is the problem in ytmusicapi? @blastbeng The problem isn't in `ytmusicapi`. The `yubal` project you linked uses browser authentication/cookies too, not OAuth.
Author
Owner

@lara-rium commented on GitHub (Feb 8, 2026):

same issue :/ heres the exact print:

>>> ytmusic.get_library_songs()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/lara/sound-tunnel/.venv/lib/python3.10/site-packages/ytmusicapi/mixins/library.py", line 93, in get_library_songs
    response = parse_func(request_func(""))
  File "/home/lara/sound-tunnel/.venv/lib/python3.10/site-packages/ytmusicapi/mixins/library.py", line 79, in <lambda>
    request_func: RequestFuncType = lambda additionalParams: self._send_request(endpoint, body)
  File "/home/lara/sound-tunnel/.venv/lib/python3.10/site-packages/ytmusicapi/ytmusic.py", line 243, in _send_request
    raise YTMusicServerError(message + error)
ytmusicapi.exceptions.YTMusicServerError: Server returned HTTP 400: Bad Request.
Request contains an invalid argument.```
tried a couple of errors, all the same error
<!-- gh-comment-id:3865957746 --> @lara-rium commented on GitHub (Feb 8, 2026): same issue :/ heres the exact print: ``` >>> ytmusic.get_library_songs() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/lara/sound-tunnel/.venv/lib/python3.10/site-packages/ytmusicapi/mixins/library.py", line 93, in get_library_songs response = parse_func(request_func("")) File "/home/lara/sound-tunnel/.venv/lib/python3.10/site-packages/ytmusicapi/mixins/library.py", line 79, in <lambda> request_func: RequestFuncType = lambda additionalParams: self._send_request(endpoint, body) File "/home/lara/sound-tunnel/.venv/lib/python3.10/site-packages/ytmusicapi/ytmusic.py", line 243, in _send_request raise YTMusicServerError(message + error) ytmusicapi.exceptions.YTMusicServerError: Server returned HTTP 400: Bad Request. Request contains an invalid argument.``` tried a couple of errors, all the same error
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/ytmusicapi#506
No description provided.