API Documentation

Which endpoint to use? One-shot API Quick start & token Amazon Prime example Chrome Extension Recipe: Brightcove Error codes

0. Which endpoint should I use?

CDMPOOL exposes two different flows for extracting DRM keys. Choosing the wrong one is the #1 source of failed extractions. Read this before doing anything else.

πŸ” POST /api/mpd-analyze β€” MPD sanity-check

Send us only a DASH manifest URL. We fetch it, parse it and return a suggestion bundle: detected DRM, Widevine + PlayReady PSSH, KID, resolutions, best-guess license URL and provider (EZDRM, Axinom, Pallycon, Kinescope…).

Free & rate-limited only β€” no credit consumed. Ideal to run before /api/extract so you don't waste a call on a typo. The Extract UI already wires this into a non-intrusive "πŸ” Analyze" button.

🟒 POST /api/extract β€” one-shot

CDMPOOL performs the HTTP call to the license server itself. You send us the PSSH + license URL + headers, we do everything, we return the keys.

Good for: everything β€” including Amazon Prime Video (with cookies), Bitmovin/Shaka test assets, Axinom DRMtoday, EZDRM, IPTV feeds, Brightcove-brokered channels, DStv-style Bearer-authed streams. CDMPOOL auto-handles TLS impersonation (chrome120) and JSON-envelope unwrapping for Amazon Prime.

Fails only when the session cookies you pass are expired or don't match the target region β€” never because of the caller IP. CDMPOOL proved end-to-end extraction from our Netherlands VPS on Amazon Prime FR/CA once the correct cookies are supplied.

🟣 POST /api / POST /pr/api β€” advanced 3-step (legacy)

Same crypto, but you POST the challenge to the license server yourself. Kept only for compatibility with existing browser extensions and the cdrm-project.com API. For 99% of services (including Amazon Prime, Netflix, Disney+, DStv…) the one-shot flow above is now the right answer.

Rule of thumb: use POST /api/extract. Provide cookies when the service requires auth (Amazon Prime, Disney+, Netflix, DStv…). CDMPOOL handles the TLS impersonation, JSON-envelope unwrapping and key derivation for you.

0. Manifest analyzer β€” POST /api/mpd-analyze

πŸ” Free, read-only, no credit consumed. Send us a DASH/HLS manifest URL β€” we return everything we could parse (PSSH, KID, DRM provider, resolutions, suggested license URL). Ideal to validate your inputs before spending a real /api/extract call.

Powers the "πŸ” Analyze" button on the Extract UI β€” but you can also hit it directly from your own tooling. The Extract UI displays each returned field as a chip that the user can click to fill the corresponding form input; existing values are never overwritten silently.

curl -X POST https://cdmpool.xyz/api/mpd-analyze \
  -H "Content-Type: application/json" \
  -d '{"mpd_url":"https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine/dash.mpd"}'

Successful response (ok:true):

{
  "ok": true,
  "manifest_type": "dash",           // "dash" | "hls" | "unknown"
  "drm":           "widevine",       // best guess (widevine has priority)
  "pssh":          "AAAAPnBz...",    // convenience default (widevine if any)
  "pssh_widevine": "AAAAPnBz...",
  "pssh_playready":null,
  "kid":           "800aacaa-5229-58ae-8880-62b5695db6bf",
  "license_url":   null,             // filled when the PR PSSH carries a LA_URL
                                     //   OR when the provider is recognised
  "drm_provider":  null,             // ezdrm|axinom|pallycon|kinescope|amazon|…
  "resolutions":   ["192x144","320x240","480x360","640x480","768x576"]
}

Failure response (ok:false) β€” never raises, always JSON:

{
  "ok":           false,
  "fetch_status": 403,
  "hint":         "The CDN refused our request (HTTP 403). This usually
                   means the MPD is behind auth (signed cookies, session,
                   IP-lock). Try our platform-specific kits from /downloads."
}

What we can auto-detect

Rate limiting: shared with the extract dedup cache (60s TTL per identical mpd_url). Repeat calls within the window return the cached result to save our bandwidth and resources.

1. One-shot API β€” POST /api/extract

βœ… One request, one response. CDMPOOL calls the license server on your behalf (with TLS impersonation, cookie relay and JSON-envelope unwrapping) and returns the keys. Works with Amazon Prime Video, Bitmovin, Axinom, DStv, Brightcove, EZDRM and anything else that accepts a normal license request with proper session cookies.

CDMPOOL does GetChallenge, calls the license server, unwraps JSON envelopes when needed, and returns the derived keys β€” all in a single round-trip.

curl -X POST https://cdmpool.xyz/api/extract \
  -H "Content-Type: application/json" \
  -d '{
    "token":       "<your_api_token>",
    "drm":         "widevine",
    "pssh":        "AAAA... (base64 from <cenc:pssh>)",
    "license_url": "https://example.com/license",
    "headers":     {"Authorization":"Bearer …", "X-Custom":"…"},
    "cookies":     {"session":"…"},
    "channel_name":"Optional label",
    "mpd_url":     "https://.../manifest.mpd"
  }'

Success (HTTP 200):

{
  "ok":         true,
  "keys":       [{"kid":"…","key":"…"}, ...],
  "ms":         340,
  "session_id": "…"
}

On success, CDMPOOL also silently stores the extraction under /me/extractions together with an xaccel_url (mpd_url + ?decryption_key=KID:KEY) that plays directly in VLC / IPTV Smarters. That ready-to-play URL is not returned in the API body β€” read it from the extractions library.

Failure (HTTP 4xx/5xx):

{
  "ok": false,
  "step": "license_request",
  "status": 403,
  "error_code": "E_LICENSE_FORBIDDEN",
  "hint": "License server returned 403 Forbidden. Likely missing auth
           header, missing Origin/Referer, or expired session."
}

Fields headers, cookies, channel_name, mpd_url, source_url, country, note are optional. Only token, drm, pssh and license_url are required.

πŸ”„ Automatic PlayReady fallback. When you request Widevine and the license server explicitly rejects our CDM (e.g. Amazon Prime NO_PLAYBACK_STREAMS_AVAILABLE_OVER_SECURE_TRANSPORT, Netflix device_not_supported), CDMPOOL automatically retries the request in PlayReady using the PSSH from the same MPD (if mpd_url was provided). On success the response includes:
"auto_retry": {
  "attempted": true,
  "from_drm":  "widevine",
  "to_drm":    "playready",
  "ok":        true,
  "pssh_source": "mpd"
}
Auth/session/geo/timeout failures do not trigger the retry β€” they fail identically in PlayReady, so we don't add latency. For maximum auto-recovery, always pass "mpd_url" in your requests.

cURL β€” parse keys with jq

curl -s -X POST https://cdmpool.xyz/api/extract \
  -H 'Content-Type: application/json' \
  -d '{
    "token":       "YOUR_TOKEN",
    "drm":         "widevine",
    "pssh":        "AAAAW3Bzc2g...",
    "license_url": "https://cwip-shaka-proxy.appspot.com/no_auth"
  }' | jq -r '.keys[] | "--key \(.kid):\(.key)"'
# β†’ --key 62e6b3fd5b6c4c9c9f22a1e5f9a7d820:8f2b1e...

Python example

import requests

r = requests.post("https://cdmpool.xyz/api/extract", json={
    "token":       "YOUR_TOKEN",
    "drm":         "widevine",
    "pssh":        "AAAAW3Bzc2g...",
    "license_url": "https://cwip-shaka-proxy.appspot.com/no_auth",
})
d = r.json()
if d["ok"]:
    for k in d["keys"]:
        print(f"--key {k['kid']}:{k['key']}")
else:
    print(f"[{d['error_code']}] {d['hint']}")

Node.js example

const r = await fetch("https://cdmpool.xyz/api/extract", {
  method: "POST",
  headers: {"Content-Type": "application/json"},
  body: JSON.stringify({
    token:       "YOUR_TOKEN",
    drm:         "widevine",
    pssh:        "AAAAW3Bzc2g...",
    license_url: "https://cwip-shaka-proxy.appspot.com/no_auth",
    headers:     {"X-AxDRM-Message": "eyJ..."}   // optional per-service auth
  })
}).then(r => r.json());

if (r.ok) {
  for (const k of r.keys) console.log(`--key ${k.kid}:${k.key}`);
} else {
  console.error(`[${r.error_code}] ${r.hint}`);
}

2. Quick start & token info

Every request needs your personal API token. Find it in My account after signing in. Each successful extraction consumes 1 unit from your daily quota (5/day on Free, unlimited on VIP).

Pass the token in the JSON body as "token" for /api/extract, or as the api-key header for /extension. Select the DRM system per call with "drm": "widevine" or "drm": "playready".

3. Amazon Prime Video β€” full pipeline example

Good news! As of Feb 2026 CDMPOOL supports Amazon Prime end-to-end via a single POST /api/extract call β€” we auto-detect atv-ps.primevideo.com / atv-ps.amazon.com URLs and:

Important: Amazon rotates the KID on every MPD fetch. You must capture the PSSH from a fresh manifest and submit within the same session, otherwise you will get PRS.InvalidRequest.

Recommended flow β€” PlayReady, one shot (works from any IP):

import requests
from curl_cffi import requests as cffi   # pip install curl_cffi

ASIN         = "amzn1.dv.gti.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
MARKETPLACE  = "A3K6Y4MI8GDYMT"           # EU. NA=ATVPDKIKX0DER
DEVICE_ID    = "fdd8c346-7883-430a-9fc3-dcdf4bf4df74"
COOKIES      = {                          # export from your browser (Netscape or dict)
    "at-main-av":  "Atza|...",           #   auth token
    "ubid-main-av":"131-...",             #   session id
    "session-id":  "143-...",
    "session-token":"...",
}

# 1) Ask Amazon for the current playback URL & manifest
qs = {
    "deviceID": DEVICE_ID, "deviceTypeID": "AOAGZA014O5RE",
    "firmware": "1", "gascEnabled": "false", "marketplaceID": MARKETPLACE,
    "playerType": "xp", "operatingSystemName": "Windows",
    "operatingSystemVersion": "10.0", "deviceApplicationName": "EdgeNext",
    "asin": ASIN, "consumptionType": "Streaming",
    "desiredResources": "PlaybackUrls", "resourceUsage": "CacheResources",
    "videoMaterialType": "Feature", "displayWidth": "2560", "displayHeight": "1440",
    "deviceStreamingTechnologyOverride": "DASH", "deviceDrmOverride": "CENC",
}
base = "https://atv-ps.primevideo.com/cdp/catalog/GetPlaybackResources"
r = cffi.get(base, params=qs, cookies=COOKIES, impersonate="chrome120")
url_set = r.json()["playbackUrls"]["urlSets"]
mpd_url = url_set[next(iter(url_set))]["urls"]["manifest"]["url"]

# 2) Fetch MPD, extract the current PlayReady PSSH
import re
mpd = cffi.get(mpd_url, cookies=COOKIES, impersonate="chrome120").text
pssh = re.search(
    r'<ContentProtection[^>]*schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95"[^>]*>'
    r'.*?<cenc:pssh[^>]*>([^<]+)</cenc:pssh>',
    mpd, re.DOTALL | re.IGNORECASE).group(1).strip()

# 3) Send everything to CDMPOOL β€” one call, one response with the keys
qs_lic = {**qs, "desiredResources": "PlayReadyLicense",
          "resourceUsage": "ImmediateConsumption"}
license_url = base + "?" + "&".join(f"{k}={v}" for k, v in qs_lic.items())

result = requests.post("https://cdmpool.xyz/api/extract", json={
    "token":         YOUR_CDMPOOL_TOKEN,
    "drm":           "playready",
    "pssh":          pssh,
    "license_url":   license_url,
    "headers":       {},          # nothing extra required
    "cookies":       COOKIES,     # <-- CDMPOOL passes them to Amazon for you
    "channel_name":  "Prime Video CA β€” my extraction",
}).json()

print(result["keys"])   # [{"kid": "...", "key": "..."}]

The full runnable version (with cookie loader, ASIN discovery and offline Widevine fallback) is in the Amazon Prime Video kit. Widevine still requires an L1 CDM which CDMPOOL does not currently host β€” prefer PlayReady for Amazon Prime.

4. Chrome Extension β€” POST /extension

⚠️ Beta. The extension captures license bytes generated by the browser's local CDM, which produces a session state CDMPOOL's server-side CDM cannot import. It only works with extension builds that ship a patched pywidevine (rare). For daily use, prefer POST /api/extract with cookies β€” same result, no browser side glue.

Endpoint reference:

POST https://cdmpool.xyz/extension
Header:  api-key: <YOUR_API_TOKEN>
Content-Type: application/json

Body:
{
  "init_data":        "<PSSH base64>",
  "license_request":  "<optional>",
  "license_response": "<base64 license response>"
}

Response:
{ "message": "success", "keys": "--key KID:KEY\n--key KID:KEY" }

Extension setup β€” edit license.json inside the extension folder:

{
  "api_url": "https://cdmpool.xyz/extension",
  "api_key": "<YOUR_API_TOKEN>"
}

5. Recipe: Brightcove-brokered streams (TΓ©lΓ©-QuΓ©bec, TSN, MLB…)

Brightcove exposes a public policy key that lets the license request succeed without cookies. Perfect for a one-shot /api/extract call:

import re, requests

CDMPOOL, TOKEN = "https://cdmpool.xyz", "<YOUR_API_TOKEN>"
PAGE = "https://telequebec.tv/regarder/en-direct/jeunesse"

# 1) Discover Brightcove account/player/media IDs from the public page
html = requests.get(PAGE).text
acc  = re.search(r'brightcoveAccountId\\?"[,:\\"]*([0-9]+)', html).group(1)
pid  = re.search(r'brightcovePlayerId\\?"[,:\\"]*([A-Za-z0-9]+)', html).group(1)
med  = re.search(r'brightcoveMediaId\\?"[,:\\"]*([0-9]+)', html).group(1)

# 2) Grab the policy key baked in the Brightcove player JS
js   = requests.get(f"https://players.brightcove.net/{acc}/{pid}_default/index.min.js").text
pk   = re.search(r'BCpkAD[A-Za-z0-9._-]{40,}', js).group(0)

# 3) Ask Brightcove for the playback info (returns MPD + license URL)
pb   = requests.get(
    f"https://edge.api.brightcove.com/playback/v1/accounts/{acc}/videos/{med}",
    headers={"Accept": f"application/json;pk={pk}"},
).json()
src  = next(s for s in pb["sources"] if s["src"].endswith(".mpd")
                                     and "com.widevine.alpha" in s.get("key_systems", {}))
mpd_url = src["src"]
lic_url = src["key_systems"]["com.widevine.alpha"]["license_url"]

# 4) Extract the Widevine PSSH from the MPD
mpd  = requests.get(mpd_url).text
pssh = re.search(
    r'urn:uuid:edef8ba9[^"]*"[^>]*>\s*<cenc:pssh[^>]*>([^<]+)',
    mpd).group(1)

# 5) One-shot extraction via CDMPOOL β€” done
out = requests.post(f"{CDMPOOL}/api/extract", json={
    "token": TOKEN, "drm": "widevine",
    "pssh": pssh, "license_url": lic_url,
    "mpd_url": mpd_url,
    "channel_name": "TΓ©lΓ©-QuΓ©bec β€” Jeunesse",
}).json()

for k in out["keys"]:
    print(f"--key {k['kid']}:{k['key']}")
# Ready-to-play xaccel URL is available in /me/extractions after each success.

Adapting to other services β€” replace the "discovery" step (1-3) with the one specific to the service (e.g. Amazon Prime uses atv-ps.primevideo.com/cdp/catalog/GetPlaybackResources). The /api/extract call in step 5 is always the same regardless of the target service β€” CDMPOOL detects the license server format and handles the crypto for you.

6. Error responses & error codes

HTTP status returned by /api/extract, /api and /pr/api

StatusMeaning
200Success β€” response contains keys (or challenge+session_id on the low-level endpoints)
400Bad PSSH / missing license_url β€” see error_code
403Missing or invalid member API token
429Daily quota reached (5/day on Free β€” upgrade to VIP for unlimited)
495SSL/TLS handshake with the license server failed
502License server unreachable (DNS / connection refused)
504License request timed out (30 s limit)

