Get Started with Vega Content Personalization
Here’s what you need to know to integrate Vega Content Personalization into your app for Fire TV. If you want to review some of the types of data used for this integration, see the Data Type Reference.
If the data is not accessible or needs to be transformed, this integration may take longer. Confirm your data is stored in the requested format using the documentation below.
Prerequisites
- Access to the source code of your Fire TV app.
- A Fire TV device that supports this integration. Check with your Amazon contact for a list of device types currently supported.
- Your app must participate in the Catalog Ingestion process, so Fire TV recognizes the content IDs.
- Your app must share entitlements for each customer, so Fire TV shows the entitled provider as part of our content discovery experience. For more information, reach out to your Amazon contact.
- Your app must complete the Content Launcher integration.
Integration steps
Step 1. Include the package dependencies in your app
Add the kepler-content-personalization
and headless-task-manager
as well as amzn/kepler-epg-provider
dependencies in your package.json file.
"dependencies": {
"@amazon-devices/react-native-kepler": "~2.0.0",
"react": "18.2.0",
"react-native": "0.72.0",
"@amazon-devices/kepler-content-personalization": "^1.2.0",
"@amazon-devices/kepler-epg-provider": "^1.0.0",
"@amazon-devices/headless-task-manager": "^1.0.0"
}
- The
kepler-content-personalization
package provides APIs for sending your content personalization data to the system. - The
amzn/kepler-epg-provider
package would provide the dependencies used forchannelDescriptor
inPlaybackEvent
data model. - The
headless-task-manager
package provides APIs to register your data-pull background service with the system. More details are given below.
Step 2. Update your manifest file
Update your manifest.toml file to include Content Personalization support. Below is an example for a sample app:
schema-version = 1
## Define your package
[package]
title = "Vega Video app"
version = "2.17.0"
id = "com.amazondeveloper.keplervideoapp"
[components]
## Define your app's interactive component (if it doesn't already exist)
[[components.interactive]]
id = "com.amazondeveloper.keplervideoapp.main"
library-name = "com.amazondeveloper.keplervideoapp"
runtime-module = "/com.amazon.kepler.keplerscript.runtime.loader_2@IKeplerScript_2_0"
categories = ["com.amazon.category.kva"]
launch-type = "singleton"
## Define your app's service component which can handle data request.
[[components.service]]
id = "com.amazondeveloper.keplervideoapp.content.dataRefresh.provider"
runtime-module = "/com.amazon.kepler.headless.runtime.loader_2@IKeplerScript_2_0"
launch-type = "singleton"
categories = ["com.amazon.category.kepler.media"]
## Define a process group for each component
[processes]
[[processes.group]]
component-ids = ["com.amazondeveloper.keplervideoapp.main"]
[[processes.group]]
component-ids = ["com.amazondeveloper.keplervideoapp.content.dataRefresh.provider"]
[wants]
## Defines that your app has a dependency on the Content Personalization data service
[[wants.service]]
id = "com.amazon.tv.developer.dataservice"
[needs]
## Defines the privilege your app needs in order to use the Content Personalization interface to provide data
[[needs.privilege]]
id = "com.amazon.tv.content-personalization.privilege.provide-data"
[offers]
## Defines the data refresh service component of your app
[[offers.service]]
id = "com.amazondeveloper.keplervideoapp.content.dataRefresh.provider"
## Defines the interactive component of your app
[[offers.interaction]]
id = "com.amazondeveloper.keplervideoapp.main"
## Add extras to declare support for Content Personalization
[[extras]]
key = "interface.provider"
## If you're utilizing account login or content launcher interface,
## replace component-id with appropriate service component. Follow Account Login Integration Guide
component-id = "com.amazondeveloper.keplervideoapp.content.dataRefresh.provider"
## Defines support for the Content Personalization interface and the attributes
[[extras.value.application.interface]]
interface_name = "com.amazon.kepler.media.IContentPersonalizationServer"
attribute_options = ["SupportedCustomerLists", "DataRefreshComponentId"]
[extras.value.application.interface.static_values]
SupportedCustomerLists = ["Watchlist"]
DataRefreshComponentId = "com.amazondeveloper.keplervideoapp.content.dataRefresh.provider"
Here is an example of the correct way to define multiple interfaces:
[extras.value.application]
[[extras.value.application.interface]]
interface_name = "com.amazon.kepler.media.IContentPersonalizationServer"
attribute_options = ["SupportedCustomerLists", "DataRefreshComponentId"]
[extras.value.application.interface.static_values]
SupportedCustomerLists = ["Watchlist"]
DataRefreshComponentId = "com.amazondeveloper.keplervideoapp.content.dataRefresh.provider"
[[extras.value.application.interface]]
interface_name = "com.amazon.kepler.media.IContentLauncherServer"
override_command_component = { LaunchContent = "com.amazondeveloper.keplervideoapp.main" }
attribute_options = ["partner-id"]
[extras.value.application.interface.static_values]
partner-id = "<Your partner id>"
And here is an incorrect way to do this. Do not do it this way.
[extras.value.application]
[[extras.value.application.interface]]
interface_name = "com.amazon.kepler.media.IContentPersonalizationServer"
attribute_options = ["SupportedCustomerLists", "DataRefreshComponentId"]
[[extras.value.application.interface]]
interface_name = "com.amazon.kepler.media.IContentLauncherServer"
override_command_component = { LaunchContent = "com.amazondeveloper.keplervideoapp.main" }
attribute_options = ["partner-id"]
[extras.value.application.interface.static_values]
partner-id = "<Your partner id>"
SupportedCustomerLists = ["Watchlist"]
DataRefreshComponentId = "com.amazondeveloper.keplervideoapp.content.dataRefresh.provider"
Step 3. Make a sample API call
Begin with a sample/mock event generated at the app launch. To construct the event and send it, use the following code:
// Example playback event generated when user starts watching content
const playbackEvent: IPlaybackEvent = new PlaybackEventBuilder()
.playbackPositionMs(0)
.playbackState(PlaybackState.PLAYING)
.durationMs(2000)
.eventTimestamp(new Date())
.contentId(
new ContentIdBuilder()
.id('content_CDF_ID')
.idNamespace(ContentIdNamespaces.NAMESPACE_CDF_ID)
.build(),
)
.profileId(
new ProfileIdBuilder()
.id('myProfileId')
.idNamespace(ProfileIdNamespaces.NAMESPACE_APP_INTERNAL)
.build(),
)
.buildActiveEvent();
// Send the event
ContentPersonalizationServer.reportNewPlaybackEvent(playbackEvent);
Step 4. Validate the integration
Trigger the sample event code you constructed to run inside your app. After you run the code successfully, view the logs to validate the SDK has been linked to your app and is processing the message.
You can validate the steps by searching your app logs:
journalctl --follow |grep -Ei 'kepler.tv.personalization'
The log message you receive for the event shown in Step 3 should contain at least one of the following logs:
Dec 15 00:53:15.705114 carat-3fc19fb5ae526e4d local0.info keplerscript-ru[22814]: 1483317178 INFO kepler.tv.personalization.ancp.turbomodule:reportNewPlaybackEvent called
Dec 15 00:53:15.711945 carat-3fc19fb5ae526e4d local0.info keplerscript-ru[22814]: 1483317178 INFO kepler.tv.personalization:PlaybackEventBuilder buildActiveEvent() called
Dec 15 00:53:15.711990 carat-3fc19fb5ae526e4d local0.info keplerscript-ru[22814]: 1483317178 INFO kepler.tv.personalization:reportNewPlaybackEvent call received
Step 5. Make API calls as part of in-app functionality
For each data type, review the data type's When to Send section to understand where in your code to make calls to Vega Content Personalization. Find the relevant parts of your code that run when customers take each action and add an API call. Each data type has different triggers.
- For Watch Activity, see Watch Activity: When to Send.
- For Individual Content Entitlement, see Individual Content Entitlements: When to Send.
- For Watchlist, see Watchlist: When to Send.
For example, when a customer adds an item to their watchlist, we expect you to call the reportNewCustomerListEntry
API with the relevant information. First locate the code in your app that adds items to the watchlist, and then make the API call as part of your logic.
Step 6. Implement your data pull service for background or off-device data
To allow Amazon to pull data from your app, implement the service as described below. The service contains everything needed to setup and connect, only requiring you to implement the functions to send data to a receiver object. This object allows you to share data in chunks, as needed. This prevents loading large lists into memory.
-
Create a service.js file with the same path where package.json is located with the following content. This tells the system about the entry points for your
content.dataRefresh.provider
service.import { HeadlessEntryPointRegistry } from '@amazon-devices/headless-task-manager'; import { onStartService, onStopService, } from './src/headless/HeadlessService'; HeadlessEntryPointRegistry.registerHeadlessEntryPoint2( 'com.amazondeveloper.keplervideoapp.content.dataRefresh.provider::onStartService', () => onStartService, ); HeadlessEntryPointRegistry.registerHeadlessEntryPoint2( 'com.amazondeveloper.keplervideoapp.content.dataRefresh.provider::onStopService', () => onStopService, );
-
Create your Headless Service Interface file under src/headless/HeadlessServiceInterface.ts with the following content:
import { IComponentInstance, } from "@amazon-devices/react-native-kepler"; export interface HeadlessServiceInterface { /** * This function is called when native service onStart is called. * @param {IComponentInstance} componentInstance - The headless component instance. */ onStart(componentInstance: IComponentInstance): Promise<void>; /** * This function is called when native service onStop is called. * @param {IComponentInstance} componentInstance - The headless component instance. */ onStop(componentInstance: IComponentInstance): Promise<void>; }
-
Create your data pull service under src/headless/HeadlessService.ts with the following content:
import { ContentPersonalizationServer, CustomerListType, IContentEntitlementsHandler, IContentEntitlementsProvider, ICustomerListEntriesHandler, ICustomerListEntriesProvider, IPlaybackEventsHandler, IPlaybackEventsProvider } from "@amazon-devices/kepler-content-personalization"; import { HeadlessServiceInterface } from "./HeadlessInterface"; import { IComponentInstance } from "@amazon-devices/react-native-kepler"; /*********** Content Personalization Handlers *************/ const contentEntitlementsHandler: IContentEntitlementsHandler = { getAllContentEntitlements: ( contentEntitlementsProvider: IContentEntitlementsProvider, ) => { contentEntitlementsProvider.addContentEntitlementChunk(<ADD ENTITLEMENTS>); contentEntitlementsProvider.commit(); }, }; const customerListEntriesHandler: ICustomerListEntriesHandler = { getAllCustomerListEntries: ( listType: CustomerListType, customerListEntriesProvider: ICustomerListEntriesProvider, ) => { customerListEntriesProvider.addCustomerListChunk(listType, <ADD LIST ENTRIES>); customerListEntriesProvider.commit(); }, }; const playbackEventsHandler: IPlaybackEventsHandler = { getPlaybackEventsSince: ( sinceTimestamp: Date, playbackEventsProvider: IPlaybackEventsProvider, ) => { playbackEventsProvider.addPlaybackEventChunk(<ADD PLAYBACK EVENTS>); playbackEventsProvider.commit(); }, }; /*********** Assign the Content Personalization Handlers *************/ class HeadlessService implements HeadlessServiceInterface { onStart(componentInstance: IComponentInstance): Promise<void> { ContentPersonalizationServer.setContentEntitlementsHandlerForComponent( contentEntitlementsHandler, componentInstance ); ContentPersonalizationServer.setCustomerListEntriesHandlerForComponent( customerListEntriesHandler, componentInstance ); ContentPersonalizationServer.setPlaybackEventsHandlerForComponent( playbackEventsHandler, componentInstance ); return Promise.resolve(); } onStop(componentInstance: IComponentInstance): Promise<void> { return Promise.resolve(); } } const HeadlessServiceInstance = new HeadlessService() as HeadlessServiceInterface; export const onStartService = (componentInstance: IComponentInstance): Promise<void> => { return HeadlessServiceInstance.onStart(componentInstance); }; export const onStopService = (componentInstance: IComponentInstance): Promise<void> => { return HeadlessServiceInstance.onStop(componentInstance); };
Implementation details
Make changes to your catalog integration when directed by your Amazon contact
To make use of the entitlement and activity data, Fire TV needs the following data from your existing Fire TV catalog integration. Fire TV’s public catalog integration documentation will be updated to include these elements in the future, but here is a preview of what’s needed concerning the Vega Content Personalization integration. Your Amazon contact will inform you of when to begin these catalog changes.
- TVOD (Purchases and Rentals) - Add new purchase and rental offers to applicable titles. They will look identical to subscription offers, but without a Subscription ID.
- International - For expansion into other countries, Fire TV is moving from per-country catalogs to Fire TV’s global catalog format. The above mentioned catalog changes enable an easier transition if included and launched as part of your global catalog. However, Fire TV still supports per-country catalogs.
Amazon content ID
Vega Content Personalization supports content identification across different namespaces. All content IDs must be passed with an associated namespace in order for Amazon to identify the content item. Amazon supports the following namespace:
Namespace | Description |
---|---|
cdf_id |
This is the ID space, since data is shared in your Amazon catalog integration. These are the IDs you have specified for each piece of content. It corresponds to the CommonWorkType/ID field in the Catalog Data Format (CDF). |
Amazon profile ID
All data types allow you to share an associated profile ID. Since there are different profile types, specify the namespace. Amazon currently supports the following profile ID namespace:
Namespace | Description |
---|---|
app_internal |
This namespace indicates you are sharing a string representation of your internal profile ID for the active profile. This ID serves to identify specific profiles in your app, so we can attribute activity to the right Fire TV profile. Do not provide the actual ID you use internally. The provided value should not allow for the identification of the customer. We recommend taking a hash value of your internal profile ID, which should be the same across all devices. Also, do not send us the profile name provided by the customer. Even if your app doesn't use profiles, provide a consistent value. |
Reporting updates
The SDK contains functionality to share information using three types of operations:
New
(example:reportNewCustomerListEntry
)- Represents an incremental update to add a new entry to the dataset.Removed
(example:reportRemovedCustomerListEntry
) - Represents a decremental update to remove an entry from the dataset.Refreshed
(example:reportRefreshedCustomerList
) - Indicates that there have been changes to the dataset due to off-device action, and a new refreshed version of the list needs to be fetched using the data pull service.
New
and Removed
are useful when the customer interacts with content in the app, and it performs adds or removes. The Refreshed
update is useful when there are significant changes to the list due to customer sign-in, off-device activity, or if you suspect the list is out of sync for another reason.
Providing large sets of data
When supplying large amounts of data through the data pull service, pull the data in reasonably small pages from your cloud backend and call the provider APIs as the data is is being pulled down. This will limit memory usage for your service and prevents your app from being terminated by the system.
An example flow might look something like this:
const contentEntitlementsHandler: IContentEntitlementsHandler = {
getAllContentEntitlements: (
provider: IContentEntitlementsProvider,
) => {
let myCloudHasMoreData = true;
while(myCloudHasMoreData){
let myCloudResponse = getNextPageFromMyBackend();
let myCloudData = myCloudResponse.getData();
let entitlements: IContentEntitlements[] = [];
for(let element in myCloudData){
let entitlement = <build data with ContentEntitlementBuilder>
entitlements.push(entitlement)
}
provider.addContentEntitlementChunk(entitlements);
myCloudHasMoreData = myCloudResponse.hasMoreData();
}
provider.commit();
},
};
Last updated: Sep 30, 2025