Skip to main content

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/requests client is served a 403 "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
    client.auth.login("username", "password")   # title & search endpoints need a login
    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:
        await client.auth.login("username", "password")
        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)

Most endpoints need a logged-in client. Since October 2026 the API rejects anonymous requests to title detail, credits, recommendations, search, explore feeds and every account/sync endpoint with a bare 400 Bad Request; the client raises MDLLoginRequiredError (a subclass of MDLAuthError) for these. Genres, tag search, articles, reviews and public custom lists still work without logging in.

Rejected credentials raise MDLAuthError — the API reports them as 400 "Invalid password or username.", not 401.

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

Access tokens last 30 days. When a request gets a 401, the client exchanges the stored refresh token for a new pair once and retries; a FileTokenStore saves the new pair. If there is no refresh token or the refresh is rejected, the stored tokens are cleared and MDLAuthError is raised, so log in again. Call client.auth.refresh() to refresh ahead of expiry.

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

Metadata

Release files for mydramalist-client 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mydramalist-client 0.3.0
File Size Uploaded Provenance
mydramalist_client-0.3.0.tar.gz 181.1 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for mydramalist-client 0.3.0
File Interpreter ABI Platform
mydramalist_client-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 237.2 kB

Release files / mydramalist_client-0.3.0.tar.gz

Download URL mydramalist_client-0.3.0.tar.gz
Size 181.1 kB
Tags Source
SHA-256 checksum
How to use checksums
cfa7ae8ad1e54954e542dac7af294805f3a3353158d614a30fcd9c688e78bbe8
BLAKE2b-256 checksum
How to use checksums
8551d12041d326a0b9b1d6a8bdf2daa17e48477fba0409b7bc8a9c8bdc9bc9b4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Release files / mydramalist_client-0.3.0-py3-none-any.whl

Download URL mydramalist_client-0.3.0-py3-none-any.whl
Size 56.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
17d16ff25ea4b05fc12ed91cf0bc0d27e0aa550ea0e00341769fa03277b4b6a3
BLAKE2b-256 checksum
How to use checksums
cbf46d942e6647a85651c7e6e332c3ec9024971d8b7aa4a4d7d0c668ef5a0ce9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Security for mydramalist-client 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for mydramalist_client-0.3.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page