---
title: "action=get_streams"
description: "List live streams in the panel catalog. Paginated with limit and start (offset). Response includes a next_cursor for keyset-style paging."
---

# action=get_streams

Lists live streams (channels) in the panel catalog. Both admin and reseller keys see the full catalog; this endpoint is not filtered by reseller. That is intentional. A reseller building a storefront needs to display what content exists even for packages or bouquets they do not personally sell.

The endpoint accepts the classic XC `limit` and `start` pagination parameters. `start` is a best-effort bridge to the native cursor-based pagination; the response also carries a `next_cursor` field for callers that prefer to walk pages by the last id they saw.

## Endpoint

`GET https://<your-panel-domain>/panel-api/xc/{accesscode}/admin/index.php?action=get_streams`

Also accepted: POST with `action=get_streams` in the body, and the `/reseller/index.php` sub-path.

## Authentication

Send the API key as `?api_key=<token>`, as an `api_key=<token>` POST field, or as `Authorization: Bearer <token>`.

## Required scope

`streams:read`. A key without the scope gets `STATUS_NO_PERMISSIONS` with `error: "insufficient_scope"`.

## Query parameters

| Name | Type | Required | Default | Description |
| ---- | ---- | -------- | ------- | ----------- |
| `action` | string | Yes | | Must be `get_streams`. |
| `api_key` | string | Yes (if not using Bearer) | | The API key. |
| `limit` | int | No | `50` | Page size. Minimum `1`, maximum `100`. Values above `100` are silently clamped. |
| `start` | int | No | `0` | Best-effort offset. Mapped to the native `cursor` (return items with `id > start`). See the note under [Pagination](#pagination) below. |

## Response

`data.items` is the current page of `Stream` objects. `data.next_cursor` is the numeric id you feed back in as `start=<next_cursor>` for the next page, or `null` when the last page is reached.

```json
{
  "status": "STATUS_SUCCESS",
  "data": {
    "items": [
      {
        "id": 30,
        "name": "Channel 30",
        "icon": "https://cdn.example.com/logos/channel-30.png",
        "categories": [
          { "id": 1, "name": "Category 1" }
        ]
      },
      {
        "id": 38,
        "name": "Channel 38",
        "icon": "https://cdn.example.com/logos/channel-38.png",
        "categories": [
          { "id": 2, "name": "Category 2" }
        ]
      },
      {
        "id": 39,
        "name": "Channel 39",
        "icon": "https://cdn.example.com/logos/channel-39.png",
        "categories": [
          { "id": 2, "name": "Category 2" }
        ]
      }
    ],
    "next_cursor": 39
  }
}
```

Field-by-field.

| Field | Type | Description |
|---|---|---|
| `id` | int | Stream id. Use it with [action=get_stream](/docs/?page=xc-ref-streams-get) for detail. |
| `name` | string | Human-readable channel name. |
| `icon` | string | Absolute URL of the channel logo, or empty string if none. |
| `categories` | array | Zero or more category objects the stream belongs to. Each has `id` and `name`. |

## Pagination

The XC dialect maps classic `start`-based offset paging to the native `cursor` keyset paging.

- With `start` omitted or `0`, you get the first page starting from the lowest id.
- With `start = N > 0`, the compat layer sends `cursor = N` to the native handler, which returns items with `id > N`.
- The response's `next_cursor` is the id of the last item on the current page. Pass it as `start=<next_cursor>` on the next request. When `next_cursor` is `null`, there are no more pages.

Because the mapping is `cursor > id`, not a true offset, `start=<N>` skips **ids up to and including N**, not "the first N rows". If your ids are dense and monotonic (typical for a healthy panel), that is what you want. If your ids are sparse (rows deleted over time), the two behaviors diverge. For reliable large-scale pagination, migrate to the native `GET /panel-api/v1/streams` and consume `next_cursor` directly (see [Panel API Catalog](/docs/?page=xai-ref-overview#streams)).

## Examples

### cURL

```bash
# First page
curl "https://<your-panel-domain>/panel-api/xc/panel_api/admin/index.php?api_key=<your-api-key>&action=get_streams&limit=50"

# Follow-up page by cursor id from the previous response
curl "https://<your-panel-domain>/panel-api/xc/panel_api/admin/index.php?api_key=<your-api-key>&action=get_streams&limit=50&start=39"
```

### PHP raw

```php
$base = 'https://<your-panel-domain>/panel-api/xc/panel_api/admin/index.php';
$token = '<your-api-key>';

$start = 0;
$all = [];
do {
    $url = $base . '?' . http_build_query([
        'api_key' => $token,
        'action'  => 'get_streams',
        'limit'   => 100,
        'start'   => $start,
    ]);
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $body = json_decode(curl_exec($ch), true);
    curl_close($ch);
    if (($body['status'] ?? '') !== 'STATUS_SUCCESS') {
        throw new RuntimeException('get_streams failed: ' . ($body['data']['message'] ?? 'unknown'));
    }
    $all = array_merge($all, $body['data']['items']);
    $start = $body['data']['next_cursor'];
} while ($start !== null);

echo count($all), " streams total\n";
```

### Python raw

```python
import requests

base = "https://<your-panel-domain>/panel-api/xc/panel_api/admin/index.php"
token = "<your-api-key>"

start = 0
all_streams = []
while True:
    r = requests.get(base, params={
        "api_key": token,
        "action":  "get_streams",
        "limit":   100,
        "start":   start,
    }, timeout=30)
    r.raise_for_status()
    body = r.json()
    if body["status"] != "STATUS_SUCCESS":
        raise RuntimeError(f"get_streams failed: {body['data'].get('message')}")
    all_streams.extend(body["data"]["items"])
    start = body["data"]["next_cursor"]
    if start is None:
        break

print(len(all_streams), "streams total")
```

## Errors

| `status` | `error` slug | When it happens | How to fix |
| -------- | ----------- | --------------- | ---------- |
| `STATUS_INVALID_DATA` | `validation_error` | `action` parameter is missing. | Include `action=get_streams`. |
| `STATUS_NO_PERMISSIONS` | `insufficient_scope` | The key does not carry `streams:read`. | Issue a new key with the scope. |
| `STATUS_FAILURE` | `invalid_key` | Key not sent or not recognized. | Check the key value. |
| `STATUS_FAILURE` | `rate_limited` | Per-minute request budget exceeded. | Slow down. See [Rate limits & Idempotency](/docs/?page=panel-api-rate-limits-idempotency). |

## See also

- [action=get_stream](/docs/?page=xc-ref-streams-get). Detail for a single stream by id.
- [action=get_movies](/docs/?page=xc-ref-movies-list). Companion listing for VOD entries.
- [Panel API Catalog](/docs/?page=xai-ref-overview#streams). Native v1 endpoint with richer filters and cursor pagination.
