Unofficial fully-typed sync + async Python client for the MyDramaList app API
Project description
mdl
An unofficial, fully-typed, sync and async Python client for the private
MyDramaList app API (https://app-api.mydramalist.com/v1), reconstructed from the app's
reverse-engineered spec. Built on curl_cffi and
Pydantic v2.
Install as mydramalist-client; import as mdl.
Interoperability / research tool. This library targets an undocumented, private API discovered by decompiling the MyDramaList Flutter app. Endpoint paths and field names are inferred from machine code and may change without notice; models tolerate unknown/renamed fields but nothing is guaranteed. Use responsibly and in accordance with MyDramaList's Terms of Service. Not affiliated with or endorsed by MyDramaList.
Install
pip install mydramalist-client
The mdl-api-key
Despite its name, mdl-api-key is not a secret — the app fills it with a random 20-char
string generated on each launch, and the server does not validate it. This library generates
a valid one for you, so you do not need to extract or supply anything. You may still pin a
value via api_key= or MDL_API_KEY for reproducible requests, but it is optional. See
docs/api-key-extraction.md.
Heads up — Cloudflare: production sits behind Cloudflare bot protection that fingerprints the client's TLS handshake. A plain
httpx/requestsclient is served a403 "Just a moment..."challenge; only a client impersonating a real mobile/browser TLS fingerprint gets through. See Transport.
Quick start (sync)
from mdl import MDLClient
with MDLClient() as client: # api_key generated automatically
title = client.titles.get_title(686)
print(title.title, title.rating)
results = client.search.titles("signal")
for movie in results:
print(movie.id, movie.title)
Quick start (async)
import asyncio
from mdl import AsyncMDLClient
async def main():
async with AsyncMDLClient() as client:
title = await client.titles.get_title(686)
print(title.title)
asyncio.run(main())
Authentication
with MDLClient() as client:
client.auth.login("username", "password") # password is MD5-hashed for you
profile = client.account.get_profile() # bearer token attached automatically
print(profile.username)
Tokens are held in an in-memory store by default. Persist them with FileTokenStore:
from mdl import MDLClient, FileTokenStore
client = MDLClient(token_store=FileTokenStore("~/.mydramalist/token.json"))
The client clears stored tokens on 401 and raises MDLAuthError. Automatic
token refresh via refresh_token is not yet implemented — callers must
re-authenticate when the access token expires.
Transport
Production MyDramaList sits behind Cloudflare bot protection that fingerprints the
client's TLS/HTTP2 handshake (JA3/JA4). This is the real access gate — not the mdl-api-key.
A plain httpx/requests client (a stock Python TLS stack) is served Cloudflare's
403 "Just a moment..." challenge on every request, regardless of headers or User-Agent.
Only a client that impersonates a real mobile/browser TLS fingerprint is allowed through — in
testing, curl_cffi with impersonate="safari_ios"
(or chrome) reaches the API and returns real JSON.
If you get 403 responses with a "Just a moment..." HTML body, this is why: the request
never reached the API, it was stopped at Cloudflare's edge.
Resource groups
The client exposes one attribute per API area, e.g.:
| Attribute | Area |
|---|---|
client.auth |
login, social auth, register, reset password |
client.account |
own profile/settings, verification, devices, payments |
client.users |
other users & people pages, credits, likes |
client.titles |
title detail, progress, reviews, recommendations, credits, genres |
client.search |
title & people search |
client.explore |
trending / top_airing / upcoming / recommended feeds |
client.reviews |
review CRUD, voting |
client.comments |
comment listing & CRUD |
client.watchlist |
watchlist sync (add/remove/status lists) |
client.custom_lists |
custom lists, items, votes |
client.feeds |
activity feed, posts, embeds, uploads |
client.articles |
articles |
client.friends / client.messages / client.notifications / client.groups |
social |
client.subscription / client.flowers / client.awards / client.reports |
misc |
client.calendar / client.leaderboard |
calendar & leaderboard |
Development
The async implementation under src/mdl/_async is the source of truth; the sync
package src/mdl/_sync is generated from it with
unasync:
pip install -e ".[dev]"
python scripts/build_sync.py # regenerate _sync after editing _async
python scripts/check_sync.py # verify _sync is up to date (CI does this)
ruff check src tests
mypy
pytest
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mydramalist_client-0.1.0.tar.gz.
File metadata
- Download URL: mydramalist_client-0.1.0.tar.gz
- Upload date:
- Size: 174.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4053da058eb1735360f374ef7b3c4c8fc7f1045b4239bbed311ccd2b7c6ce5e6
|
|
| MD5 |
061b4979cb55774e517777515ac27927
|
|
| BLAKE2b-256 |
ce9cabb8fe86525b115ae368114dbb5b85442492616da7d39a8fecc700aff04e
|
Provenance
The following attestation bundles were made for mydramalist_client-0.1.0.tar.gz:
Publisher:
ci.yml on danieyal/pymdl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mydramalist_client-0.1.0.tar.gz -
Subject digest:
4053da058eb1735360f374ef7b3c4c8fc7f1045b4239bbed311ccd2b7c6ce5e6 - Sigstore transparency entry: 2142892708
- Sigstore integration time:
-
Permalink:
danieyal/pymdl@04ded771d7e410e6d958dff38a33ecb29c54a2f1 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/danieyal
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@04ded771d7e410e6d958dff38a33ecb29c54a2f1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file mydramalist_client-0.1.0-py3-none-any.whl.
File metadata
- Download URL: mydramalist_client-0.1.0-py3-none-any.whl
- Upload date:
- Size: 52.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ec4129118a897f2892d7356a3a2e8332a8c84bdcf0efc326762b45f2f05f9821
|
|
| MD5 |
2566bedbd723c22fe5cc784906214b82
|
|
| BLAKE2b-256 |
06c97994f3c0241647da6374e7ee717ab1b436cb39e4633f9b232d48137f2951
|
Provenance
The following attestation bundles were made for mydramalist_client-0.1.0-py3-none-any.whl:
Publisher:
ci.yml on danieyal/pymdl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mydramalist_client-0.1.0-py3-none-any.whl -
Subject digest:
ec4129118a897f2892d7356a3a2e8332a8c84bdcf0efc326762b45f2f05f9821 - Sigstore transparency entry: 2142892795
- Sigstore integration time:
-
Permalink:
danieyal/pymdl@04ded771d7e410e6d958dff38a33ecb29c54a2f1 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/danieyal
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@04ded771d7e410e6d958dff38a33ecb29c54a2f1 -
Trigger Event:
push
-
Statement type: