DRM quick start

Five calls take you from nothing to a metered licence. Everything below runs against the hosted service at https://api.superdrm.com; on an on-premises install swap in https://api.<your domain> (or http://127.0.0.1:8770 on the api host).

#1. Get a tenant

Hosted: sign up at superdrm.com/portal, verify your email, and copy the two secrets shown once: api_key (sdrm_live_…, for your servers) and token_secret (64 hex, for minting player licence tokens). Store both in your secret manager. See Portal & console.

On-premises: the installer creates a first tenant and writes its credentials to secrets/demo-tenant.json; create more with the CLI:

Shell
./bin/superdrm tenant:create acme "Acme Corp" --plan growth

#2. Issue a content key

From the packager, one call per file. JSON:

Shell
curl -s -X POST https://api.superdrm.com/v1/keys \
  -H "Authorization: Bearer sdrm_live_…" -H "Content-Type: application/json" \
  -d '{"label":"acme-invoice-2026-09"}'
issue-key.ts
const r = await fetch("https://api.superdrm.com/v1/keys", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.SUPERDRM_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ label: "acme-invoice-2026-09" }),
});
const key = await r.json();           // { content_id, kid, key, iv, wv_pssh, fp_hls, … }
JSON
{
  "content_id": "6d8b28c0-605e-4531-b25c-1981e7b990fc",
  "kid": "04304543e3ab52ff801fd4d366981781",
  "key": "…32 hex…", "iv": "…32 hex…", "scheme": "cbcs",
  "systems": ["widevine", "fairplay", "clearkey"],
  "wv_pssh": "AAAATHBzc2g…", "pr_pssh": null,
  "fp_hls": "URI=\"skd://6d8b28c0-…\",KEYFORMAT=\"com.apple.streamingkeydelivery\",KEYFORMATVERSIONS=\"1\"",
  "licence_urls": { "com.widevine.alpha": "https://api.superdrm.com/v1/acme/license/widevine", "com.apple.fps": "…/fairplay", "org.w3.clearkey": "…/clearkey" },
  "fairplay_cert_url": "https://api.superdrm.com/v1/acme/fairplay/cert",
  "created": true
}

Or CPIX 2 XML, the form packagers already parse:

Shell
curl -s "https://api.superdrm.com/v1/cpix?k=6d8b28c0-605e-4531-b25c-1981e7b990fc&c=acme-invoice&EncryptionScheme=cbcs" \
  -H "Authorization: Bearer sdrm_live_…"

Encrypt with the returned key, IV and PSSH (MP4Box, shaka-packager and Bento4 all accept CPIX or the raw values).

#3. Serve a licence

Two ways. Server relay — your backend authorises the viewer, then forwards the CDM challenge:

Shell
curl -s -X POST "https://api.superdrm.com/v1/acme/license/widevine?cid=6d8b28c0-605e-4531-b25c-1981e7b990fc" \
  -H "Authorization: Bearer sdrm_live_…" \
  -H 'X-SuperDRM-Policy: {"hw":true}' \
  --data-binary @challenge.bin -o licence.bin -D - | grep -i x-superdrm-security
relay.ts
export async function relay(system: string, contentId: string, challenge: Uint8Array, strict: boolean) {
  const r = await fetch(`https://api.superdrm.com/v1/acme/license/${system}?cid=${contentId}`, {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.SUPERDRM_KEY}`, "Content-Type": "application/octet-stream",
               "X-SuperDRM-Policy": JSON.stringify({ hw: strict }) },
    body: challenge,
  });
  if (!r.ok) throw new Error(`superdrm ${r.status}: ${await r.text()}`);   // {error, code}
  console.log("granted", r.headers.get("x-superdrm-security"));            // L1 | L3 | hw | sw
  return new Uint8Array(await r.arrayBuffer());
}

Player token — your backend mints a token and the player talks to SuperDRM directly:

Shell
curl -s -X POST https://api.superdrm.com/v1/tokens \
  -H "Authorization: Bearer sdrm_live_…" -H "Content-Type: application/json" \
  -d '{"content_id":"6d8b28c0-…","sub":"user-42","ttl_s":3600,"policy":{"hw":true}}'
mint-token.ts
const r = await fetch("https://api.superdrm.com/v1/tokens", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.SUPERDRM_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ content_id: "6d8b28c0-…", sub: "user-42", ttl_s: 3600, policy: { hw: true } }),
});
const { servers, fairplay_cert_url } = await r.json();
player.configure({ drm: { servers } });   // Shaka: URLs already carry ?lt=<token>

The reply's servers map drops straight into a Shaka drm.servers config; each URL already carries ?lt=<token>.

#4. Read the meter

Shell
curl -s "https://api.superdrm.com/v1/usage?from=2026-09-01&to=2026-09-30" -H "Authorization: Bearer sdrm_live_…"
curl -s "https://api.superdrm.com/v1/usage/licences?limit=20" -H "Authorization: Bearer sdrm_live_…"
usage.ts
const h = { Authorization: `Bearer ${process.env.SUPERDRM_KEY}` };
const usage = await (await fetch("https://api.superdrm.com/v1/usage?from=2026-09-01&to=2026-09-30", { headers: h })).json();
const recent = await (await fetch("https://api.superdrm.com/v1/usage/licences?limit=20", { headers: h })).json();
console.log(usage.totals, recent.licences[0]);   // { widevine: { ok, failed }, … }

Every licence attempt is a row, refused ones included, with the security level actually granted (L1, L3, hw, sw) and an error code when refused.

#5. Revoke

Shell
curl -s -X POST https://api.superdrm.com/v1/keys/6d8b28c0-…/revoke -H "Authorization: Bearer sdrm_live_…"

Every later licence for that content id is refused with revoked; the refusal is metered too. Token expiry (30 days at most) and the authorization webhook are the other two refusal paths.

Updated September 2026