---
title: "Streaming API v2: Conventions"
description: "Base URL, methods, headers, pagination, filters, extra list fields, relative images, ids, types and dates."
---

## 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:

```json
{ "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/`](/docs/api-v2/examples/): the test suite
generates them from the code and fails if they drift, so you can use them as
fixtures.
