Documentation

Widevine CDM API reference

Five public endpoints, one JSON shape, and a cache that makes repeat lookups free. Everything below is live on https://cdmapi.com.

Quickstart

# 1 — resolve keys for a PSSH
curl -sS -X POST https://cdmapi.com/api/v1/keys \
  -H "X-API-Key: wb_live_…" -H "Content-Type: application/json" \
  -d '{"pssh":"AAAAOnBzc2g…","license_url":"https://…/license"}'

# 2 — ask the cache only (no licence call)
curl -sS -X POST https://cdmapi.com/api/v1/keys \
  -H "X-API-Key: wb_live_…" \
  -d '{"pssh":"AAAAOnBzc2g…"}'

# 3 — search everything you have captured
curl -sS "https://cdmapi.com/api/v1/vault?q=86cd972ae6101ee98a5b257978ee9992" \
  -H "X-API-Key: wb_live_…"

Authentication

Send any of these on every request. Keys are issued per user and can be revoked or rotated without affecting others.

X-API-Key: wb_live_…
Authorization: Bearer wb_live_…
X-Secret-Key: wb_live_…        # pywidevine serve clients
?key=wb_live_…                 # query string, for quick tests

Endpoints

POST /api/v1/keys

Resolve keys for a PSSH. Omit license_url to query the cache only.

FieldTypeNotes
psshstringBase64 pssh box or raw widevinePsshData — both accepted.
license_urlstringOptional. Without it the request becomes a cache lookup and answers 404 cache_miss if unknown.
mpd_url, service_urlstringStored on the cache row so the capture is self-describing.
headersobjectSent with the licence POST. Missing Content-Type triggers automatic negotiation on 415.
license_bodystringraw (default), b64_json, b64_plain, json_raw.
service_certificatestringBase64 certificate; returns provider_id.
use_cacheboolDefault true.

POST /api/v1/roundtrip

Complete a licence exchange from a challenge you already hold — the path a browser extension uses when the player abandons its own request.

FieldTypeNotes
session_idstringFrom /{device}/open.
challengestringBase64 challenge returned by get_license_challenge.
license_urlstringRequired here — it is the endpoint the browser used.
headersobjectCaptured request headers. Origin/Referer/User-Agent are injected when absent.
mpd_url, page_url, pssh_rawstringRecorded on the cache row.

GET /api/v1/vault

Search the cache by PSSH, KID, key, MPD, licence URL, page or username. A KID hit returns every key from that request.

QueryNotes
qThe search term. Empty returns the newest rows.
limitDefault 200, newest first.

GET /cdm · /cdm/config · /cdm/details

Return the caller's own client config, paste-ready:

{ "name": "cdmapi.com", "device_name": "ChromeCDM", "device_type": "Chrome",
  "system_id": 40379, "security_level": 3, "host": "https://cdmapi.com", "secret": "wb_live_…" }

pywidevine serve surface

For RemoteCdm and devine-style tools, authenticated with X-Secret-Key.

Method & pathBody
GET /{device}/open— → {session_id, device{system_id,security_level}, details}
POST /{device}/set_service_certificate{session_id, certificate} → provider_id
POST /{device}/get_license_challenge/STREAMING{session_id, init_data, privacy_mode} → challenge_b64
POST /{device}/parse_license{session_id, license_message}
POST /{device}/get_keys/CONTENT{session_id} → keys[{key_id,key,type}]
GET /{device}/close/{session_id}—

Error codes

Every failure uses the same envelope: {"ok":false,"error":{"code":"…","message":"…"}}.

unauthorized            401  missing or unknown API key
expired / disabled      401/403  key exists but cannot be used
bad_pssh                400  PSSH is not a valid box or widevinePsshData
bad_request             400  a required field is missing
cache_miss              404  no cached keys and no license_url supplied
concurrency_limit       429  your per-user limit is saturated
global_busy             503  the shared CDM pool is saturated
license_http            502  the provider refused the licence POST
license_invalid         502  the licence did not parse as a SignedMessage
license_unreachable     502  network error contacting the licence server
engine_*                503  CDM unavailable, busy or misconfigured
no_keys                 502  licence parsed but returned no content keys

Clients

pywidevine RemoteCdm

from pywidevine.remotecdm import RemoteCdm

cdm = RemoteCdm(
    device_type="CHROME",
    system_id=40379,
    security_level=3,
    host="https://cdmapi.com",
    secret="wb_live_…",
    device_name="ChromeCDM",
)

session = cdm.open()
challenge = cdm.get_license_challenge(session, pssh)

The endpoint reports Server: pywidevine serve v1.4.3, which the client version check requires.

Key vault CLI

export WB_SERVER=https://cdmapi.com
export WB_API_KEY=wb_live_…

python3 vault.py --kid 86cd97…
python3 vault.py --pssh AAAAOnBz…
python3 vault.py 86cd97… --json

Exit 0 when something matched, 1 when nothing did, 2 on a request error.

Browser extension

Our cdmapi.com extension sniffs the PSSH, licence endpoint and manifest headers, asks the cache first, and pushes anything new. Captures land in the dashboard with their MPD and headers intact.

Acceptable use

cdmapi.com is a developer tool for analysing and testing DRM-protected content you are authorised to access — your own catalogue, your own accounts, or content you hold rights for. You are responsible for every request made with your key. Do not use it to redistribute protected works or to bypass access controls you have no right to bypass. Keys cached on your account are visible to you and to the operator for support purposes.

Privacy & terms

Data. Requests are logged with timestamp, API key label, PSSH, cache status, latency and originating IP, and successful captures are stored so the cache and vault can work. Captured licence headers are stored with the cache row. Nothing is sold.

Retention. Cache rows expire after the configured TTL (24–48 h by default). Request logs are kept for abuse investigation.

Liability. The service is provided as-is, without warranty. You indemnify the operator against claims arising from your use.

Ready to send your first PSSH?

Create a key, paste it into the examples above, and the dashboard will show every capture with its MPD, licence endpoint and headers.