minim icon indicating copy to clipboard operation
minim copied to clipboard

Tidal resources give "AccessDenied"

Open RyanThaDude opened this issue 4 months ago • 5 comments

I've been playing around with the code for a while, and tried to get an image from the get_image function in Tidal, but I keep getting this returned instead of the image data.

artist_image = client_tidal_private.get_image(artist["picture"], "artist") gives me:

<?xml version="1.0" encoding="UTF-8"?>
<Error>
	<Code>AccessDenied</Code>
	<Message>Access Denied</Message>
	<RequestId>[hidden]</RequestId>
	<HostId>[hidden]</HostId>
</Error>

So far I'm having no issues retrieving data from the API, but resources are giving me the error. I've tried outside of minim manually through different methods and I can get the image without problem.

RyanThaDude avatar Aug 28 '25 18:08 RyanThaDude

Hi! Can you share the JSON value of artist? This might be a weird edge case I hadn't considered.

The minim.tidal.PrivateAPI.get_image method is NOT an endpoint, but rather a convenience function I wrote to help grab images from TIDAL's resource CDN.

If I can't figure out what's wrong, I suggest waiting maybe 1-2 weeks as I finalize a release with all the endpoints of the new public TIDAL API, which now officially has endpoints for grabbing artist images.

bbye98 avatar Aug 28 '25 23:08 bbye98

This is happening on all resources, sadly. I did a little research on this. My guess is that it has to do something with the size.

I only use images from artists and albums/tracks, so I have no knowledge of the valid sizes for playlist, userProfile and video. If an arbitrary size is used, the 'AccessDenied' is thrown. The only sizes I can get to work with artist is 160x160, 320x320, 750x750, and origin. With album, it's 80x80, 160x160, 320x320, 640x640, 1280x1280, and origin. With minin, it's impossible to pass the origin (original size). Even if I pass a valid size or no size at all, I still receive 'AccessDenied`.

Example, for this album cover 297a855f-315c-4bec-9b7d-9344c45a869c: https://resources.tidal.com/images/297a855f/315c/4bec/9b7d/9344c45a869c/1280x1280.jpg works. However, album_cover = client_tidal_private.get_image(uuid="297a855f-315c-4bec-9b7d-9344c45a869c", type="album", width=1280, height=1280) does not.

On a related note, I have a request to have an option to pass the URL string instead of bytes on get_image(). That way, in my case at least, the web browser will load the image instead of the server having to not only download the image(s) but base64 encode and send to the browser.

Also, constant RESOURCES_URL in the class PrivateAPI probably should be https and not http. :)

RyanThaDude avatar Aug 29 '25 02:08 RyanThaDude

Ah, yes. TIDAL only has cover artwork stored at specific sizes. While I allow the user to specify any combination of width and height in that function, I generally recommend for those values to be left blank and for the default values for the media type to be used:

IMAGE_SIZES = {
    "artist": (750, 750),
    "album": (1280, 1280),
    "playlist": (1080, 1080),
    "track": (1280, 1280),
    "userProfile": (1080, 1080),
    "video": (640, 360),
}

I believe these value should always be valid, but I have not done any extensive testing.

A bit surprising passing in 1280 for both width and height doesn't work for album cover artwork, given the code just sends a GET request to:

self.session.get(
    f"{self.RESOURCES_URL}/{media_type}"
    f"/{uuid.replace('-', '/')}"
    f"/{width}x{height}.{extension}"
)

I'll check this out soon since I've lost my client credentials after some hardware replacements.

bbye98 avatar Aug 30 '25 20:08 bbye98

Hey @RyanThaDude,

I just pushed an update that enables access to >95% of the new TIDAL API's endpoints.

tidal_client = tidal.API()  # using cached access token from PKCE flow
cover_arts = tidal_client.get_album_cover_art(420319211, "US")["included"][0][
    "attributes"
]["files"]

In cover_arts:

0 =
{'href': 'https://resources.tidal.com/images/297a855f/315c/4bec/9b7d/9344c45a869c/1280x1280.jpg', 'meta': {'width': 1280, 'height': 1280}}
1 =
{'href': 'https://resources.tidal.com/images/297a855f/315c/4bec/9b7d/9344c45a869c/1080x1080.jpg', 'meta': {'width': 1080, 'height': 1080}}
2 =
{'href': 'https://resources.tidal.com/images/297a855f/315c/4bec/9b7d/9344c45a869c/750x750.jpg', 'meta': {'width': 750, 'height': 750}}
3 =
{'href': 'https://resources.tidal.com/images/297a855f/315c/4bec/9b7d/9344c45a869c/640x640.jpg', 'meta': {'width': 640, 'height': 640}}
4 =
{'href': 'https://resources.tidal.com/images/297a855f/315c/4bec/9b7d/9344c45a869c/320x320.jpg', 'meta': {'width': 320, 'height': 320}}
5 =
{'href': 'https://resources.tidal.com/images/297a855f/315c/4bec/9b7d/9344c45a869c/160x160.jpg', 'meta': {'width': 160, 'height': 160}}
6 =
{'href': 'https://resources.tidal.com/images/297a855f/315c/4bec/9b7d/9344c45a869c/80x80.jpg', 'meta': {'width': 80, 'height': 80}}

Not a big fan of how the TIDAL OpenAPI is set up, but at least there's no more confusion about what image sizes are available.

bbye98 avatar Sep 01 '25 03:09 bbye98

Awesome! I feel Tidal's OpenAPI v2 is hot garbage as it doesn't give as much data as v1 did, which adds more queries...

Also, Tidal also uses origin.jpg for original size for it's covers/pictures and they're generally larger than the max size listed. Covers are usually 1400x1400, but you can't actually specify that size, oddly.

RyanThaDude avatar Sep 01 '25 03:09 RyanThaDude