Use this guide if you host your videos on your own server or CDN and want to hand Tappla a feed you control. Your system decides what is in the app, how the rows are ordered and what is featured — none of that has to live in our dashboard.
The format is Roku's Direct Publisher JSON schema. If you already produce one of those, you are most of the way there. This page states exactly which parts of it we read.
The same feed drives Apple TV, Roku and Fire TV, so you maintain one catalog rather than three.
Check your feed before you rely on it
In your dashboard under Integrations there is a Check this feed button next to your feed URL, and pasted feeds are checked when you save them. It applies the same rules the import does, so a pass there means the import passes.
It is worth using. "Valid JSON" and "will produce a working app" are different questions, and a feed can be perfectly formed and still show nothing on a television.
Where we fetch it from
These are enforced. A feed that breaks any of them is refused, and your previous catalog keeps serving.
HTTPS only. Plain HTTP is refused.
A publicly reachable address. Internal or private hosts are blocked.
No redirects. The URL you enter must serve the feed itself.
Content-Type must contain application/json. A feed served as text/plain is refused even when the JSON is fine — this catches a lot of people hosting from a static file server.
We re-read your feed roughly every ten minutes, and immediately when you press Re-sync now. Keep the URL stable.
Sample feed
{
"providerName": "ACME Corp",
"lastUpdated": "2026-08-17T08:23:03Z",
"playlists": [
{ "name": "latest-videos", "itemIds": ["jin4sce", "ubm6wzj"] }
],
"categories": [
{ "playlistName": "latest-videos", "name": "My latest videos" }
],
"shortFormVideos": [
{
"id": "jin4sce",
"title": "Video title 1",
"shortDescription": "Description of video 1",
"thumbnail": "https://your-cdn.com/video1_thumbnail.jpg",
"content": {
"dateAdded": "2026-08-01T17:00:00Z",
"duration": 339,
"videos": [
{ "url": "https://your-cdn.com/video1.m3u8", "quality": "HD", "videoType": "HLS" }
]
}
}
]
}What every video needs
id — unique and stable across the whole feed. This is what playlists point at.
title
content.videos — at least one entry. An item with none is skipped, and a row where every item is skipped disappears from the app. This is the single most common reason a feed imports and the app still looks empty.
content.dateAdded — recommended. Without it, sorting by date has nothing to sort on.
thumbnail — an absolute HTTPS URL, 16:9, JPEG. 1280x720 or 1920x1080 works well.
Ship HLS. Apple TV and Roku both need it for anything longer than a short clip. One adaptive master.m3u8 per video is the right shape — there is no benefit to listing renditions separately, the manifest already carries them.
Two rules that catch people out
A playlist name is an identifier, not a label. When you reorder or hide rows in the Tappla dashboard, we remember your choice against the playlist's name. Rename it in your feed and that row loses its saved position and visibility. If you want the visible label to change, keep name fixed and put the label in categories — that is exactly what the indirection is for.
Do not name a playlist "Live". The apps treat "live" as a keyword to pick the live presenter and playback path, and Tappla injects its own live row when you have a live stream configured. A playlist of your own with that name is dropped.
Two playlists sharing a name is fine — the second is shown as "Name (2)". Nothing is discarded.
Videos, movies, specials, live
shortFormVideos, movies, tvSpecials and liveFeeds are merged into one pool, and it makes no difference which you use. What matters is whether a playlist points at an item:
Referenced by a playlist, and it appears in that row.
Referenced by none, and it lands in an automatic row named after its array ("Movies", "Short Form Videos", and so on).
If you want control over your rows, reference every item from a playlist. The automatic rows exist so nothing silently disappears, not as a layout to rely on.
Members-only content
You can mark rows as members only directly in the dashboard, under Playlists & Order — no feed changes needed.
If you would rather drive it from your own system, add a tappla object to an item:
"tappla": {
"access": "locked",
"entitlementTags": ["premium"]
}Namespaced under one key on purpose, so your feed stays a valid Direct Publisher document and you do not have to fork it. Everything in that object is optional, and a feed without it behaves exactly as it does today.
What we ignore
These are part of the Direct Publisher schema and cause no harm, but we do not currently use them: longDescription, genres, tags, rating, credits, releaseDate, content.captions, content.language, validity periods, ad breaks, and series/season/episode structures.
There is also no DRM: we play clear HLS.
Before you point us at a URL
HTTPS, public host, no redirect, Content-Type application/json
Every item has a unique stable id and at least one entry in content.videos
Streams are HLS (master.m3u8), typed "videoType": "HLS"
content.dateAdded present if you care about date ordering
Playlist names chosen as permanent identifiers, labels via categories
No playlist named "Live"
Thumbnails absolute HTTPS, 16:9, JPEG
Run Check this feed and read what it says
Stuck on something this page does not answer? Write to [email protected] with your feed URL and we will look at it with you.