XTOR Specification v1.0

Complete technical documentation for implementing XTOR feeds

Overview

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.

Key Principles

  • Decentralized - Anyone can host a feed
  • Provider-agnostic - Any client can consume any feed
  • Open standard - No registration or API keys required
  • Minimal - Three simple endpoints

Required Endpoints

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

1. Manifest Endpoint

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.

Discovery Flow: Clients should fetch the manifest first to understand available filters, sort options, and search capability before querying the /videos endpoint.

Response Format

Response Structure
{
  "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")
      }
    ]
  }
}

Required Fields

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.

Optional Fields

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.

Filters Structure

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").

Sort Structure

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").

Complete Example

Request
GET /manifest
Response (200 OK)
{
  "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" }
    ]
  }
}

Minimal Example (Required Fields Only)

Minimal Valid Manifest
{
  "name": "Simple Video Feed",
  "xtor_version": "1.0",
  "active": true
}
Best Practice: Even if your feed doesn't support search or filters initially, include empty arrays or false values to explicitly communicate capabilities to clients.

2. Videos List Endpoint

GET /videos

Returns a paginated list of videos with optional filtering, search, and sorting capabilities. This is the primary endpoint for browsing video content.

Query Parameters

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
Example Requests:
GET /videos - First page with defaults
GET /videos?page=2&limit=50 - Second page with 50 items
GET /videos?filter=genre:rock&filter=year:2024 - Multiple filters
GET /videos?query=concert&sort=recent - Search with sorting

Response Format

The response MUST be a JSON object with the following structure:

Response 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
  }
}

Video Object Fields

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.
Important: While 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.

Pagination Object

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.

Complete Example

Request
GET /videos?filter=genre:rock&filter=year:2024&page=1&limit=2&sort=recent
Response (200 OK)
{
  "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
  }
}

Formats Array (Optional)

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.

Implementation Notes

  • Empty results: If no videos match the criteria, return {"videos": [], "pagination": {"page": 1, "has_next": false}}
  • Invalid filters: Unknown filter keys should be ignored (don't return error)
  • Invalid sort: Unknown sort IDs should fall back to default sort
  • Page out of range: Return empty results if page number exceeds available pages
  • Search not supported: If manifest has search: false or omits search field, ignore search parameter
  • Performance: Consider omitting formats array in list response for faster loading (lazy load from detail endpoint)
Recommended Response Size: Keep responses under 1MB. If including formats inline makes responses too large, omit the formats array and let clients lazy-load from /videos/:id.

3. Video Detail Endpoint

GET /videos/:id

Returns complete details for a single video, including all available playback formats and full metadata.

CRITICAL REQUIREMENT: All videos MUST have at least one playable format. The formats array MUST be present and non-empty in this response. This is the only place where formats are required to be returned.

URL Parameters

Parameter Type Description Example
:id string The unique video identifier from the /videos endpoint /videos/mv001

Response Format

Response Structure
{
  "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
    }
  ]
}

Response Fields

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.

Formats Array Structure

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.
Quality Guidelines:
• Use specific pixel heights when known: 720, 1080, 1440, 2160, etc.
• Use null for adaptive streaming (HLS/DASH) where quality varies
• Order formats from lowest to highest quality for better UX
• Include multiple qualities when available to give users choice

Complete Example

Request
GET /videos/mv001
Response (200 OK)
{
  "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" }
  ]
}

Live Stream Example

Live Stream Response
{
  "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" }
  ]
}

Minimal Example (Required Fields Only)

Minimal Valid Response
{
  "id": "video123",
  "title": "Sample Video",
  "thumbnail": "https://example.com/thumb.jpg",
  "formats": [
    { "quality": 720, "url": "https://example.com/video.mp4" }
  ]
}

Error Responses

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"}

Implementation Notes

  • Lazy Loading: This endpoint is called when users click play if formats were omitted from /videos list response
  • Caching: Consider aggressive caching (e.g., 5-15 minutes) as video metadata rarely changes
  • Performance: Response should be fast (<200ms) since it blocks video playback
  • Empty Formats: An empty formats array is invalid - return 404 or 500 if video has no playable formats
  • URL Expiry: If using signed URLs, ensure they're valid for at least the video's duration + buffer time
Use Cases:
Lazy loading: When /videos omits formats for performance
Full metadata: When user clicks video for details before playing
Deep linking: When user shares/bookmarks specific videos
Quality selection: To show all available quality options in player

Feed Types & Use Cases

XTOR is designed for playable video content. All videos must have at least one format URL.

Note: Pure directory/catalog feeds (with only source_url and no playable formats) are NOT valid XTOR feeds.

Live Streams

For live content, set duration to null and typically use quality: null for adaptive streams.

Supported Video Formats

XTOR supports any HTTP-accessible video format:

  • MP4 / WebM - Direct video files
  • HLS (.m3u8) - Adaptive streaming
  • DASH (.mpd) - Adaptive streaming
  • Direct HTTP streams - Progressive download

Client Behavior Guidelines

Quality Selection

  • When quality: null: Display as "Auto" or "Default"
  • When multiple qualities available: Let user select preferred quality
  • Remember user's quality preference across sessions

Source URL Handling

  • When source_url present: Show "Watch Original" or "View on [Platform]" button
  • On click: Open in system browser or in-app browser
  • Preserve tracking/affiliate parameters in URL

CORS Requirements

All XTOR feeds MUST set proper CORS headers:

Required Headers
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: Content-Type

HTTP Status Codes

Code Usage
200 OK Successful response
404 Not Found Video ID not found
400 Bad Request Invalid parameters
500 Internal Server Error Server error

Versioning

The xtor_version field in the manifest indicates which version of the XTOR specification the feed implements.

License: XTOR is an open format. Anyone can implement it freely without restrictions.