Base URL and methods

https://<gateway>/api/v2/<action>, with query parameters as usual (/api/v2/search?q=news). Every response is application/json.

  • POST, form-encoded body (application/x-www-form-urlencoded): login, logout, logout-all, refresh-session, history-update, history-remove and history-clear. All of them except refresh-session answer 405 to any other method.
  • GET for everything else.

Headers

Header Who sends it Where
X-API-KEY the gateway every request
X-XAI-Client-IP the gateway every request
Authorization: Bearer <session_token> the app every request except identity and login

Pagination, sorting and filters

Lists accept offset (default 0) and limit (default 50, 1-200; larger values are clamped to 200) and answer:

{ "total": 1234, "offset": 0, "limit": 50, "next_offset": 50, "items": [ ... ] }

next_offset is null on the last page. total counts what this line can see, after filters.

  • sort_by: name (natural, case-insensitive), added or rating. Without it, the panel's own order (the channel num order for channels). Series have no added: sort them by name or rating. sort_order: asc (default) or desc.
  • genre_ids (movies and series): comma-separated ids from GET /genres; genre_mode: or (default, any of them) or and (all of them).
  • category_id: 0 or omitted means all categories.

List fields and compact series: fields

With capability list_fields. vod-movies, series, search and recently-added accept fields, a comma-separated list of extra fields for movie and series items, so a row of posters does not need a vod-info per title:

Field Type Value
plot string Synopsis, HTML entities decoded, tags removed, at most 300 characters cut at a word boundary with …; "" if none
backdrop string or null The first backdrop
year int or null Release year
duration_secs int or null Movie length; for a series, the typical length of an episode
genres string As in the detail, entities decoded; "" if none
  • Only the fields you ask for are added, in the order of the table, before is_adult. Channels get nothing.
  • With fields present (even empty, fields=), series items are compact: id, type, name, poster, rating, tmdb_id, category_ids, last_modified, is_adult and the fields asked for. cast, director, youtube_trailer, backdrops, release_date, episode_run_time and the full plot stay in series-info. With full_info=1 the compact item also carries seasons and episodes.
  • Without fields every item is exactly as before.
  • An unknown or repeated name answers 400 invalid_parameter. Other endpoints ignore fields.

Images: images=relative

With capability relative_images. Any endpoint accepts images=relative: every item image the panel itself serves (icon, poster, backdrop, backdrops, image, series_poster and the season poster) comes as a path (/images/1f3a….jpg, no host and no query) to request through your gateway; any other image stays an absolute URL from a third party (TMDB, a logo host…), to load directly. Without the parameter, URLs come as stored. Any other value answers 400 invalid_parameter. brand-info and actor-images are not affected.

Ids and types

  • Every item has id and type (channel, movie, series, episode). Channels, movies and series have independent id spaces: channel 123 and movie 123 are different items, so keep the type next to the id.
  • Detail endpoints take id: vod-info?id=, series-info?id=, episode-info?id=, short-epg?id=, epg?id=, upcoming-matches?id=.
  • Playback uses its own type names: channel, vod (a movie) and saga (an episode), in get-playback-url, playback-routes and the playback token (section 7).
  • Types are exact: ids, counts and dates are integers; rating is a number (0-10) or null; tmdb_id is an integer or null; booleans are true/false; category_ids and backdrops are arrays (possibly empty). Text fields that may be missing are null (icon, poster, epg_id) or "" (descriptions).
  • Text lists: genres, cast and director are display text with commas.
  • Adult content: every category and every item (channel, movie, series, episode) carries is_adult. An item is adult when any of its categories is. The panel does not hide anything: hide or lock adult content in the app (for example behind a parental PIN), also in search, recently added and history. A change made by the reseller can take a few minutes to show.
  • Hidden categories: the reseller can hide categories for a line. They are left out everywhere: category lists, category_ids, lists, search, recently added, events, epg-now and history. An item whose categories are all hidden is left out (one without categories is shown), and its detail endpoint answers 404 not_found. num stays the channel's position in the whole package, so hiding a category does not renumber the others.
  • Dates: unix seconds (UTC) everywhere, except exp_user/exp_token (ISO 8601 UTC, 2027-01-15T00:00:00Z; exp_user is null for a line that never expires) and release_date/air_date (YYYY-MM-DD or "").
  • New fields may be added at any time: ignore unknown ones.

Every endpoint has a real example in examples/: the test suite generates them from the code and fails if they drift, so you can use them as fixtures.