ytmusicapi icon indicating copy to clipboard operation
ytmusicapi copied to clipboard

oauth authentication: Request contains an invalid argument

Open sigma67 opened this issue 3 months ago • 25 comments

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

  • [x] 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!

sigma67 avatar Sep 02 '25 08:09 sigma67

PR welcome

sigma67 avatar Sep 02 '25 08:09 sigma67

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

Goldenfreddy0703 avatar Sep 04 '25 02:09 Goldenfreddy0703

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

KoljaWindeler avatar Sep 06 '25 05:09 KoljaWindeler

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.

Goldenfreddy0703 avatar Sep 06 '25 05:09 Goldenfreddy0703

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

king-millez avatar Sep 09 '25 06:09 king-millez

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

sigma67 avatar Sep 09 '25 07:09 sigma67

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.

Aetrocles avatar Sep 26 '25 11:09 Aetrocles

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

I am trying to fetch the user library

kikaDev404 avatar Sep 27 '25 20:09 kikaDev404

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.

sigma67 avatar Sep 28 '25 09:09 sigma67

I am also experiencing this issue.

atjacobs avatar Sep 29 '25 03:09 atjacobs

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.

robdupre avatar Oct 01 '25 19:10 robdupre

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

jorbig avatar Oct 01 '25 20:10 jorbig

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.

sigma67 avatar Oct 02 '25 06:10 sigma67

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.....

Goldenfreddy0703 avatar Oct 02 '25 07:10 Goldenfreddy0703

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

DanielWeigl avatar Oct 02 '25 07:10 DanielWeigl

@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.

sigma67 avatar Oct 02 '25 07:10 sigma67

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.

Goldenfreddy0703 avatar Oct 02 '25 18:10 Goldenfreddy0703

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.

Aetrocles avatar Oct 08 '25 19:10 Aetrocles

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

carvaofficial avatar Oct 09 '25 14:10 carvaofficial

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?

davidpelayo avatar Nov 25 '25 11:11 davidpelayo

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.

thosch6 avatar Nov 27 '25 21:11 thosch6

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.

sigma67 avatar Dec 04 '25 10:12 sigma67

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."

thosch6 avatar Dec 04 '25 11:12 thosch6

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?

sigma67 avatar Dec 04 '25 12:12 sigma67

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

thosch6 avatar Dec 10 '25 06:12 thosch6