Content Delivery API

How to authenticate

All content delivery API methods, except /v1/access_token, require a bearer-type Authorization header containing an access token. To acquire an access token you should call /v1/access_token API method described below.

Passing Authorization header with cURL:

curl -X $HTTP_METHOD -H "Authorization: Bearer $ACCESS_TOKEN" https://api-cdn.qencode.com/v1/$OBJECT

All requests must be made over HTTPS.

Getting Access Token

POST
/v1/access_token/<api_key>

Qencode requires API keys to generate access tokens that are used to authenticate all of your live stream requests. To get an access_token, you must provide your API key in the authorization header of the request.

You can view and manage the API keys associated with your projects inside of your Qencode Account.

Access token is valid for 24 hours.

warning
Caution
To build a secure solution we strongly recommend that you DO NOT call this method directly from any client application as you will expose your api key publicly. We recommend you first obtain an access token from your server and then pass to the client app.
Arguments

For live-streaming, an API key is assigned to each Project created in your Qencode account. After logging into your account, you can manage your API keys on the Live Streaming Projects page, as well as track the usage of each Project on the Statistics page.

Returns

After API key authentication is complete, you will receive this session based token, which can be used to call all other live streaming API methods.

Request Example

Replace the 'your_api_key' value below with your API key. You can find you API key in your Live Streaming Project within your account.

curl -X POST https://api-live.qencode.com/v1/access_token/your_api_key
Response Example

Token returned should be passed in Authorization header to all other live streaming API methods described below

{
 "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyjpYXQiOjE2MjgxNTgyNJMsInN1YiI6MTMwLCJleHAiOjE2MjgyNDQ2NjN9.SxS3zLx2CZbZ9ylTpd25kj9el6_4TqqTWUA9RT2iJ9I"
}

Creating a Playback Domain

POST
/v1/domains

You can create custom domains and use them for live-streaming or media storage playback.

A custom domain can either be used for live-streaming or media-storage, so you'll need to create separate domains for live-streaming and media storage playback in case you need to support both.

Returns

You won't be able to update your domain object after it's created. Instead you can delete it and create another domain.

You can create up to 20 custom domains for playback. In case this does not cover your use case you can contact our support.

Input Objects Structure
Attributes
Specify a fully qualified domain name (FQDN) for your domain. For example, live.mydomain.com.
Specify the type of domain you are creating. Possible values: live or vod.
Required for domains of vod type. Specify the name of the bucket in your Qencode media storage you want to expose with the domain name.
Output Objects Structure
Attributes
The domain object contains all the attributes of your playback domain: id, type, domain name and creation date and time.
Attributes
There are two types of domains: live and vod. Live domains are used for live-streaming and vod domains are used for media storage playback.
Specify a fully qualified domain name (FQDN) for your domain. For example, live.mydomain.com.
Request Example
curl -X POST 'https://api-cdn.qencode.com/v1/domains' \ 
-H 'Authorization: Bearer $ACCESS_TOKEN' \ 
-H 'Content-Type: application/json' \ 
--data-raw '{"name": "live.mydomain.com", "type": "live"}'
Response Example
{
  "domain": {
    "created": "2023-06-07 14:17:25",
    "id": "aeac03d6-beb0-4638-a094-fbc35653f931",
    "type": "live",
    "domain_name": "live.mydomain.com"
  }
}

Getting a Playback Domain Data

GET
/v1/domains/<domain_id>

Gets a playback domain information.

Returns
Output Objects Structure
Attributes
The domain object contains all the attributes of your playback domain: id, type, domain name and creation date and time.
Attributes
There are two types of domains: live and vod. Live domains are used for live-streaming and vod domains are used for media storage playback.
Specify a fully qualified domain name (FQDN) for your domain. For example, live.mydomain.com.
Request Example
curl -H "Authorization: Bearer $ACCESS_TOKEN" 
 -X GET https://api-cdn.qencode.com/v1/domains/$DOMAIN_ID
Response Example
{
  "domain": {
    "created": "2023-06-07 14:17:25",
    "id": "aeac03d6-beb0-4638-a094-fbc35653f931",
    "type": "live",
    "domain_name": "live.mydomain.com"
  }
}

Listing Playback Domains

GET
/v1/domains

Gets a list of user domains.

Returns

In case no filter specified, contains list of all user domains.

You can optionally pass the domain type in the last URL segment. E.g. `GET /v1/domains/live` will return the list of live-streaming domains.

Request Example
curl -H "Authorization: Bearer $ACCESS_TOKEN" 
 -X GET https://api-cdn.qencode.com/v1/domains
Response Example
{
  "domains": [
    {
      "created": "2023-06-07 14:17:25",
      "id": "aeac03d6-beb0-4638-a094-fbc35653f931",
      "type": "live",
      "domain_name": "live.mydomain.com"
    }
  ]
}

Deleting a Playback Domain

DELETE
/v1/domains/<domain_id>

Deletes a playback domain.

Returns
Status is 'ok' in case domain was successfully deleted or 'failed' in case there was an error.
Request Example
curl -H "Authorization: Bearer $ACCESS_TOKEN" 
 -X DELETE https://api-cdn.qencode.com/v1/domains/$DOMAIN_ID
Response Example
{
  "status": "ok"
}

Creating a TLS Subscription

POST
/v1/tls/subscriptions

You can create automatically managed TLS subscriptions for custom domains and use them for live-streaming or media storage playback.

TLS subscription is a Let's Encrypt TLS certificate automatically issued and renewed for a playback domain.

Use this as a quick and easy way to setup TLS/HTTPS for your domain.

Returns

TLS subscription is a quick and easy way to setup TLS for your custom playback domain.

Input Objects Structure
Attributes
Specify an ID of the existing Playback domain. You should create a Playback domain before creating a TLS subscription for it.
Output Objects Structure
Attributes
The TLS subscription object contains all the attributes of your TLS subscription: id, domain ID, status, instructions for domain ownership verification and creation date and time.
Attributes
The status can be one of the following: pending, processing, issued, renewing, failed.
You should follow the instructions provided to verify your TLS subscription.
Request Example
curl -X POST 'https://api-cdn.qencode.com/v1/tls/subscriptions' \ 
-H 'Authorization: Bearer $ACCESS_TOKEN' \ 
-H 'Content-Type: application/json' \ 
--data-raw '{"domain_id": "aeac03d6-beb0-4638-a094-fbc35653f931"}'
Response Example
{
  "tls_subscription": {
    "status": "pending",
    "created": "2023-06-12 15:10:48",
    "domain_id": "aeac03d6-beb0-4638-a094-fbc35653f931",
    "message": "TLS subscription created. You should verify your domain ownership. Create a CNAME for 'live.mydomain.com' and point it to 'j.sni.global.fastly.net.'.",
    "id": "7d819560-6220-4b76-bf9a-3784a64ac7ff"
  }
}

Getting a TLS subscription Data

GET
/v1/tls/subscriptions/<tls_subscription_id>

Gets a TLS subscription information.

Returns
Output Objects Structure
Attributes
The TLS subscription object contains all the attributes of your TLS subscription: id, domain ID, status, instructions for domain ownership verification and creation date and time.
Attributes
The status can be one of the following: pending, processing, issued, renewing, failed.
You should follow the instructions provided to verify your TLS subscription.
Request Example
curl -H "Authorization: Bearer $ACCESS_TOKEN" 
 -X GET https://api-cdn.qencode.com/v1/tls/subscriptions/$TLS_SUBSCRIPTION_ID
Response Example
{
  "tls_subscription": {
    "status": "pending",
    "created": "2023-06-12 15:10:48",
    "domain_id": "aeac03d6-beb0-4638-a094-fbc35653f931",
    "id": "7d819560-6220-4b76-bf9a-3784a64ac7ff"
  }
}

Listing TLS subscriptions

GET
/v1/tls/subscriptions

Gets a list of user TLS subscriptions.

Returns
In case no filter specified, contains list of all user TLS subscriptions.
Request Example
curl -H "Authorization: Bearer $ACCESS_TOKEN" 
 -X GET https://api-cdn.qencode.com/v1/tls/subscriptions
Response Example
{
  "tls_subscriptions": [
      {
          "status": "pending",
          "created": "2023-06-12 15:10:48",
          "id": "7d819560-6220-4b76-bf9a-3784a64ac7ff",
          "domain_id": "aeac03d6-beb0-4638-a094-fbc35653f931"
      }
  ]
}

Deleting a TLS subscription

DELETE
/v1/tls/subscriptions/<tls_subscription_id>

Deletes a TLS subscription.

Returns
Status is 'ok' in case TLS subscription was successfully deleted or 'failed' in case there was an error.
Request Example
curl -H "Authorization: Bearer $ACCESS_TOKEN" 
 -X DELETE https://api-cdn.qencode.com/v1/tls/subscriptions/$TLS_SUBSCRIPTION_ID
Response Example
{
  "status": "ok"
}

Creating a Signing key

POST
/v1/signing-keys

You can create signing keys and use them for live-streaming or media storage playback authentication.

A signing key can either be used for a Live stream, a Live stream Playback domain or a Media storage bucket.

Returns

You won't be able to get the private key data using the /v1/signing-keys after the signing key created.

You should save private key data you get from the response of /v1/signing-keys and store it in a secure place.

Input Objects Structure
Attributes
Specify an existing Playback domain ID in case you want using the signing key to authenticate playback for Live streams sharing the same custom Playback domain.
Specify an existing Live stream ID in case you want using the signing key to authenticate playback for a Live stream that does not have a custom Playback domain.
Specify an existing Media storage bucket name in case you want to use the signing key to authenticate an access to a media storage bucket.
You can specify a name for a signing key for the ease of identification.
Output Objects Structure
Attributes
The signing key object contains all the attributes of your signing key: id, domain_id or stream_id or bucket_name, private key and creation date and time.
Attributes
This field is returned in case you specified a Playback domain ID when creating the signing key.
This field is returned in case you specified a Live stream ID when creating the signing key.
This field is returned in case you specified a Media storage bucket name when creating the signing key.
Private key data is only returned in the response of the /v1/signing-keys method. You won't be able to get the private key data using the /v1/signing-keys after the signing key created. You should save private key data you get from the response of /v1/signing-keys and store it in a secure place.
Request Example
curl -X POST 'https://api-cdn.qencode.com/v1/signing-keys' \ 
-H 'Authorization: Bearer $ACCESS_TOKEN' \ 
-H 'Content-Type: application/json' \ 
--data-raw '{"stream_id": "1f1ab6a3-d867-4787-b882-7f2fb1203634"}'
Response Example
{
  "signing_key": {
      "stream_id": "1f1ab6a3-d867-4787-b882-7f2fb1203634",
      "private_key": "-----BEGIN RSA PRIVATE KEY-----
MIIJKgIBAAKCAgEA4DAKSi4F8yQTt1L4N9s9Wt+3hBoHt5KMs14jy37YNnqxaIzJ
...
1c4v1P/wPr2sqepof19dbacmGcxplhHJ4d3BXAdInxYuGrgerShJXy5icFWf8A==
-----END RSA PRIVATE KEY-----
",
      "created": "2023-06-12 15:00:40",
      "id": "ec25a6a6-b31e-4ac9-af01-711460421c6b",
      "name": null
      }
}

Getting a Signing key data

GET
/v1/signing-keys/<signing_key_id>

Gets a signing key information.

Returns
Output Objects Structure
Attributes
The signing key object contains all the attributes of your signing key: id, domain_id or stream_id or bucket_name, private key and creation date and time.
Attributes
This field is returned in case you specified a Playback domain ID when creating the signing key.
This field is returned in case you specified a Live stream ID when creating the signing key.
This field is returned in case you specified a Media storage bucket name when creating the signing key.
Private key data is only returned in the response of the /v1/signing-keys method. You won't be able to get the private key data using the /v1/signing-keys after the signing key created. You should save private key data you get from the response of /v1/signing-keys and store it in a secure place.
Request Example
curl -H "Authorization: Bearer $ACCESS_TOKEN" 
 -X GET https://api-cdn.qencode.com/v1/signing-keys/$SIGNING_KEY_ID
