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 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.
{
"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 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
startomitted or0, you get the first page starting from the lowest id. - With
start = N > 0, the compat layer sendscursor = Nto the native handler, which returns items withid > N. - The response's
next_cursoris the id of the last item on the current page. Pass it asstart=<next_cursor>on the next request. Whennext_cursorisnull, 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).
Examples
cURL
# 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
$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
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. |
See also
- action=get_stream. Detail for a single stream by id.
- action=get_movies. Companion listing for VOD entries.
- Panel API Catalog. Native v1 endpoint with richer filters and cursor pagination.