This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. This guidance is aimed at Claude Code but may as well be suitable for other AI tooling, such as GitHub Copilot.
Instructions for an LLM (such as Claude) working with the code:
- Take these instructions in mind
- Look at existing code, type hints, docstrings and comments
- Propose changes to extend this document with new learnings
deezer-python-gql is an async typed Python client for Deezer's Pipe GraphQL API (pipe.deezer.com/api). It is a standalone library designed to be consumed by Music Assistant's Deezer provider, replacing the official deezer-python REST wrapper with a type-safe GraphQL client.
All client methods and response models are generated from the GraphQL schema and .graphql query files using ariadne-codegen. The only hand-written code is the base client (auth), the schema conversion script, and tests.
make setup— Install all dependencies (runtime + test + dev/codegen) and configure pre-commit hooks- Uses uv as the package manager (not pip directly)
- Always re-run
make setupafter pulling latest code
make generate— Fetch live schema frompipe.deezer.com, convert to SDL, run ariadne-codegen (full pipeline)make codegen— Convert existingschema.jsonto SDL and run ariadne-codegen (no live fetch, faster)make fetch-schema— Only fetch introspection JSON from the live API (no auth required)make clean-generated— Delete all generated code (deezer_python_gql/generated/)
uv run python scripts/explore.py queries/get_me.graphql— Run a.graphqlfile against the live APIuv run python scripts/explore.py -q '{ me { id } }'— Run an inline queryuv run python scripts/explore.py -q '...' -v '{"id": "123"}'— Pass variables as JSONmake explore Q=queries/get_me.graphql— Shorthand via Make- Requires a
.envfile withDEEZER_ARL=your_arl_value(.envis gitignored) - Handles JWT auth automatically via
DeezerBaseClient
make testoruv run pytest— Run all tests (pytest with coverage enabled by default)make lintoruv run pre-commit run --all-files— Run all linters and type checksuv run pytest tests/test_client.py— Run a specific test fileuv run mypy— Type check onlyuv run ruff check .— Lint onlyuv run ruff format --check .— Format check only
Always run make lint after a code change to ensure the new code adheres to the project standards.
ruff format— Code formattingruff check --fix— Linting with auto-fixcodespell— Spell checking (skips*.json,schema.graphql)check-ast— Python AST validationcheck-json— JSON validation (excludesschema.json)check-toml— TOML validationmypy— Strict type checking
deezer-python-gql/
├── deezer_python_gql/ # Package source
│ ├── __init__.py # Public API — exports DeezerGQLClient
│ ├── base_client.py # Hand-written: ARL→JWT auth, execute(), get_data(), check_audiobook_ids()
│ ├── py.typed # PEP 561 marker for type checkers
│ └── generated/ # AUTO-GENERATED by ariadne-codegen — never edit manually
│ ├── __init__.py
│ ├── base_client.py # Generated copy of base client (codegen artifact)
│ ├── base_model.py # Pydantic base model configuration
│ ├── client.py # DeezerGQLClient with typed methods (one per .graphql query)
│ ├── enums.py # GraphQL enum types used by queries
│ ├── fragments.py # Pydantic base classes from shared GraphQL fragments
│ ├── input_types.py # GraphQL input types used by mutations
│ ├── get_me.py # Response models for GetMe query
│ ├── get_track.py # Response models for GetTrack query
│ ├── get_album.py # Response models for GetAlbum query
│ ├── get_artist.py # Response models for GetArtist query
│ ├── get_playlist.py # Response models for GetPlaylist query
│ ├── get_livestream.py # Response models: livestream (radio station) details
│ ├── get_podcast.py # Response models: podcast with episodes and rights
│ ├── get_podcast_episode.py # Response models: single podcast episode with media
│ ├── get_audiobook.py # Response models: audiobook with paginated chapters
│ ├── get_audiobook_chapter.py # Response models: audiobook chapter with media
│ ├── search.py # Response models for Search query
│ ├── search_flows.py # Response models for SearchFlows query
│ ├── get_flow.py # Response models: user's default Flow
│ ├── get_flow_batch.py # Response models: 4 batched Flow track sets (aliased)
│ ├── get_flow_configs.py # Response models: mood & genre flow configs
│ ├── get_flow_config_tracks.py # Response models: tracks for a flow config
│ ├── get_made_for_me.py # Response models: SmartTracklist/Flow items
│ ├── get_smart_tracklist.py # Response models: smart tracklist with tracks
│ ├── get_similar_tracks.py # Response models: recommended/similar tracks
│ ├── get_artist_mix.py # Response models: artist mix tracks
│ ├── get_track_mix.py # Response models: track mix tracks
│ ├── get_charts.py # Response models: country charts
│ ├── get_recommendations.py # Response models: personalized recommendations
│ ├── get_user_charts.py # Response models: personal top tracks/artists/albums
│ ├── get_recently_played.py # Response models: recently played content
│ ├── get_favorite_artists.py # Response models: favorite artists
│ ├── get_favorite_albums.py # Response models: favorite albums
│ ├── get_favorite_tracks.py # Response models: favorite tracks
│ ├── get_favorite_playlists.py # Response models: favorite playlists
│ ├── get_favorite_podcasts.py # Response models: favorite podcasts
│ ├── get_favorite_audiobooks.py # Response models: favorite audiobook IDs
│ ├── get_podcast_episode_bookmarks.py # Response models: podcast bookmarks
│ ├── get_music_together_groups.py # Response models: Music Together groups
│ ├── get_music_together_group.py # Response models: single Music Together group
│ ├── get_music_together_affinity.py # Response models: Music Together affinity
│ ├── add_*_to_favorite.py # Mutation models: add artist/album/track/playlist/podcast/audiobook
│ ├── remove_*_from_favorite.py # Mutation models: remove favorites
│ ├── create_playlist.py # Mutation model: create playlist
│ ├── update_playlist.py # Mutation model: update playlist
│ ├── delete_playlist.py # Mutation model: delete playlist
│ ├── add_tracks_to_playlist.py # Mutation model: add tracks to playlist
│ ├── remove_tracks_from_playlist.py # Mutation model: remove tracks from playlist
│ ├── bookmark_podcast_episode.py # Mutation model: bookmark episode
│ ├── unbookmark_podcast_episode.py # Mutation model: unbookmark episode
│ ├── mark_as_played_podcast_episode.py # Mutation model: mark played
│ ├── mark_as_not_played_podcast_episode.py # Mutation model: mark not played
│ ├── music_together_create_group.py # Mutation model: create Music Together group
│ ├── music_together_join_group.py # Mutation model: join group
│ ├── music_together_leave_group.py # Mutation model: leave group
│ ├── music_together_refresh_suggested_tracklist.py # Mutation model: refresh tracks
│ ├── music_together_update_group_settings.py # Mutation model: update settings
│ └── music_together_generate_group_name.py # Mutation model: generate name
├── queries/ # GraphQL query/mutation documents (.graphql files)
│ ├── fragments.graphql # Shared fragments (TrackFields, ArtistFields, etc.)
│ ├── get_me.graphql # GetMe: current authenticated user
│ ├── get_track.graphql # GetTrack: full track details with media/lyrics
│ ├── get_album.graphql # GetAlbum: album details with paginated tracks
│ ├── get_artist.graphql # GetArtist: artist with top tracks and albums
│ ├── get_playlist.graphql # GetPlaylist: playlist with paginated tracks
│ ├── get_livestream.graphql # GetLivestream: livestream (radio station) details
│ ├── get_podcast.graphql # GetPodcast: podcast with episodes and rights
│ ├── get_podcast_episode.graphql # GetPodcastEpisode: episode with media and podcast ref
│ ├── get_audiobook.graphql # GetAudiobook: audiobook with paginated chapters
│ ├── get_audiobook_chapter.graphql # GetAudiobookChapter: chapter with media token
│ ├── search.graphql # Search: unified search across entity types
│ ├── search_flows.graphql # SearchFlows: discover all available Deezer flows via search
│ ├── get_flow.graphql # GetFlow: user's default Flow with tracks
│ ├── get_flow_batch.graphql # GetFlowBatch: 4 batched Flow track sets using aliases
│ ├── get_flow_configs.graphql # GetFlowConfigs: mood & genre flow config lists
│ ├── get_flow_config_tracks.graphql # GetFlowConfigTracks: tracks for a specific flow config
│ ├── get_made_for_me.graphql # GetMadeForMe: SmartTracklist & Flow items
│ ├── get_smart_tracklist.graphql # GetSmartTracklist: tracklist with paginated tracks
│ ├── get_similar_tracks.graphql # GetSimilarTracks: recommended tracks for a given track
│ ├── get_artist_mix.graphql # GetArtistMix: track mix from given artists
│ ├── get_track_mix.graphql # GetTrackMix: track mix from given tracks
│ ├── get_charts.graphql # GetCharts: country charts (tracks/albums/artists/playlists)
│ ├── get_recommendations.graphql # GetRecommendations: personalized recommendations
│ ├── get_user_charts.graphql # GetUserCharts: personal top tracks/artists/albums
│ ├── get_recently_played.graphql # GetRecentlyPlayed: recently played mixed content
│ ├── get_favorite_artists.graphql # GetFavoriteArtists: paginated favorite artists
│ ├── get_favorite_albums.graphql # GetFavoriteAlbums: paginated favorite albums
│ ├── get_favorite_tracks.graphql # GetFavoriteTracks: paginated favorite tracks
│ ├── get_favorite_playlists.graphql # GetFavoritePlaylists: paginated favorite playlists
│ ├── get_favorite_podcasts.graphql # GetFavoritePodcasts: paginated favorite podcasts
│ ├── get_favorite_audiobooks.graphql # GetFavoriteAudiobooks: favorite audiobook IDs
│ ├── get_podcast_episode_bookmarks.graphql # GetPodcastEpisodeBookmarks: bookmarked episodes
│ ├── get_music_together_groups.graphql # GetMusicTogetherGroups: user's Music Together groups
│ ├── get_music_together_group.graphql # GetMusicTogetherGroup: single group with tracks
│ ├── get_music_together_affinity.graphql # GetMusicTogetherAffinity: group member affinity
│ ├── favorites.graphql # Mutations: add/remove favorites (all entity types)
│ ├── playlists.graphql # Mutations: create/update/delete/add/remove playlist tracks
│ └── music_together.graphql # Mutations: create/join/leave/refresh/update/generate groups
├── schema.graphql # Full SDL schema (16,668 lines, ~915 types) — generated
├── schema.json # Raw introspection JSON — generated
├── scripts/
│ ├── convert_schema.py # Fetch introspection + fix broken types + convert to SDL
│ └── explore.py # Ad-hoc query runner for development (reads ARL from .env)
├── tests/
│ ├── test_client.py # Unit tests: client setup, model parsing from fixtures
│ └── fixtures/ # Recorded API responses (sanitized, never from live calls)
│ ├── get_me.json
│ ├── get_track.json
│ ├── get_album.json
│ ├── get_artist.json
│ ├── get_playlist.json
│ ├── get_livestream.json
│ ├── get_podcast.json
│ ├── get_podcast_episode.json
│ ├── get_audiobook.json
│ ├── get_audiobook_chapter.json
│ ├── search.json
│ ├── search_flows.json
│ ├── get_flow.json
│ ├── get_flow_batch.json
│ ├── get_flow_configs.json
│ ├── get_flow_config_tracks.json
│ ├── get_similar_tracks.json
│ ├── get_artist_mix.json
│ ├── get_track_mix.json
│ ├── get_made_for_me.json
│ ├── get_smart_tracklist.json
│ ├── get_charts.json
│ ├── get_recommendations.json
│ ├── get_user_charts.json
│ ├── get_user_playlists.json
│ ├── get_recently_played.json
│ ├── get_favorite_artists.json
│ ├── get_favorite_albums.json
│ ├── get_favorite_tracks.json
│ ├── get_favorite_playlists.json
│ ├── get_favorite_podcasts.json
│ ├── get_favorite_audiobooks.json
│ ├── get_podcast_episode_bookmarks.json
│ ├── get_music_together_groups.json
│ ├── get_music_together_group.json
│ ├── get_music_together_affinity.json
│ ├── add_*_to_favorite.json # One per entity type (artist, album, track, etc.)
│ ├── remove_*_from_favorite.json # One per entity type
│ ├── create_playlist.json
│ ├── update_playlist.json
│ ├── delete_playlist.json
│ ├── add_tracks_to_playlist.json
│ ├── remove_tracks_from_playlist.json
│ ├── bookmark_podcast_episode.json
│ ├── unbookmark_podcast_episode.json
│ ├── mark_as_played_podcast_episode.json
│ ├── mark_as_not_played_podcast_episode.json
│ ├── music_together_create_group.json
│ ├── music_together_join_group.json
│ ├── music_together_leave_group.json
│ └── music_together_generate_group_name.json
├── pyproject.toml # Project config (dependencies, ruff, mypy, pytest, codegen)
├── Makefile # Dev commands
├── .pre-commit-config.yaml # Local hooks (no remote repos)
├── cliff.toml # git-cliff changelog config
└── .github/workflows/ # CI: test.yml, publish-to-pypi.yml, release.yml
pipe.deezer.com/api → schema.json → schema.graphql → deezer_python_gql/generated/
(introspection) (raw JSON) (SDL, fixed) (typed client + models)
↑ ↑ ↑ ↑
fetch_introspection fix_introspection convert_to_sdl ariadne-codegen
(scripts/convert_schema.py) (reads queries/*.graphql)
- Introspection (no auth):
scripts/convert_schema.py --fetchsends the standard introspection query topipe.deezer.com/api. No authentication required. - Fix: Patches broken introspection results — missing
possibleTypeson unions, missinginterfaceson objects, truncatedNON_NULL/LISTtype wrappers. - SDL conversion: Uses
graphql-core'sbuild_client_schema+print_schemato produce clean SDL. - Codegen: ariadne-codegen reads
schema.graphql+ allqueries/*.graphqlfiles and generates the typed async client class with Pydantic response models.
queries/fragments.graphql defines reusable field sets shared across multiple queries:
| Fragment | Used by | Key fields |
|---|---|---|
TrackFields |
search, get_track, get_album, get_artist, get_playlist, flow, flow_batch, flow_config_tracks, smart_tracklist, charts, user_charts, fav_tracks, similar_tracks, artist_mix, track_mix, music_together | id, title, ISRC, diskInfo, duration, isExplicit, isFavorite, popularity, album, contributors |
ArtistFields |
search, get_artist, charts, user_charts, recommendations, fav_artists, recently_played | id, name, picture, fansCount, isFavorite, bio (summary + full) |
AlbumFields |
search, get_album, get_artist, charts, user_charts, recommendations, fav_albums, recently_played | id, displayTitle, type, cover, contributors, releaseDate, isExplicit, isFavorite, fansCount, label, copyright |
PlaylistFields |
search, get_playlist, charts, recommendations, fav_playlists, recently_played | id, title, picture, estimatedTracksCount, fansCount, isFavorite, description, owner |
LivestreamFields |
search, get_livestream | id, name, language, description, isOnStream, country, cover, media (url + codec) |
PodcastFields |
search, get_podcast, fav_podcasts | id, displayTitle, cover, description, isExplicit, isFavorite, type |
PodcastEpisodeFields |
search, get_podcast, get_podcast_episode, podcast_episode_bookmarks | id, title, description, duration, cover, publicationDate, media (url + codec) |
AudiobookFields |
get_audiobook | id, displayTitle, cover, description, duration, releaseDate, fansCount, isExplicit, isFavorite, chaptersCount, discsCount, producerLine, publisher, contributors |
AudiobookChapterFields |
get_audiobook, get_audiobook_chapter | id, isrc, displayTitle, diskInfo, duration, isExplicit, isFavorite |
PageInfoFields |
All paginated queries | hasNextPage, endCursor |
All entity queries use shared fragments. Every query that returns tracks, albums, artists, or playlists uses the corresponding fragment via ...TrackFields etc. Some queries (e.g., get_track.graphql, get_album.graphql) extend the fragment with additional fields like media, lyrics, url, or tracksCount.
ariadne-codegen generates fragments.py with Pydantic base classes (TrackFields, ArtistFields, etc.). Query model classes inherit from these via multiple inheritance, e.g., GetChartsChartsCountryTracksEdgesNode(TrackFields). This means fragment fields are type-safe and IDE-discoverable on all inheriting models. Consumers can type method parameters as TrackFields and accept any query-specific track model.
To use a fragment in a new query, reference it with ...TrackFields in the .graphql file.
- Create a
.graphqlfile inqueries/(e.g.,queries/get_user_favorites.graphql) - Define the query using types from
schema.graphql— reuse existing fragments where applicable - Run
make codegen(ormake generatefor a fresh schema) - ariadne-codegen produces:
- A new method on
DeezerGQLClient(e.g.,get_user_favorites()) - A new response model file in
generated/(e.g.,get_user_favorites.py)
- A new method on
- Add tests in
tests/
Same as queries — create a .graphql file with a mutation operation, run make codegen.
ARL cookie → POST auth.deezer.com/login/arl?jo=p&rto=c&i=c → JWT (6 min TTL)
↓
Bearer token in
Authorization header
↓
POST pipe.deezer.com/api
- The ARL cookie must be set on the
auth.deezer.comdomain (notwww.deezer.com) - Response is
Content-Type: text/plaincontaining JSON — parse withjson.loads(resp.text), notresp.json() - JWT expiration is decoded from the token payload (base64url-encoded second segment)
- Auto-refresh triggers 30 seconds before expiry
The DeezerBaseClient.get_data() method handles two important edge cases:
-
Partial errors: When the GraphQL response contains both
dataanderrors(e.g., deleted albums in a favorites list), the errors are logged but valid data is returned. Only responses with errors and no data raiseGraphQLClientGraphQLMultiError. -
Missing
__typenameinjection: The Deezer API omits__typenamefor single-member union types (e.g.,Contributoris alwaysArtist). Since Pydantic discriminated unions require this field,_inject_missing_typenames()recursively patches it into the response before model validation. The mapping is defined in_TYPENAME_PATCHES.
DeezerBaseClient self-manages its own httpx.AsyncClient, created lazily on first request. No external setup needed:
# Recommended: use as async context manager
async with DeezerGQLClient(arl="your_arl") as client:
me = await client.get_me()
# Or manage lifecycle manually
client = DeezerGQLClient(arl="your_arl")
try:
me = await client.get_me()
finally:
await client.close()An external http_client can still be passed for advanced use cases (e.g., sharing a pool across multiple clients), but the caller is then responsible for closing it.
The check_audiobook_ids(album_ids) method on DeezerBaseClient batch-checks whether album IDs are actually audiobooks. It builds a single aliased GraphQL query:
{ a0: audiobook(audiobookId: "123") { id displayTitle } a1: audiobook(audiobookId: "456") { id displayTitle } }Important: Querying only { id } is insufficient — the API echoes back the input ID for any valid album, regardless of whether it's an audiobook. The displayTitle field returns null (with AudiobookNotFoundError) for non-audiobooks, so the method checks displayTitle is not None to distinguish real audiobooks.
- Python: 3.12+ required (tested on 3.12, 3.13)
- Runtime dependencies:
httpx>=0.27.0,pydantic>=2.0.0— intentionally minimal - Dev dependencies (extras
[dev]):ariadne-codegen[subscriptions]— only needed for code generation - Test dependencies (extras
[test]): ruff, mypy, pytest, pytest-asyncio, pytest-cov, codespell, pre-commit - Package manager: uv (not pip)
- Build system: setuptools (declared in pyproject.toml)
| Setting | Value | Purpose |
|---|---|---|
schema_path |
schema.graphql |
SDL schema input |
queries_path |
queries |
Directory of .graphql query files |
target_package_name |
generated |
Output package name |
target_package_path |
deezer_python_gql |
Output parent directory |
client_name |
DeezerGQLClient |
Generated client class name |
base_client_name |
DeezerBaseClient |
Custom base client class name |
base_client_file_path |
deezer_python_gql/base_client.py |
Custom base client file |
async_client |
true |
Generate async methods |
include_all_inputs |
false |
Only generate inputs used by queries |
include_all_enums |
false |
Only generate enums used by queries |
plugins |
ShorterResultsPlugin |
Unwrap single-field responses for cleaner API |
- ruff handles all formatting and linting (config in
pyproject.toml,select = ["ALL"]with targeted ignores) - Line length: 100 characters
- Target: Python 3.12
deezer_python_gql/generatedandscripts/are excluded from ruff
The deezer_python_gql/generated/ directory is entirely auto-generated by ariadne-codegen. Never edit files in this directory manually. To change the generated output:
- Modify the
.graphqlquery files inqueries/ - Or modify
base_client.py(which codegen copies intogenerated/) - Then run
make codegen
Uses Sphinx-style docstrings consistent with Music Assistant:
def my_function(param1: str, param2: int) -> str:
"""Brief one-line description.
Optional longer description.
:param param1: Description of param1.
:param param2: Description of param2.
"""For simple functions, a single-line docstring is sufficient:
def get_item(self, item_id: str) -> Item:
"""Get an item by its ID."""Use comments only to explain "why", not "what". Reserve inline comments for complex multi-line blocks.
- All functions must have complete type annotations (enforced by mypy strict mode)
- Use
from __future__ import annotationsfor modern syntax in all files - Prefer
X | NoneoverOptional[X] - Use
cast()sparingly and only when the type system genuinely can't infer
- Tests are in
tests/ - Framework: pytest with pytest-asyncio (
asyncio_mode = "auto") - Coverage: enabled by default via
addopts = "--cov deezer_python_gql"in pyproject.toml - No live API calls — all tests use mocked HTTP or recorded JSON fixtures
Tests are organized into five layers:
| Layer | Tests | What's validated |
|---|---|---|
| Client setup | 3 | Import, instantiation with ARL, presence of all generated methods |
| Lifecycle | 3 | close() on internal client, skip close on external client, context manager |
| Auth flow (mocked) | 5 | JWT acquisition, token reuse, refresh on expiry, cookie domain, text/plain parsing |
| Error handling | 5 | HTTP errors, invalid JSON, missing data key, GraphQL errors, success path |
| Audiobook ID checking | 2 | Batch alias query returns correct subset; empty input returns empty set |
| Model smoke tests | 62 | One per query/mutation — fixture parses correctly with key fields accessible |
Auth tests mock httpx.AsyncClient to verify the hand-written DeezerBaseClient logic:
- JWT is acquired from
auth.deezer.com/login/arlon first request - Valid JWT is reused without re-auth
- Expiring JWT (within 30s margin) triggers refresh
- ARL cookie is sent to the correct domain (
auth.deezer.com, notwww.deezer.com) text/plainresponse body is correctly parsed as JSON
Error tests call get_data() directly with crafted httpx.Response objects:
GraphQLClientHttpErrorfor non-2xx status codesGraphQLClientInvalidResponseErrorfor non-JSON or structurally invalid responsesGraphQLClientGraphQLMultiErrorfor GraphQL-level errors with message/location/path
One test per query verifying the recorded JSON fixture parses through the generated Pydantic models. These catch regressions when re-running codegen after schema changes.
Fixtures live in tests/fixtures/ — recorded from the live API, then sanitized (user IDs, media tokens).
Fixtures must include __typename fields for discriminated union types:
"__typename": "Artist"on allContributorunion nodes"__typename": "Url"on allDeezerUrlinterface nodes
The explore.py script does not add __typename automatically — patch fixtures manually or with a script after recording.
def test_<what_is_being_tested>() -> None:
"""Describe the expected behavior."""Use Conventional Commits format:
<type>: <short summary>
<optional body>
Common types: feat, fix, docs, chore, refactor, test, ci.
git-cliff uses these prefixes to categorize changelog entries automatically (configured in cliff.toml).
| Workflow | Trigger | Purpose |
|---|---|---|
test.yml |
Push to main, PRs, or reusable |
Lint + type check + test on Python 3.12 & 3.13 |
release.yml |
Manual (workflow_dispatch) |
Run tests → bump version → generate changelog → GitHub Release |
publish-to-pypi.yml |
GitHub Release published | Build and publish to PyPI via pypa/gh-action-pypi-publish |
- Push conventional commits to
main(directly or via PR) - Go to Actions → Release → Run workflow (optionally force
major/minor/patch) - git-cliff auto-detects next version from commit types, generates changelog
- GitHub Release is created with tag → triggers PyPI publish automatically
main— single development branch, all PRs target this- No
dev/stablesplit (simple library, not a server)
- Endpoint:
https://pipe.deezer.com/api(POST, standard GraphQL) - Introspection: Enabled without auth (~915 types, 16,668 lines SDL)
- Auth: ARL → JWT Bearer token (6 min TTL, auto-refresh)
- Rate limiting: Not documented; GraphQL naturally batches, reducing request volume
- Schema stability: Undocumented, unofficial API — may change without notice
| Type | Key Fields |
|---|---|
Track |
id, title, ISRC, diskInfo, duration, album, contributors, lyrics, media (token + sizes), isFavorite, recommendedTracks, isAtmos, isExplicit |
Album |
id, displayTitle, type, cover, label, contributors, releaseDate, tracks, isFavorite, fallback |
Artist |
id, name, bio, picture, albums(types, roles), topTracks, relatedArtist, isFavorite |
Playlist |
id, title, description, picture, tracks(first, after, order), rawTracks, owner, isFavorite |
Livestream |
id, name, language, description, cover, country, media: [ExternalMedia!]! |
Podcast |
id, displayTitle, description, cover, type, isExplicit, isFavorite, episodes(first, after), hasRights |
PodcastEpisode |
id, title, description, duration, cover, publicationDate, media (url + codec), url, podcast |
Audiobook |
id, displayTitle, cover, description, duration, releaseDate, chaptersCount, discsCount, publisher, contributors, chapters(first, after) |
AudiobookChapter |
id, isrc, displayTitle, diskInfo, duration, gain, media (token + sizes + rights), audiobook |
Lyrics |
synchronizedLines, synchronizedWordByWordLines, text, copyright, writers |
Flow |
id, title, cover, tracks: [FlowTrack!]! (each call returns fresh batch) |
FlowConfig |
id, title, visuals, tracks: [FlowConfigTrack!]! |
TrackMedia |
id, version, token, estimatedSizes, rights: MediaRights! |
Search |
results.{tracks, artists, albums, playlists, podcasts, podcastEpisodes, livestreams}, topResult, mixedResults |
PrivateUser |
id, playlists, userFavorites, favorites (deprecated, used for audiobooks), flow, flowConfigs, recommendations, madeForMe, recentlyPlayed, charts, musicTogetherGroups, podcastEpisodeBookmarks |
MusicTogetherGroup |
id, name, isReady, isFamily, members, suggestedTracklist(mood), curatedTracklist, estimatedMembersCount |
- Favorites:
add/remove{Album,Artist,Track,Playlist,Podcast,Audiobook}ToFavorite/FromFavorite(audiobook mutations deprecated but functional) - Playlists:
createPlaylist,updatePlaylist,deletePlaylist,addTracksToPlaylist,removeTracksFromPlaylist - Podcasts:
bookmarkPodcastEpisode,unbookmarkPodcastEpisode,markAsPlayedPodcastEpisode,markAsNotPlayedPodcastEpisode - Music Together:
musicTogetherCreateGroup,musicTogetherJoinGroup,musicTogetherLeaveGroup,musicTogetherRefreshSuggestedTracklist,musicTogetherUpdateGroupSettings,musicTogetherGenerateGroupName - Not available: No listen logging mutation (
log.listenonly exists in GW API)
- ARL cookie domain must be
auth.deezer.com, notwww.deezer.com - Auth response is
Content-Type: text/plain— usejson.loads(resp.text) - Endpoint is
login/arl, notlogin/renew(which requires OAuth refresh token) - JWT TTL is 360 seconds — refresh proactively (30s margin implemented in base client)
The live introspection result has known issues that scripts/convert_schema.py auto-fixes:
- Union types may have empty
possibleTypes - Object types may be missing
interfacesfield NON_NULL/LISTtype wrappers may be truncated (missingofType)- These are fixed during the JSON→SDL conversion step