Response Example
{
  "signing_key": {
      "stream_id": "1f1ab6a3-d867-4787-b882-7f2fb1203634",
      "created": "2023-06-12 15:00:40",
      "name": "My Signing Key",
      "id": "ec25a6a6-b31e-4ac9-af01-711460421c6b"
  }
}

Listing Signing keys

GET
/v1/signing-keys

Gets a list of user signing keys.

Returns
In case no filter specified, contains list of all user signing keys.
Request Example
curl -H "Authorization: Bearer $ACCESS_TOKEN" 
 -X GET https://api-cdn.qencode.com/v1/signing-keys
Response Example
{
  "signing_keys": [
      {
          "created": "2023-06-07 14:17:25",
          "id": "e52f59d7-e65c-4036-81c6-0163403a1a88",
          "domain_id": "aeac03d6-beb0-4638-a094-fbc35653f931"
      },
      {
          "stream_id": "1f1ab6a3-d867-4787-b882-7f2fb1203634",
          "created": "2023-06-12 15:00:40",
          "id": "ec25a6a6-b31e-4ac9-af01-711460421c6b",
      "name": null
      }
  ]
}

Updating a Signing key

PUT
/v1/signing-keys/<signing_key_id>

Updates the signing key name.

Returns
Input Objects Structure
Attributes
Specify a name for the signing key for the ease of identification.
Output Objects Structure
Attributes
The signing key object contains all the attributes of your signing key: id, domain_id or stream_id or bucket_name, private key and creation date and time.
Attributes
This field is returned in case you specified a Playback domain ID when creating the signing key.
This field is returned in case you specified a Live stream ID when creating the signing key.
This field is returned in case you specified a Media storage bucket name when creating the signing key.
Private key data is only returned in the response of the /v1/signing-keys method. You won't be able to get the private key data using the /v1/signing-keys after the signing key created. You should save private key data you get from the response of /v1/signing-keys and store it in a secure place.
Request Example
curl -H "Authorization: Bearer $ACCESS_TOKEN" 
 -X PUT https://api-cdn.qencode.com/v1/signing-keys/$SIGNING_KEY_ID  \ 
-H 'Content-Type: application/json' \ 
--data-raw '{"name": "My signing key"}'
Response Example
{
  "signing_key": {
      "stream_id": "1f1ab6a3-d867-4787-b882-7f2fb1203634",
      "created": "2023-06-12 15:00:40",
      "id": "ec25a6a6-b31e-4ac9-af01-711460421c6b",
      "name": "My signing key"
      }
}

Deleting a Signing key

DELETE
/v1/signing-keys/<signing_key_id>

Deletes a signing key.

Returns
Status is 'ok' in case signing key was successfully deleted or 'failed' in case there was an error.
Request Example
curl -H "Authorization: Bearer $ACCESS_TOKEN" 
 -X DELETE https://api-cdn.qencode.com/v1/signing-keys/$SIGNING_KEY_ID
Response Example
{
  "status": "ok"
}

Setting a media storage bucket access policy

PUT
/v1/buckets/<bucket_name>/policy/<policy_name>

Sets bucket access policy.

Policy name can either be 'public' or 'authenticated'. Buckets are set to public access policy by default.

In case authenticated policy is set for a bucket all content inside the bucket can be accessed with a signed token passed in the URL.

See Signed tokens for more information.

Returns
Status is 'ok' in case the bucket policy was successfully updated or 'failed' in case there was an error.
Request Example
curl -H "Authorization: Bearer $ACCESS_TOKEN" 
 -X PUT https://api-cdn.qencode.com/v1/buckets/$BUCKET_NAME/policy/$POLICY_NAME
Response Example
{
  "status": "ok"
}

Setting a media storage bucket CORS policy

POST
/v1/buckets/<bucket_name>/cors

Sets bucket CORS policy.

Accepts an object where attribute names are CORS headers and values a corresponding CORS header values

Supported CORS headers are listed below

