Complete technical documentation for implementing XTOR feeds
XTOR is a standard HTTP/JSON format that enables any content provider to expose their video catalog in a machine-readable way. Clients (like XTOR TV app) can connect to any XTOR-compatible feed.
All XTOR feeds MUST implement these three endpoints:
| Endpoint | Purpose | Required |
|---|---|---|
GET /manifest |
Feed metadata and capabilities | ✅ Yes |
GET /videos |
Paginated video list | ✅ Yes |
GET /videos/:id |
Individual video details | ✅ Yes |
GET /manifest
Returns metadata describing the feed's capabilities, available filters, and sorting options. This is the first endpoint clients should call to discover what features the feed supports.
/videos endpoint.
{
"name": string, // Required: Feed name
"description": string, // Optional: Feed description
"xtor_version": string, // Required: XTOR spec version (e.g., "1.0")
"logo": string, // Optional: Square logo URL (64x64px recommended)
"active": boolean, // Required: Is feed currently active?
"search": boolean, // Optional: Does feed support search?
"filters": [ // Optional: Available filters
{
"id": string, // Filter identifier (e.g., "category")
"label": string, // Human-readable name (e.g., "Category")
"options": [ // Available filter values
{
"id": string, // Option identifier (e.g., "rock")
"label": string // Human-readable name (e.g., "Rock")
}
]
}
],
"sort": { // Optional: Sorting options
"default": string, // Default sort option ID
"options": [
{
"id": string, // Sort identifier (e.g., "recent")
"label": string // Human-readable name (e.g., "Most Recent")
}
]
}
}
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ✅ Yes | Display name of the feed. Shown in client applications and feed lists. |
xtor_version |
string | ✅ Yes | XTOR specification version. Must be "1.0" for this version. |
active |
boolean | ✅ Yes | Whether the feed is currently operational. Set to false during maintenance or if feed is being deprecated. |
| Field | Type | Required | Description |
|---|---|---|---|
description |
string | ❌ No | Brief description of feed content (1-2 sentences recommended). |
logo |
string (URL) | ❌ No | Square logo image URL. Recommended size: 64x64px or larger. Used in feed directories and client UI. |
search |
boolean | ❌ No | Whether feed supports text search via search query parameter. Defaults to false if omitted. |
filters |
array | ❌ No | Array of available filter definitions. See "Filters Structure" below. |
sort |
object | ❌ No | Sorting options configuration. See "Sort Structure" below. |
The filters array defines available filtering options. Each filter object has:
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | ✅ Yes | Filter identifier used in filter query parameter (e.g., "genre", "year"). |
label |
string | ✅ Yes | Human-readable filter name shown in UI (e.g., "Genre", "Year"). |
options |
array | ✅ Yes | Array of available filter values. Must contain at least one option. |
Each option in the options array has:
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | ✅ Yes | Option value used in filter query (e.g., "rock", "2024"). |
label |
string | ✅ Yes | Human-readable option name (e.g., "Rock", "2024"). |
The sort object defines available sorting methods:
| Field | Type | Required | Description |
|---|---|---|---|
default |
string | ❌ No | ID of the default sort option. If omitted, first option is used as default. |
options |
array | ✅ Yes | Array of available sort methods. Must contain at least one option. |
Each option in the options array has:
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | ✅ Yes | Sort method identifier used in sort query parameter (e.g., "recent", "popular"). |
label |
string | ✅ Yes | Human-readable sort method name (e.g., "Most Recent", "Most Popular"). |
GET /manifest
{
"name": "Indie Music Videos",
"description": "Independent artist music videos and live performances",
"xtor_version": "1.0",
"logo": "https://example.com/logo-64x64.png",
"active": true,
"search": true,
"filters": [
{
"id": "genre",
"label": "Genre",
"options": [
{ "id": "rock", "label": "Rock" },
{ "id": "electronic", "label": "Electronic" },
{ "id": "jazz", "label": "Jazz" }
]
},
{
"id": "year",
"label": "Year",
"options": [
{ "id": "2025", "label": "2025" },
{ "id": "2024", "label": "2024" },
{ "id": "2023", "label": "2023" }
]
}
],
"sort": {
"default": "recent",
"options": [
{ "id": "recent", "label": "Most Recent" },
{ "id": "popular", "label": "Most Popular" },
{ "id": "alphabetical", "label": "A-Z" }
]
}
}
{
"name": "Simple Video Feed",
"xtor_version": "1.0",
"active": true
}
false values to explicitly communicate capabilities to clients.
GET /videos
Returns a paginated list of videos with optional filtering, search, and sorting capabilities. This is the primary endpoint for browsing video content.
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
page |
integer | Optional | Page number, starting from 1 (default: 1) | ?page=2 |
limit |
integer | Optional | Items per page (default: 20, max: 100 recommended) | ?limit=50 |
query |
string | Optional | Search query to filter videos by title/description. Only if manifest has search: true |
?query=concert |
filter |
string | Optional | Filter in format key:value. Can be repeated for multiple filters. Keys must match filter IDs from manifest. |
?filter=genre:rock |
sort |
string | Optional | Sort option ID from manifest. Defaults to manifest's default sort if omitted. | ?sort=popular |
GET /videos - First page with defaultsGET /videos?page=2&limit=50 - Second page with 50 itemsGET /videos?filter=genre:rock&filter=year:2024 - Multiple filtersGET /videos?query=concert&sort=recent - Search with sorting
The response MUST be a JSON object with the following structure:
{
"videos": [
{
"id": string, // Required: Unique video identifier
"title": string, // Required: Video title
"thumbnail": string, // Required: Thumbnail image URL
"source_url": string, // Optional: Original content URL
"duration": integer|null, // Optional: Duration in seconds (null = live stream)
"formats": [ // Optional: Video quality formats
{
"quality": integer|null, // Height in pixels (e.g., 720, 1080) or null
"url": string // Required: Direct video playback URL
}
]
}
],
"pagination": {
"page": integer, // Required: Current page number
"has_next": boolean // Required: Whether more pages exist
}
}
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | ✅ Yes | Unique identifier for the video. Used in /videos/:id endpoint. |
title |
string | ✅ Yes | Human-readable video title. Displayed in listings and player. |
thumbnail |
string (URL) | ✅ Yes | Absolute URL to thumbnail image. Should be at least 320x180px. JPEG/PNG/WebP formats recommended. |
source_url |
string (URL) | ❌ No | Link to original source (YouTube, Vimeo, etc.). Used for attribution and "Watch Original" buttons. If omitted, feed is assumed to be the original source. |
duration |
integer or null | ❌ No | Video duration in seconds. Use null for live streams. If omitted, duration is unknown. |
formats |
array | ❌ No* | Optional in list response for performance. If omitted, client will fetch from /videos/:id when user plays. Must be non-empty in detail endpoint. |
formats is optional in the list response, every video MUST be playable. If formats are omitted here, they MUST be present and non-empty in the /videos/:id detail endpoint.
| Field | Type | Required | Description |
|---|---|---|---|
page |
integer | ✅ Yes | Current page number (matches request parameter or default 1). |
has_next |
boolean | ✅ Yes | true if more pages exist, false if this is the last page. |
GET /videos?filter=genre:rock&filter=year:2024&page=1&limit=2&sort=recent
{
"videos": [
{
"id": "mv001",
"title": "Sunset Live Performance",
"thumbnail": "https://example.com/thumbs/mv001.jpg",
"source_url": "https://youtube.com/watch?v=abc123",
"duration": 245,
"formats": [
{ "quality": 480, "url": "https://example.com/videos/mv001-480p.mp4" },
{ "quality": 720, "url": "https://example.com/videos/mv001-720p.mp4" },
{ "quality": 1080, "url": "https://example.com/videos/mv001-1080p.mp4" }
]
},
{
"id": "live001",
"title": "Festival Live Stream",
"thumbnail": "https://example.com/thumbs/live001.jpg",
"duration": null,
"formats": [
{ "quality": null, "url": "https://example.com/streams/live001.m3u8" }
]
}
],
"pagination": {
"page": 1,
"has_next": true
}
}
When included in the list response, each format object has:
| Field | Type | Required | Description |
|---|---|---|---|
quality |
integer or null | ✅ Yes | Video height in pixels (360, 480, 720, 1080, 1440, 2160, etc.) or null for adaptive/unknown quality. |
url |
string (URL) | ✅ Yes | Direct playback URL. Can be MP4, HLS (.m3u8), DASH (.mpd), or any HTTP-accessible video format. |
{"videos": [], "pagination": {"page": 1, "has_next": false}}search: false or omits search field, ignore search parameterformats array in list response for faster loading (lazy load from detail endpoint)/videos/:id.
GET /videos/:id
Returns complete details for a single video, including all available playback formats and full metadata.
formats array MUST be present and non-empty in this response. This is the only place where formats are required to be returned.
| Parameter | Type | Description | Example |
|---|---|---|---|
:id |
string | The unique video identifier from the /videos endpoint |
/videos/mv001 |
{
"id": string, // Required: Unique identifier
"title": string, // Required: Video title
"description": string, // Optional: Full video description
"thumbnail": string, // Required: Thumbnail image URL
"source_url": string, // Optional: Original content URL
"duration": integer|null, // Optional: Duration in seconds (null = live)
"published_at": string, // Optional: ISO 8601 timestamp
"formats": [ // Required: Must have at least one format
{
"quality": integer|null, // Height in pixels or null
"url": string // Required: Direct playback URL
}
]
}
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | ✅ Yes | Unique video identifier. Must match the requested ID. |
title |
string | ✅ Yes | Video title. Displayed in player and listings. |
thumbnail |
string (URL) | ✅ Yes | Absolute URL to thumbnail. Should be at least 320x180px. |
formats |
array | ✅ Yes | Required and must be non-empty. Array of playback formats with quality options. Every XTOR video must be playable. |
description |
string | ❌ No | Full video description with details, context, or credits. Can be multiple paragraphs. Supports plain text. |
source_url |
string (URL) | ❌ No | Link to original source (YouTube, Vimeo, etc.). Used for "Watch Original" buttons and attribution. |
duration |
integer or null | ❌ No | Duration in seconds. Use null for live streams. Used for progress bars and time displays. |
published_at |
string (ISO 8601) | ❌ No | Publication timestamp in ISO 8601 format (e.g., "2025-01-15T20:30:00Z"). Used for "published N days ago" displays. |
This field is REQUIRED and must contain at least one format. Each format object provides a playback URL and quality information:
| Field | Type | Required | Description |
|---|---|---|---|
quality |
integer or null | ✅ Yes | Video height in pixels (360, 480, 720, 1080, 1440, 2160, 4320, etc.) or null for adaptive/unknown quality. Used for quality selector UI. |
url |
string (URL) | ✅ Yes | Direct playback URL. Must be HTTP/HTTPS accessible. Supports MP4, WebM, HLS (.m3u8), DASH (.mpd), etc. |
null for adaptive streaming (HLS/DASH) where quality variesGET /videos/mv001
{
"id": "mv001",
"title": "Sunset Live Performance",
"description": "Recorded live at the Indie Music Festival 2025. Features original songs from the album 'Midnight Dreams' including the hit single 'Sunset Boulevard'. Performed by indie rock band The Wanderers.",
"thumbnail": "https://example.com/thumbs/mv001.jpg",
"source_url": "https://youtube.com/watch?v=abc123&ref=xtor-feed",
"duration": 245,
"published_at": "2025-01-15T20:30:00Z",
"formats": [
{ "quality": 360, "url": "https://example.com/videos/mv001-360p.mp4" },
{ "quality": 480, "url": "https://example.com/videos/mv001-480p.mp4" },
{ "quality": 720, "url": "https://example.com/videos/mv001-720p.mp4" },
{ "quality": 1080, "url": "https://example.com/videos/mv001-1080p.mp4" }
]
}
{
"id": "live001",
"title": "Festival Live Stream",
"description": "24/7 live music festival coverage featuring emerging artists.",
"thumbnail": "https://example.com/thumbs/live001.jpg",
"duration": null,
"formats": [
{ "quality": null, "url": "https://example.com/streams/live001.m3u8" }
]
}
{
"id": "video123",
"title": "Sample Video",
"thumbnail": "https://example.com/thumb.jpg",
"formats": [
{ "quality": 720, "url": "https://example.com/video.mp4" }
]
}
| Status Code | Scenario | Response |
|---|---|---|
404 Not Found |
Video ID doesn't exist | {"error": "Video not found"} |
400 Bad Request |
Invalid ID format | {"error": "Invalid video ID"} |
/videos list response/videos omits formats for performanceXTOR is designed for playable video content. All videos must have at least one format URL.
source_url and no playable formats) are NOT valid XTOR feeds.
For live content, set duration to null and typically use quality: null for adaptive streams.
XTOR supports any HTTP-accessible video format:
quality: null: Display as "Auto" or "Default"source_url present: Show "Watch Original" or "View on [Platform]" buttonAll XTOR feeds MUST set proper CORS headers:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: Content-Type
| Code | Usage |
|---|---|
200 OK |
Successful response |
404 Not Found |
Video ID not found |
400 Bad Request |
Invalid parameters |
500 Internal Server Error |
Server error |
The xtor_version field in the manifest indicates which version of the XTOR specification the feed implements.