Creating a Custom Mode
A Custom Mode lets you plug your own content library into Combust Media. You run a small server that returns JSON, generate an encrypted mode code from this site, and share that code with users — they paste it into the app to unlock your content.
Combust Media never proxies your content: the app talks directly to your server using the URLs in your descriptor. You stay in full control of what you serve and to whom.
Overview
1. Build a server that answers the mode contract (JSON routes)
2. Write a mode descriptor (title, URLs, bearer token, …)
3. Generate an encrypted code here → Create a mode
4. Share the code — users add it in the app to unlock your mode
There are four media_type values:
media_type |
Player | Flow | Use for |
|---|---|---|---|
vod_video |
Video | Main → Info → Watch | Movies, series |
vod_audio |
Audio | Main → Info → Watch | Audiobooks, podcasts |
live_tv |
Video | Main → Watch | Live TV channels |
live_radio |
Audio | Main → Watch | Live radio stations |
Mobile currently ships
vod_video. The others are part of the contract and are rolling out.
Step 1 — Build your server
Your server exposes a few JSON endpoints. Every route must require your bearer token (sent as
Authorization: Bearer <token> on each request).
Routes for vod_video / vod_audio
| Route | Required | Returns |
|---|---|---|
GET /main |
Yes | Home-screen sections + items |
GET /info |
Yes | Detail for one item |
GET /watch |
Yes | Playback URL + subtitles |
GET /search/[query] |
No | Paginated search results |
GET /main
{
"sections": [
{
"title": "Trending Movies",
"media_items": [
{
"id": "movie1",
"title": "John Wick: Chapter 4",
"image": "https://img.example.com/3.jpg",
"type": "movie",
"release_date": "2023",
"duration": "120m",
"info_url": "https://yourserver.com/info?id=movie1"
}
]
}
]
}
Each item's type is movie or series. info_url is called next.
Important: every URL you embed (info_url, watch_url) must be reachable from the user's
device — use your public host, not localhost.
GET /info
The app calls the item's info_url. episodes is always an array — a single entry for a
movie, one per episode for a series.
{
"id": "tv1",
"title": "Platonic",
"cover": "https://img.example.com/cover_1.jpg",
"image": "https://img.example.com/1.jpg",
"description": "A long description…",
"type": "series",
"release_date": "2023-05-23",
"duration": "45 min",
"rating": "9.0",
"episodes": [
{ "title": "Eps 1: Pilot", "episode": "1", "season": "1", "watch_url": "https://yourserver.com/watch?id=tv1_1" }
]
}
For a movie, episodes is [{ "watch_url": "https://yourserver.com/watch?id=movie1" }].
GET /watch
The app calls the watch_url. Return the direct media URL (e.g. an HLS .m3u8) and any subtitles
(empty array if none).
{
"url": "https://yourserver.com/media/file.m3u8",
"subtitles": [
{ "url": "https://yourserver.com/subs/eng.vtt", "lang": "English" }
]
}
GET /search/[query]
Only if you set a search URL. The app appends the URL-encoded query and an optional ?page.
{
"next_page_url": "https://yourserver.com/search/rick?page=2",
"prev_page_url": null,
"media_items": [ /* same shape as /main items */ ]
}
Live routes (live_tv / live_radio)
Live content skips the info step — tapping a channel goes straight to playback. /main returns
channels (with logo, watch_url, optional info_url for EPG) instead of media_items, and
/watch returns just url.
Step 2 — Write your descriptor
Your descriptor is the set of fields below. You don't write JSON by hand — the mode builder form collects them.
| Field | Required | Notes |
|---|---|---|
title |
Yes | Display name |
description |
Yes | What the mode provides |
media_type |
Yes | One of the four types |
logo_url |
Yes | Square image, ≥200×200 |
bearer_token |
Yes | Token your server checks on every request |
main_screen_url |
Yes | Your /main URL |
search_screen_url |
No | Your /search URL |
Modes have no developer-settable expiry. Every mode ships locked — access is granted per person with a renewal code (see below).
Step 3 — Generate your code
Open the mode builder, fill in the fields, and click Generate code. You'll get
a string that starts with v1:. The descriptor is encrypted server-side with a key that never
leaves Combust Media — the raw fields (including your bearer token) are never exposed in the code.
Step 4 — Share it
Give the v1:… code to your users however you like (email, a web page, a private group). In the
app they open Custom → Add, paste the code, and your mode appears — but it stays locked
until they redeem a renewal code, so pair it with one per user (see below). You can also add codes
yourself on the Custom modes page.
Access & renewal codes
A mode code grants no access on its own — every mode expires the moment it's created, so it lands in a user's app locked. Access is granted per person with a renewal code:
- You mint renewal codes on the renewal codes page. Each carries a grants access until date and is single-use (self-expires if unredeemed).
- The user redeems a code (
XXXX-XXXX) in the app, which sets their expiry to that date. - Redeeming again with a later date extends them; mint one code per person.
So distribute the v1:… mode code together with a renewal code for each user. This runs
subscription-style, per-person access — different people, different expiry dates, one mode — with
no payments handled inside the app.
Good to know
- Caching: the app caches
/mainfor about 30 minutes;/infoand/watchare always fetched fresh, so playback URLs can be short-lived or signed. - Security: your bearer token authorizes every request — rotate it (with a new code) if it leaks. The encrypted code is safe to distribute; the underlying token is not embedded in plaintext.
- Reachability: test that every URL your server returns loads from a phone on mobile data, not just your local network.