Returns
Status is 'ok' in case the bucket CORS policy was successfully updated or 'failed' in case there was an error.
Input Objects Structure
Attributes
Specify * or a particular origin as a value, for example https://mydomain.com
Please note, in case you specify true as a value for this header, you should also specify a particular origin for the Access-Control-Allow-Origin header
Supported methods are GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH

Indicates how long the results of a preflight request (that is the information contained in the Access-Control-Allow-Methods and Access-Control-Allow-Headers headers) can be cached.

Request Example
curl -H "Authorization: Bearer $ACCESS_TOKEN" 
 -X POST https://api-cdn.qencode.com/v1/buckets/$BUCKET_NAME/cors -H 'Content-Type: application/json' --data '{"Access-Control-Allow-Origin": "*"}'
Response Example
{
  "status": "ok"
}

Getting a media storage bucket CORS policy

GET
/v1/buckets/<bucket_name>/cors

Gets bucket CORS policy.

Returns
Specifies the domain allowed to access resources from the bucket
Indicates whether the browser should include credentials, like cookies, when making requests to the bucket
Lists the HTTP methods permitted when accessing the bucket. Supported methods are GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH
Defines the duration (in seconds) for which the browser can cache the CORS response
Request Example
curl -H "Authorization: Bearer $ACCESS_TOKEN" 
 -X GET https://api-cdn.qencode.com/v1/buckets/$BUCKET_NAME/cors
Response Example
{
  "Access-Control-Allow-Origin": "https://media-storage.us-west.qencode.com",
  "Access-Control-Allow-Methods": "GET, PUT, POST, DELETE",
  "Access-Control-Max-Age": "1600",
  "Access-Control-Allow-Credentials": "true"
}

Deleting a media storage bucket CORS policy

DELETE
/v1/buckets/<bucket_name>/cors

Deletes bucket CORS policy.

Returns
Status is 'ok' in case the bucket CORS policy was successfully deleted or 'failed' in case there was an error.
Request Example
curl -H "Authorization: Bearer $ACCESS_TOKEN" 
 -X DELETE https://api-cdn.qencode.com/v1/buckets/$BUCKET_NAME/cors
Response Example
{
  "status": "ok"
}

Deleting a media storage bucket CORS header

DELETE
/v1/buckets/<bucket_name>/cors/<header_name>

Removes a header from the bucket CORS policy.

Returns
Status is 'ok' in case the bucket CORS header was successfully deleted or 'failed' in case there was an error.
Request Example
curl -H "Authorization: Bearer $ACCESS_TOKEN" 
 -X DELETE https://api-cdn.qencode.com/v1/buckets/$BUCKET_NAME/cors/Access-Control-Allow-Origin
Response Example
{
  "status": "ok"
}

Purging CDN cache for a bucket

POST
/v1/buckets/<bucket_name>/cache/purge_all

Purges CDN cache for all objects in a bucket.

Returns
Status is 'ok' in case cache for all objects in the bucket was successfully purged or 'failed' in case there was an error.
Request Example
curl -H "Authorization: Bearer $ACCESS_TOKEN" 
 -X POST https://api-cdn.qencode.com/v1/buckets/my-bucket/cache/purge_all
Response Example
{
  "status": "ok"
}

Purging CDN cache for an object in a bucket

POST
/v1/buckets/<bucket_name>/cache/purge

Purges CDN cache for a single object in a bucket.

Returns
Status is 'ok' in case cache for the object in the bucket was successfully purged or 'failed' in case there was an error.
Input Objects Structure
Attributes
Request Example
curl -H "Authorization: Bearer $ACCESS_TOKEN" 
 --location "https://api-cdn.qencode.com/v1/buckets/my-bucket/cache/purge" -H 'Content-Type: application/json' 
 --data '{"path": "path/to/object"}'
Response Example
{
  "status": "ok"
}

Generate signed tokens

Signed tokens are JSON Web Tokens (JWT) used to authenticate requests to Qencode Content delivery service.
You can use it to authenticate access to live-streams playback or to a content in your Qencode media storage bucket.

To authenticate the request a signed token should be added to the resource url using the auth query string param. For example, you can authenticate live-stream playback URL request the following way:

https://play-$PLAYBACK_ID.qencode.com/qhls/qlive/playlist.m3u8?auth=$TOKEN

