Introduction

Welcome to Paket! This documentation explains key concepts and describes how Platforms and Publishers engage with the Paket Watch APIs.

So, what is it?

Paket is a universal engagement platform for Connected TVs and subscription marketplaces. We facilitate, through our suite of APIs, device and platform-agnostic features that help drive engagement through personalization, enhanced discovery, and advanced analytics.

The Paket API is organized around REST. Our API has predictable resource-oriented URLs, accepts JSON-encoded request bodies, returns JSON-encoded responses, and uses standard HTTP response codes, authentication, and verbs.

Base URL

https://api.paket.tv/v1

There are two primary types of API consumers:

Platforms
Platform partners are typically TV OEMs, platforms, operating systems, and marketplaces.

Publishers
Publisher partners are typically publishers of streaming media or other subscription-based apps.

How a partner engages with the API depends largely on whether they are a Platform or a Publisher. This designation is assigned in the Paket Developer Portal at the time of the Company's account setup.

Clients

To begin working with the Paket API, Partners must first create a Client in the Paket Developer Portal.

Clients are used by Platforms and Publishers, respectively, to communicate with the Paket API and to exchange data with other Clients.

It is at the Client level that API credentials are issued, webhooks are delivered, and API products - such as TVaaS or Bundles - are configured.

Clients should correlate broadly to an operating system or platform, in the case of Platform accounts; or to an application, in the case of Publisher accounts.

Authentication

The Paket API uses basic access authentication to authenticate all incoming API requests (please note there are additional Security protocols used to secure API requests).

Authenticated Request:

curl https://api.paket.tv/v1 \
  -H "Authorization: Basic [Base64 encoding of username and password]"

Authenticating via Basic Auth involves passing a Base64 encode of your Client's username and password, prefixed with Basic within the request's Authorization header.

API credentials be found in the Paket Developer Portal within the Client's API Credentials tab. The username and password are itemized as 'API Username' and 'API Secret Key,' respectively:

Paket API Portal: Credentials

Testing

API requests may be tested in a live environment simply by using the API Test Key provided in the Paket Developer Portal. Test keys begin with the prefix test_sk and inherit the same behaviors as live API Secret Keys, save that they call test responses.

Test API requests are validated identically to live requests and although the response schema are identical to live requests, the responses themselves are dynamically generated.

The Paket-Response-Type: Test header indicates whether or not the response is via the API Test Key.

Security

Securing API requests is critical to ensure data integrity. The Paket API employs the following methods to ensure that requests made to its API are authenticated and secure:

Errors

Paket uses conventional HTTP response codes to indicate the success or failure of an API request. In general: Codes in the 2xx range indicate success. Codes in the 4xx range indicate an error that failed given the information provided (e.g., a required parameter was omitted, etc.). Codes in the 5xx range indicate an error with Paket's servers.

Error Code Meaning
400 Bad Request -- Your request is invalid.
401 Unauthorized -- Your API credentials are wrong.
403 Forbidden -- You do not have permission to access the requested resource.
404 Not Found -- The specified resource could not be found.
500 Internal Server Error -- We had a problem with our server. Try again later.
503 Service Unavailable -- We're temporarily offline for maintenance. Please try again later.

Idempotency

The Paket API supports idempotency for safely retrying requests without accidentally performing the same operation twice. When creating or updating an object, use an idempotency key. Then, if a connection error occurs, you can safely repeat the request without risk of creating a second object or performing the update twice.

To perform an idempotent request, provide an additional Idempotency-Key element to the request header.

Paket's idempotency works by saving the resulting status code and body of the first request made for any given idempotency key, regardless of whether it succeedes or fails. Subsequent requests with the same key return the same result.

A requesting Client generates an idempotency key, which is a unique key that the server uses to recognize subsequent retries of the same request. How you create unique keys is up to you, but we suggest using V4 UUIDs, or another random string with enough entropy to avoid collisions. Idempotency keys are up to 255 characters long.

We remove keys from the system automatically after they’re at least 72 hours old and will generate a new request if a key is reused after the original is pruned. The idempotency layer compares incoming parameters to those of the original request and errors if they’re the same to prevent accidental misuse.

We save results only after the execution of an endpoint begins. If incoming parameters fail validation, or the request conflicts with another request that’s executing concurrently, we don’t save the idempotent result because no API endpoint initiates the execution. You can retry these requests.

All POST and PUT requests accept idempotency keys. Don’t send idempotency keys in GET and DELETE requests because it has no effect. These requests are idempotent by definition.

Platform Data

Some Platforms will require custom attributes and schemas that are not available in an API product's default attribute set.

To address this requirement, Platforms are able to configure custom app-level schemas and attributes to be provided by Publisher partners; as well as to define the regional availability of a Platform Client by region or specific countries.

For example, a Platform Client may require a custom app identifier, custom_app_id, is returned in API responses. In order to ensure such custom app identifier is provided by a Publisher Client, the Platform will configure this custom attribute from within the Client in the Paket Developer Portal.

Example platform_data attribute:

{
    ...,
    platform_data: {
        custom_app_id: 12345,
        deep_link: "app://12345/cid12345"
    }
}

When preparing to integrate with the Platform, the Publisher will configure the requested custom attribute, in this case the custom_app_id. This data will then be returned in subsequent Platform API responses under the platform_data attribute.

Taking this example further, perhaps the Platform Client also requires a deep_link url attribute comprised of the custom attribute custom_app_id and the default attribute content_id. In this example, the data must be returned using the following schema: app://custom_app_id/content_id.

Custom Schema Definition

The Platform will also define this schema under the custom attribute deep_link and by using handlebars to define the schema: app://{{custom_app_id}}/{{content_id}}.

If the value of an Publisher's custom_app_id is 12345 and an item's content_id is cid12345, the resulting platform_data attribute returned in an API response to the requesting Platform will look something like the example to the right.

Pagination

Response

{
    "total": 24,
    "next_key": 6,
    "items": [
        {...},
        {...},
        {...},
        {...},
        {...},
    ]
}

Many of our top-level API resources have support for bulk fetches through “list” API methods. For example, you can list Session Participants, Session Lists, and Participant Lists. These list API methods share a common structure and can accept the following two parameters: limit, and next_key.

The limit parameter is provided at the time of the request and limits the number of items returned in the API response. By default, this value is 50.

The next_key parameter is returned in a response in which the total results exceed the default or specified limit and specifies the next item to be returned in a subsequent API request. Alternatively, a request can be made with any next_key value, however, there is no guarantee such an object will exist.

Versioning

When backwards-incompatible changes are made to the API, we release a new, dated version. The current version is 2023-12-01. For information on all API updates, view our API changelog.

By default, requests made to the Paket API use your account’s default API version (controlled in the Developer Portal) unless you override it by setting the Paket-Version header.

Webhook events also use your account’s API version by default, unless you set an API version during endpoint creation.

You can upgrade your API version in the Paket Developer Portal. As a precaution, use API versioning to test a new API version before committing to an upgrade.

Core API

The Core API includes requests that are used for more than one API product and, in many instances, across all of Paket's API products.

API Status

Endpoint

GET https://api.paket.tv/v1

Retrieves the API status by making an authenticated GET request to the API's root path.

The Status Object

The Status Object

{
    "message": "The API is healthy!",
    "client_id: "fab3d02606122a08"
}

Attributes

message string
The API Status message.

client_id string
The unique identifier of the requesting Client.

Retrieve API Status

GET https://api.paket.tv/v1

curl --location 'https://api.paket.tv/v1' \
    --header 'Accept: application/json' \
    --header 'Authorization: Basic <credentials>'

Response

{
    "message": "The API is healthy!"
}

Parameters

No parameters.

Returns

Returns a status object.

Sessions

Endpoint

POST https://api.paket.tv/v1/sessions

A Session represents a Platform end user either at the account or profile level. Whether the Session is associated to the account or profile is, ultimately, up to the Platform to determine. For optimal flexibility, however, a Session ID should be created and stored by a Platform at the profile level.

Ideally, a Session should be indefinitely associated with an end-user's account or profile. So long as the Session ID persists, all items associated with the Session will remain active.

The Session Object

The Session Object

{
  "session_id": "faa37296-dc32-4cce-b8c0-47ac4bc032c1",
  "client_type": "platform",
  "client_id": "fab3d02606122a08",
  "created_at": "2024-02-16T04:41:06.596Z"
}

Attributes

session_id string
A unique Session identifier.

client_type string
The requesting Client type.

client_id string
The requesting Client ID.

created_at string
The ISO 8601 timestamp when the Session was created.

The Session Participant Object

The Session Participant Object is created when a Participant is added to a Session and contains useful information related to a Participant's relationship to a Session.

The Session Participant Object