Every failing response includes a stable error_code + a human hint

Sample failing response:

{
  "ok": false,
  "step": "challenge",
  "status": 400,
  "error_code": "E_PSSH_NOT_BASE64",
  "hint": "PSSH is not valid base64. Copy the exact base64 string inside
           <cenc:pssh>…</cenc:pssh> in the MPD (Widevine block,
           schemeIdUri urn:uuid:edef8ba9-…-21ed). No line breaks, no XML tags."
}

All error codes (branch on error_code, never on the hint text)

CodeStepWhat went wrong & how to fix
E_INVALID_TOKENauthMissing or wrong token field.
E_QUOTA_REACHEDauthDaily quota hit (Free = 5/day). Upgrade to VIP.
E_BAD_DRMinputdrm must be widevine or playready.
E_LICENSE_URL_MISSINGinputYou forgot the license_url field.
E_BAD_HEADERSinputheaders must be a JSON object (dict), not a string.
E_BAD_COOKIESinputcookies must be a JSON object {name: value}.
E_PSSH_MISSINGchallengeField pssh is empty β€” pass the base64 PSSH from the MPD.
E_PSSH_NOT_BASE64challengePSSH is not valid base64 β€” no line breaks, no XML tags, no URL.
E_PLAYREADY_BAD_INITchallengePlayReady init is empty or not a base64-encoded WRMHEADER XML.
E_CHALLENGE_BAD_REQUESTchallengeCDM rejected the request β€” check PSSH matches the DRM you picked.
E_APIMAIN_AUTHchallengeInternal APIMAIN token issue β€” contact support.
E_CHALLENGE_UNKNOWNchallengeUnclassified CDM error β€” inspect the body/description.
E_LICENSE_UNAUTHORIZEDlicense_requestLicense 401 β€” add Authorization / Cookie in headers JSON.
E_LICENSE_FORBIDDENlicense_requestGeneric 403 β€” likely missing auth (Authorization / Cookie / Origin) or expired session headers.
E_LICENSE_TOKEN_EXPIREDlicense_requestSession token expired (5-15 min TTL) β€” capture fresh headers.
E_LICENSE_NOT_ENTITLEDlicense_requestNot a paying subscriber for this content.
E_LICENSE_MISSING_ORIGINlicense_requestMissing Origin / Referer / User-Agent β€” add them in headers JSON.
E_LICENSE_NOT_FOUNDlicense_requestLicense URL 404 β€” the endpoint path is wrong.
E_LICENSE_BAD_CHALLENGElicense_requestLicense 400 saying the challenge is malformed β€” mismatched PSSH or missing service-specific header.
E_LICENSE_BAD_REQUESTlicense_requestOther 400 β€” see body.
E_LICENSE_RATE_LIMITEDlicense_requestLicense 429 β€” wait 1-5 minutes before retrying.
E_LICENSE_REDIRECTlicense_request301/302/307/308 β€” use the FINAL URL after redirect.
E_LICENSE_UPSTREAM_DOWNlicense_requestLicense 5xx β€” retry later.
E_LICENSE_UNKNOWNlicense_requestOther license failures.
E_LICENSE_UNREACHABLEconnection_errorDNS failure / connection refused / RemoteDisconnected.
E_LICENSE_SSLssl_errorCert verification failed β€” bad cert on the license server.
E_LICENSE_TIMEOUTtimeout> 30 s β€” license server too slow or unreachable.
E_KEYS_DERIVATIONget_keysCDM couldn't derive keys β€” retry with a fresh session.
E_INTERNALexceptionUnexpected server error β€” details in the body.

Programmatic branching (Python)

r = requests.post("https://cdmpool.xyz/api/extract", json=payload).json()
code = r.get("error_code", "")
if code == "E_LICENSE_UNAUTHORIZED":
    payload["headers"]["Authorization"] = "Bearer " + fresh_token()
elif code in ("E_PSSH_MISSING", "E_PSSH_NOT_BASE64"):
    print("Fix your PSSH:", r["hint"])
elif code == "E_LICENSE_RATE_LIMITED":
    time.sleep(60)