where $PLAYBACK_ID is your live stream playback ID.

For live streams using the custom domain for playback the URL will look like this:

https://$CUSTOM_DOMAIN/$PLAYBACK_ID.m3u8?auth=$TOKEN

where $CUSTOM_DOMAIN is your custom Playback domain name.

To authenticate a request to Qencode media storage bucket you can build a URL like this:

https://$BUCKET.media-storage.$REGION.qencode.com/path/to/video.mp4?auth=$TOKEN

where $BUCKET is your media storage bucket name and $REGION is Qencode media storage region. Currently available Qencode media storage regions are us-west and eu-central.

To authenticate a request to Qencode media storage bucket using your custom domain you can build a URL like this:

https://$CUSTOM_DOMAIN/path/to/video.mp4?auth=$TOKEN

where $CUSTOM_DOMAIN is your custom domain name.

Signed token structure

The structure of a signed token includes a list of headers and payload values. Please see the list of supported headers and payload values below. You can also add your custom payload values to a JWT.

Signed token supported params

For Live streaming, uri should be set to the stream playback ID.

For media storage uri should be a key in the bucket or a key prefix. Setting uri to an empty string authorizes the request to access any resource in the bucket.

If not specified, token does not expire.

A timestamp that indicates the time before which the token must not be accepted.

Code examples

Here are code samples in different programming languages you can use to generate a signed token:

Request Example

Generating a signed token

import jwt
import time

def generate_signed_token(signing_key_id, private_key, uri, exp=3600, nbf=None):
    payload = {
        'uri': uri
    }
    if exp > 0:
        payload['exp'] = int(time.time()) + exp
    if nbf is not None:
        payload['nbf'] = nbf
        
    headers = {
        'alg': 'RS256',
        'typ': 'JWT',
        'kid': signing_key_id
    }

    token = jwt.encode(payload, private_key, algorithm="RS256", headers=headers)
    return token

# you can get signing key data from the response of https://api-cdn.qencode.com/v1/signing_keys endpoint
signing_key = {
  'id' = 'your_signing_key_id',
  'private_key' = 'your_signing_key_private_key'
}

# generate token for Live stream playback
stream_playback_id = 'your_stream_playback_id'

token = generate_signed_token(signing_key['id'], signing_key['private_key'], playback_id)
<?php

/*

Please note that in order to use JWT in PHP, you need to install the "firebase/php-jwt" library via Composer. 
You can do that with the following command:
composer require firebase/php-jwt

*/

require_once('vendor/autoload.php');
use FirebaseJWTJWT;

function generateSignedToken($signingKeyId, $privateKey, $uri, $exp = 3600, $nbf = null)
{
    $payload = [
        'uri' => $uri
    ];
    if ($exp > 0) {
        $payload['exp'] = time() + $exp;
    }
    if ($nbf !== null) {
        $payload['nbf'] = $nbf;
    }

    $headers = [
        'alg' => 'RS256',
        'typ' => 'JWT',
        'kid' => $signingKeyId
    ];

    $token = JWT::encode($payload, $privateKey, 'RS256', null, $headers);
    return $token;
}

// you can get signing key data from the response of https://api-cdn.qencode.com/v1/signing_keys endpoint
$signingKey = [
  'id' => 'your_signing_key_id',
  'private_key' => 'your_signing_key_private_key'
];

// generate token for Live stream playback
$streamPlaybackId = 'your_stream_playback_id';

$token = generateSignedToken($signingKey['id'], $signingKey['private_key'], $streamPlaybackId);
echo $token;
?>
const jwt = require('jsonwebtoken');

function generateSignedToken(signingKeyId, privateKey, uri, exp = 3600, nbf = null) {
    let payload = {
        uri: uri,
    };

    if (exp > 0) {
        payload.exp = Math.floor(Date.now() / 1000) + exp;
    }

    if (nbf !== null) {
        payload.nbf = nbf;
    }

    let headers = {
        alg: 'RS256',
        typ: 'JWT',
        kid: signingKeyId,
    };

    let token = jwt.sign(payload, privateKey, { algorithm: 'RS256', header: headers });
    return token;
}