{
  "app_id": "417f3625d067cbe3",
  "participant_id": "6b0af623-2923-4997-92a6-73f94bbe321e",
  "session_id": "faa37296-dc32-4cce-b8c0-47ac4bc032c1",
  "client_id": "fab3d02606122a08",
  "created_at": "2024-02-16T04:41:06.596Z"
}

Attributes

app_id string
The Publisher's Client ID to which the participant_id belongs.

participant_id string
A unique Participant identifier.

session_id string
A unique Session identifier.

client_type string
The requesting Client type.

client_id string
The requesting Client ID.

created_at string
The ISO 8601 timestamp when the Session was created.

Create a new Session

POST https://api.paket.tv/v1/sessions

curl --location --request POST 'https://api.paket.tv/v1/sessions' \
    --header 'Accept: application/json' \
    --header 'Authorization: Basic <credentials>'

Response

{
  "session_id": "faa37296-dc32-4cce-b8c0-47ac4bc032c1",
  "client_type": "platform",
  "client_id": "fab3d02606122a08",
  "created_at": "2024-02-16T04:41:06.596Z"
}

Parameters

No parameters.

Returns

Returns a Session object.

Participants

Endpoint

POST https://api.paket.tv/v1/participants

A Participant represents a Publisher (or app's) end user either at the account or profile level. Whether the Participant is associated to the account or profile is, ultimately, up to the Publisher to determine. For optimal flexibility, however, a Participant ID should be created and stored by a Publisher at the profile level.

Ideally, a Participant should be indefinitely associated with an end-user's account or profile. So long as the Participant ID persists, all items associated with the Participant will remain active.

Generally, a Publisher interacts with a Platform by explicitly sharing the Participant ID with a Platform and it being matched to an active Session.

The Participant Object

The Participant Object

{
  "participant_id": "6e5060c3-7df4-488e-9e9a-67cd5077b352",
  "client_type": "publisher",
  "client_id": "fe4a9ed381e8e376",
  "created_at": "2024-02-16T04:41:06.596Z"
}

Attributes

participant_id string
The newly generated Participant ID.

client_type string
The requesting Client type.

client_id string
The requesting Client ID.

created_at string
The ISO 8601 timestamp when the Session was created.

Create a new Participant

POST https://api.paket.tv/v1/participants

curl --location --request POST 'https://api.paket.tv/v1/participants' \
    --header 'Accept: application/json' \
    --header 'Authorization: Basic <credentials>'

Response

{
  "participant_id": "6e5060c3-7df4-488e-9e9a-67cd5077b352",
  "client_type": "publisher",
  "client_id": "fe4a9ed381e8e376",
  "created_at": "2024-02-16T04:41:06.596Z"
}

Parameters

No parameters.

Returns

Returns a Participant object.

TV as a Service API

Documentation for the TVaaS API is coming soon.

Bundle API

Documentation for the Bundle API is coming soon.

Webhooks

Webhooks play a key role in communicating critical information (in the form of events) among Platform and Publisher Clients and the Paket service. While not technically a part of the Paket API, this section explains how to set up and secure your webhooks endpoints; and serves as a reference as to which notifications are sent and on which triggering events.

Configure your endpoints

Webhooks are configured discretely under each Client object in the Paket Developer Portal. You may configure multiple endpoints, however, please note that at present all events will be sent to all active endpoints.

Webhook endpoints must be secured using TLS/SSL certificates over HTTPS and are signed using a signing secret, provided on creation of each endpoint. While it is possible to receive events without verifying the webhook signature, it is highly recommended that each event is verified before taking any programmatic action.

Webhooks Configuration

Verify webhook signature

The Paket-Signature header included in each signed event contains a timestamp and one or more signatures that you must verify. The timestamp is prefixed by t=, and each signature is prefixed by a scheme. Schemes start with v, followed by an integer. Currently, the only valid live signature scheme is v1. To aid with testing, Paket sends an additional signature with a fake v0 scheme.

Example Paket-Signature header

Paket-Signature:
t=1709156882568,
v1=f03a63e17d5720d7d01974d115059c8ec05b0f425828a2d0d341020f431bbafa,
v0=3e3be55cd51ef0476ddf7fe83e72f10cf7358e54dd8eab17560dfb879b9d485a

Paket generates signatures using a hash-based message authentication code (HMAC) with SHA-256. To prevent downgrade attacks, ignore all schemes that aren’t v1.

You can have multiple signatures with the same scheme-secret pair when you roll an endpoint’s secret, and keep the previous secret active for up to 24 hours. During this time, your endpoint has multiple active secrets and Paket generates one signature for each secret.

To create a manual solution for verifying signatures, you must complete the following steps:

Step 1: Extract the timestamp and signatures from the header

Split the header using the , character as the separator to get a list of elements. Then split each element using the = character as the separator to get a prefix and value pair.

The value for the prefix t corresponds to the timestamp, and v1 corresponds to the signature (or signatures). You can discard all other elements.

Step 2: Prepare the signed_payload string

Example signed_payload

1709156882568.{"idempotency_key":"62a0152b-6ac4-49f7-b605-5e18cbf70ed8","data":{"participant_id":"6b0af623-2923-4997-92a6-73f94bbe321e","client_id":"286d2a536a5d6b6b","app_id":"417f3625d067cbe3","created_at":"2024-03-05T17:56:26.265Z","session_id":"4c682b9e-65b4-4e43-a5e9-96f58a967240"},"type":"participant.session.created","api_version":"2023-12-10","created":1709661388431,"object":"event","id":"req_ad8a67d675a71a89"}

The signed_payload string is created by concatenating:

Step 3: Determine the expected signature

Example Hashed result

f03a63e17d5720d7d01974d115059c8ec05b0f425828a2d0d341020f431bbafa

Compute an HMAC with the SHA256 hash function. Use the endpoint’s signing secret as the key, and use the signed_payload string as the message.

Step 4: Compare the signatures

Compare the signature (or signatures) in the header to the expected signature. For an equality match, compute the difference between the current timestamp and the received timestamp, then decide if the difference is within your tolerance.

To protect against timing attacks, use a constant-time-string comparison to compare the expected signature to each of the received signatures.

Paket IP addresses

To further protect your endpoints, it is recommended that you implement IP whitelisting of the Paket servers from which you may receive webhook events.

Paket IP Addresses:

Retry behavior

Paket attempts to deliver a given event to your webhook endpoint up to fifteen (15) times over the course of 3 days with an exponential back off. In the Webhooks logs section of the Paket Publisher Portal, you can view when the next retry will occur as well as the event and event data itself.

Events

Events are our way of letting you know when something interesting happens in your account. When an interesting event occurs, we create a new Event object. For example, when a Participant is added to a Session, we create a participant.session.participant_added event, and when a bundle subscription is canceled, we create an participant.subscription.canceled event.

The Event object

Example webhook event

{
  "id": "evt_a75f6d23be8c17b1",
  "object": "event",
  "type": "participant.session.participant_added",
  "created": 1709679883853,
  "api_version": "2023-12-10",
  "request": {
    "id": "req_2442fd8627bedb77",
    "idempotency_key": null
  },
  "data": {
    "participant_id": "6b0af623-2923-4997-92a6-73f94bbe321e",
    "client_id": "286d2a536a5d6b6b",
    "app_id": "417f3625d067cbe3",
    "created_at": "2024-03-05T23:04:43.684Z",
    "session_id": "4c682b9e-65b4-4e43-a5e9-96f58a967240"
  }
}

Attributes

id string
The unique identifier of the webhook event.

object string
Description of the object type (e.g., event).

type string
Description of the event (e.g., participant.session.participant_added or participant.subscription.canceled)

created integer
UTC timestamp when the event was created.

api_version string
The Paket API version through which the triggering API request was made.

request object
An object containing details of the event's triggering API request.

request.id string
The unique identifier of the event's triggering API request.

request.idempotency_key string
The idempotency_key, if any, of the original request. Otherwise null.

data object
An object containing the data associated with the event.

Types of events

This is a list of all the types of events we currently send. We may add more at any time, so in developing and maintaining your code, you should not assume that only these types exist.

You’ll notice that these events follow a pattern: resource.event. Our goal is to design a consistent system that makes things easier to anticipate and code against.

Event

participant.session.deleted data.object is a session participant
Occurs when a Session is removed. Sent to the related Publishers only.

participant.session.participant_added data.object is a session participant
Occurs when a Participant is added to a Session. Sent to the related Publishers only.

participant.session.participant_removed data.object is a session participant
Occurs when a Participant is removed from a Session. Sent to the related Publishers only.

participant.list.item_hidden data.object is a list item
Occurs when a Participant user removes or hides an item from their List via the Platform. Sent to the related Publisher only.