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:
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:
- Forced HTTPS: Connections are strictly enforced over HTTPS at
https://api.paket.tv/v1
. - TLS/SSL: X.509 server certificates to validate server requests are authenticated as Paket Media, Inc.
- Basic Auth: Basic authentication required for all API requests w/unique Client username and rollable secret keys.
- IP Whitelisting: Restrict access to API requests to your server IP addresses designated at the Client level.
- Encryption-at-rest: All data stored is encrypted-at-rest.
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
.
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.
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:
- The timestamp (as a string)
- The character
.
- The actual JSON payload (that is, the request body)
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:
54.185.102.16
52.40.235.54
35.83.22.57
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.