// you can get signing key data from the response of https://api-cdn.qencode.com/v1/signing_keys endpoint
let signingKey = {
  id: 'your_signing_key_id',
  privateKey: 'your_signing_key_private_key',
};

// generate token for Live stream playback
let streamPlaybackId = 'your_stream_playback_id';

let token = generateSignedToken(signingKey.id, signingKey.privateKey, streamPlaybackId);
console.log(token);
using System;
using System.Collections.Generic;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

class Program
{
    static void Main()
    {
        string signingKeyId = "your_signing_key_id";
        string privateKey = "your_signing_key_private_key"; // You might need to convert this to a SecurityKey
        string uri = "your_stream_playback_id";
        int exp = 3600;
        int? nbf = null;

        var token = GenerateSignedToken(signingKeyId, privateKey, uri, exp, nbf);
        Console.WriteLine(token);
    }

    static string GenerateSignedToken(string signingKeyId, string privateKey, string uri, int exp, int? nbf)
    {
        var claims = new List<Claim>
        {
            new Claim("uri", uri)
        };

        if (exp > 0)
        {
            var expTime = DateTime.UtcNow.AddSeconds(exp);
            claims.Add(new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(expTime).ToUnixTimeSeconds().ToString()));
        }

        if (nbf != null)
        {
            claims.Add(new Claim(JwtRegisteredClaimNames.Nbf, nbf.ToString()));
        }

        var securityKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(privateKey)); // might need to be adjusted based on your key
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);

        var header = new JwtHeader(credentials);
        header.Add("kid", signingKeyId);

        var payload = new JwtPayload(claims);
        var secToken = new JwtSecurityToken(header, payload);

        var handler = new JwtSecurityTokenHandler();

        return handler.WriteToken(secToken);
    }
}
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import java.nio.charset.StandardCharsets;
import java.util.Date;

public class Main {
    public static void main(String[] args) {
        String signingKeyId = "your_signing_key_id";
        String privateKeyStr = "your_signing_key_private_key"; // You might need to convert this to a Key
        String uri = "your_stream_playback_id";
        long exp = 3600;
        Long nbf = null;

        String token = generateSignedToken(signingKeyId, privateKeyStr, uri, exp, nbf);
        System.out.println(token);
    }

    private static String generateSignedToken(String signingKeyId, String privateKeyStr, String uri, long exp, Long nbf) {
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        byte[] privateKeyBytes = privateKeyStr.getBytes(StandardCharsets.UTF_8);
        Key signingKey = Keys.hmacShaKeyFor(privateKeyBytes);

        JwtBuilder builder = Jwts.builder()
                .setId(signingKeyId)
                .claim("uri", uri)
                .signWith(signingKey, SignatureAlgorithm.HS256);

        if (exp > 0) {
            long expMillis = nowMillis + exp * 1000;
            Date expDate = new Date(expMillis);
            builder.setExpiration(expDate);
        }

        if (nbf != null) {
            long nbfMillis = nbf * 1000;
            Date nbfDate = new Date(nbfMillis);
            builder.setNotBefore(nbfDate);
        }

        return builder.compact();
    }
}
require 'jwt'
require 'time'

def generate_signed_token(signing_key_id, private_key, uri, exp=3600, nbf=nil)
    payload = {
        'uri' => uri
    }
    if exp > 0
        payload['exp'] = Time.now.to_i + exp
    end
    if !nbf.nil?
        payload['nbf'] = nbf
    end

    headers = {
        'alg' => 'RS256',
        'typ' => 'JWT',
        'kid' => signing_key_id
    }

    token = JWT.encode payload, private_key, 'RS256', headers
    return token
end

# you can get signing key data from the response of https://api-cdn.qencode.com/v1/signing_keys endpoint
signing_key = {
    'id' => 'your_signing_key_id',
    'private_key' => 'your_signing_key_private_key'
}

# generate token for Live stream playback
stream_playback_id = 'your_stream_playback_id'

token = generate_signed_token(signing_key['id'], signing_key['private_key'], stream_playback_id)
puts token