> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://mediakind.ferndocs.com/platform/how-to/media/content-protection/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://mediakind.ferndocs.com/_mcp/server. # Content protection To configure protected playback with Digital Rights Management (DRM) using the Media API, you combine three resources that meet at the streaming locator: * A content key policy defines how decryption keys and DRM licenses are delivered, and who is allowed to receive them. * A streaming policy defines how the content is encrypted for playback. * A streaming locator ties a policy pair to an asset and publishes it. Separating these resources lets you reuse one key-delivery policy across many assets while choosing a different encryption model for each publication context. You need a content key policy whenever playback must be restricted to entitled viewers or encrypted with DRM, and you can skip it entirely for unprotected playback. This guide builds a working multi-DRM policy. For conceptual background on each DRM system, see the [content protection overview](/mkio/understanding/core-concepts/content-protection). ## How the pieces fit together The protected playback chain is always the same three steps: * Create or choose a content key policy (the subject of this guide). * Create or choose a streaming policy (predefined policies cover most cases). * Create a streaming locator that references the asset, the streaming policy, and the content key policy. The rest of this guide builds that chain. ## The minimal working policy A content key policy is a named resource. Its `properties.options` array holds one or more options, and every option pairs two things: * a `configuration`: the DRM system and its license rules. * a `restriction`: who is allowed to receive a license. The most common production setup is multi-DRM (PlayReady and Widevine) gated behind a JSON Web Token (JWT). The request below creates that policy. Replace `` and the verification key with your own values. * Terminal window ```bash curl -X PUT "https://app.mk.io/api/v1/projects//media/contentKeyPolicies/multi-drm-policy" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "properties": { "description": "PlayReady and Widevine, gated by a JWT token", "options": [ { "name": "playready-jwt", "configuration": { "@odata.type": "#Microsoft.Media.ContentKeyPolicyPlayReadyConfiguration", "licenses": [ { "allowTestDevices": false, "licenseType": "NonPersistent", "contentType": "Unspecified", "contentKeyLocation": { "@odata.type": "#Microsoft.Media.ContentKeyPolicyPlayReadyContentEncryptionKeyFromHeader" } } ] }, "restriction": { "@odata.type": "#Microsoft.Media.ContentKeyPolicyTokenRestriction", "issuer": "https://your-company.com", "audience": "your-video-app", "restrictionTokenType": "Jwt", "primaryVerificationKey": { "@odata.type": "#Microsoft.Media.ContentKeyPolicySymmetricTokenKey", "keyValue": "" } } }, { "name": "widevine-jwt", "configuration": { "@odata.type": "#Microsoft.Media.ContentKeyPolicyWidevineConfiguration", "widevineTemplate": "{}" }, "restriction": { "@odata.type": "#Microsoft.Media.ContentKeyPolicyTokenRestriction", "issuer": "https://your-company.com", "audience": "your-video-app", "restrictionTokenType": "Jwt", "primaryVerificationKey": { "@odata.type": "#Microsoft.Media.ContentKeyPolicySymmetricTokenKey", "keyValue": "" } } } ] } }' ``` The fields that matter most: `@odata.type` is the discriminator on every nested object. It selects which configuration, restriction, or key type the object is. The values are fixed strings and must match exactly. * `widevineTemplate` is a JSON string, not a JSON object. An empty `"{}"` tells MK.IO to generate a default Widevine license. Supply a custom template only when you need to control persistence or validity. * `keyValue` is the Base64-encoded HMAC secret your application signs tokens with. It is the same secret on both sides: the policy verifies what your token server signs. * `issuer` and `audience` must match the `iss` and `aud` claims your tokens carry. A mismatch rejects the license request. A successful create returns `201 Created` with the stored policy. A name that already exists returns `409 Conflict`. For the full request and response schema, see the [Media API reference](/mkio/api/media). ## Choosing the configuration The `configuration` block selects the DRM system. Each system covers different platforms, so the choice is driven by the devices you need to reach. | You need to reach | Use this configuration | `@odata.type` | | ------------------------------------------------------- | ---------------------- | --------------------------------------------------------- | | Edge, Xbox, Windows | PlayReady | `#Microsoft.Media.ContentKeyPolicyPlayReadyConfiguration` | | Chrome, Firefox, Android, Android TV | Widevine | `#Microsoft.Media.ContentKeyPolicyWidevineConfiguration` | | Safari, iOS, tvOS, macOS | FairPlay | `#Microsoft.Media.ContentKeyPolicyFairPlayConfiguration` | | Basic encryption, lowest latency, no DRM license server | Clear Key | `#Microsoft.Media.ContentKeyPolicyClearKeyConfiguration` | To cover every platform, include all three DRM configurations in one policy, as the minimal example does for two of them. FairPlay is the one configuration that needs extra setup: it requires a Base64-encoded certificate (`fairPlayPfx`), its password (`fairPlayPfxPassword`), and a Base64-encoded application secret key (`ask`). Supply the certificate password directly, without Base64 encoding it. The FairPlay configuration also requires `rentalAndLeaseKeyType` and `rentalDuration`; see `ContentKeyPolicyFairPlayConfiguration` in the [Media API reference](/mkio/api/media) for their supported values. The other systems work with the fields shown above. Clear Key uses AES-128 and does not reach the security of the three DRM systems. Do not add Clear Key to a policy intended for DRM, because it lowers the overall protection. For the per-system setup detail, see [multi-DRM encryption](/mkio/how-to/drm-content-protection/multi-drm-encryption). ## Choosing the restriction The `restriction` block decides who receives a license. | Your situation | Use this restriction | `@odata.type` | | --------------------------------------------- | -------------------- | --------------------------------------------------- | | Production: only entitled viewers may play | Token restriction | `#Microsoft.Media.ContentKeyPolicyTokenRestriction` | | Testing only: anyone with the stream may play | Open restriction | `#Microsoft.Media.ContentKeyPolicyOpenRestriction` | An open restriction needs no other fields and is for testing only. A token restriction is the production default and needs `issuer`, `audience`, `restrictionTokenType`, and a `primaryVerificationKey`. The verification key type is itself chosen by `@odata.type`: | Signing approach | Key type | `@odata.type` | Required fields | | ----------------------- | --------- | ---------------------------------------------------------- | --------------------- | | Symmetric (HMAC, HS256) | Symmetric | `#Microsoft.Media.ContentKeyPolicySymmetricTokenKey` | `keyValue` | | Asymmetric (RSA) | RSA | `#Microsoft.Media.ContentKeyPolicyRsaTokenKey` | `modulus`, `exponent` | | Certificate (X.509) | X.509 | `#Microsoft.Media.ContentKeyPolicyX509CertificateTokenKey` | `rawBody` | Symmetric (HS256) is the default and the simplest. Choose RSA when your security model requires asymmetric keys; see [RSA key for token validation](/mkio/how-to/drm-content-protection/rsa-key-for-token-validation). ## Attach the policy at the locator A content key policy takes effect only when a streaming locator references it. The locator joins the asset, a streaming policy, and the content key policy. ```bash curl -X PUT "https://app.mk.io/api/v1/projects//media/streamingLocators/protected-locator" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "properties": { "assetName": "output-video", "streamingPolicyName": "Predefined_MultiDrmCencStreaming", "defaultContentKeyPolicyName": "multi-drm-policy" } }' ``` Use a predefined streaming policy unless you have a reason not to. The predefined policies remove all custom encryption configuration: | Streaming policy | Encryption | Requires in the content key policy | | ---------------------------------- | --------------------------------- | ----------------------------------- | | `Predefined_ClearStreamingOnly` | None | Nothing | | `Predefined_ClearKey` | Clear Key (AES-128) | A Clear Key option | | `Predefined_MultiDrmCencStreaming` | PlayReady and Widevine | Both PlayReady and Widevine options | | `Predefined_MultiDrmStreaming` | PlayReady, Widevine, and FairPlay | All three options | The minimal example pairs with `Predefined_MultiDrmCencStreaming` because it defines PlayReady and Widevine. To add Safari and Apple devices, add a FairPlay option to the policy and switch the locator to `Predefined_MultiDrmStreaming`. ## What goes wrong * The streaming policy and the content key policy disagree. The streaming policy enforces which DRM schemes the content key policy must contain. A `Predefined_MultiDrmStreaming` locator fails if the policy does not include all three schemes, and a `Predefined_MultiDrmCencStreaming` locator fails if it lacks PlayReady or Widevine. Match the table above. * The token is signed with a different key, issuer, or audience. MK.IO validates the token signature against `primaryVerificationKey`, and the `iss` and `aud` claims against the policy. Any mismatch rejects the license, even when the stream itself publishes correctly. * The verification key leaks into client code. Generate tokens on your server and pass them to the player at runtime. Never ship the `keyValue` secret in client-side code. * Playback shows a license error during testing. A protected stream opened without a token can surface a `DRM_FAILED_LICENSE_REQUEST` error. That is expected: the encrypted stream needs a token that has not been provided yet. For the token flow and the required claims, see [JWT token authentication](/mkio/how-to/drm-content-protection/jwt-token-authentication). ## Related operations List the content key policies in a project: ```bash curl -X GET "https://app.mk.io/api/v1/projects//media/contentKeyPolicies" \ -H "Authorization: Bearer " ``` Retrieve a single policy: ```bash curl -X GET "https://app.mk.io/api/v1/projects//media/contentKeyPolicies/multi-drm-policy" \ -H "Authorization: Bearer " ``` A standard `GET` never returns secret values, such as the verification key. When you need to inspect or rotate secrets, use the secrets variant, and only then: ```bash curl -X POST "https://app.mk.io/api/v1/projects//media/contentKeyPolicies/multi-drm-policy/getPolicyPropertiesWithSecrets" \ -H "Authorization: Bearer " ``` ## Where to go deeper For the concepts and platform-level setup behind these requests: * Content protection overview: how DRM, keys, and licenses work in MK.IO. * Multi-DRM encryption: per-system configuration detail. * JWT token authentication: the token flow and claim requirements. * Using custom claims in content key policies: route different licenses from one policy. For the complete, field-level request and response schema, including every configuration and restriction variant this guide does not cover: * Media API reference. ## What comes next * Streaming and publishing: connect the policy to locators and live playback.