---
title: "List lines, action=get_lines"
description: "Xtream Codes compatibility listing of subscriber lines. Reseller keys auto-scoped, admin keys see the whole panel. Best-effort offset pagination with exact-match filters."
---

# List lines

`action=get_lines` is the compatibility-layer equivalent of the native [`GET /panel-api/v1/lines`](/docs/?page=xai-ref-lines-list). It returns a page of subscriber lines wrapped in the classic `{"status": "STATUS_SUCCESS", "data": {...}}` envelope, in ascending numeric id order. Under the hood the request is translated to the native list endpoint and the response is rewrapped, so scoping, ordering and filters are identical.

The list is auto-scoped by key type. An **admin key** sees every line on the panel. A **reseller key** sees only lines whose `member_id` matches the reseller's own `reg_user_id`. There is no way to widen or narrow the scope from the query.

## Endpoint

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

Both `/admin/index.php` and `/reseller/index.php` are accepted for the same action. The admin-versus-reseller decision is derived from the key itself, not from the path. `{accesscode}` is decorative (any non-empty value works, see [Xtream Codes / XUI.one / OTT Panel compatibility](/docs/?page=panel-api-xtream-codes-compatibility)).

## Authentication

Send the token in one of three ways. The three forms are exclusive alternatives, not additive.

- `?api_key=<your-api-key>` in the query string (the classic XC form).
- `api_key=<your-api-key>` in the POST body (form field), if you want to keep the key out of URLs and logs.
- `Authorization: Bearer <your-api-key>` HTTP header, for integrations that already speak Bearer.

See [Authentication](/docs/?page=panel-api-authentication) for how to issue a key and pick its scopes.

## Required scope

`lines:read`.

## Query parameters

| Name | Type | Required | Default | Description |
| ---- | ---- | -------- | ------- | ----------- |
| `action` | string | yes | | Must be `get_lines`. |
| `api_key` | string | yes (unless sent via Bearer header or body) | | Your Panel API token. |
| `limit` | int | no | 50 | Page size. Clamped to the range `[1, 100]` by the native handler. |
| `start` | int | no | 0 | Offset-style pagination. Best-effort mapped to the native cursor as `cursor = start`. See the pagination note below. |
| `username` | string | no | | Exact-match filter on `username` (case-sensitive). |
| `search[value]` | string | no | | DataTables-style search value. Mapped to `username=<term>` as a best-effort exact match. Other DataTables keys (`search[regex]`, `columns[...]`) are ignored. |

`limit` and `start` are read from the query string on GET, or from the POST body on POST. Send whichever fits your existing integration.

### Pagination note

The XC dialect exposes `start` (offset) plus `limit`, while the underlying native handler uses `cursor` (keyset on the last returned id). The compat layer maps `start > 0` to `cursor = start` as a bridge. If your ids are dense and monotonic (typical for a healthy panel), pagination works as expected. If ids are sparse, or if you paginate through the whole list by walking `start = 0, limit, 2*limit, ...`, you may miss or repeat rows. For reliable pagination over a large resource, migrate this call to the native [`GET /panel-api/v1/lines`](/docs/?page=xai-ref-lines-list) with `next_cursor`.

### Search note

`search[value]` maps to an exact `username=<term>` filter. Classic XC panels searched across several columns; the compat layer only wires up the `username` filter. If your integration currently searches by `email`, `package_name`, or connection state, migrate to the native list endpoint, which exposes more filters.

## Response

`data` is an object with two fields.

| Field | Type | Description |
| ----- | ---- | ----------- |
| `items` | Line[] | Page of line objects in ascending `id` order. Same shape as the object returned by [`action=get_line`](/docs/?page=xc-ref-lines-get). |
| `next_cursor` | int or null | Cursor to pass on the next call. `null` when this was the last page. Pass it as `start=<value>` on the next request. |

```json
{
  "status": "STATUS_SUCCESS",
  "data": {
    "items": [
      {
        "id": 1512227,
        "username": "u_ab12cd34",
        "password": "9f3c1a77",
        "member_id": 42,
        "exp_date": 1813449600,
        "max_connections": 4,
        "is_trial": false,
        "is_restreamer": false,
        "enabled": true,
        "admin_enabled": true,
        "bouquets": [2, 4, 14, 15, 82, 107, 108, 110, 112, 114, 115, 118, 120, 134],
        "created_at": 1574874852
      },
      {
        "id": 1527378,
        "username": "u_e4f56789",
        "password": "1a2b3c4d",
        "member_id": 42,
        "exp_date": 1790985600,
        "max_connections": 3,
        "is_trial": false,
        "is_restreamer": false,
        "enabled": true,
        "admin_enabled": true,
        "bouquets": [2, 4, 15, 82, 107, 108, 110, 112, 114, 115, 118, 119, 120, 134],
        "created_at": 1578154802
      }
    ],
    "next_cursor": 1527378
  }
}
```

HTTP status is always `200`, even on failure. Branch on the `status` field in the envelope, not on the HTTP status.

## Examples

### cURL

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

Filter by exact username:

```bash
curl "https://<your-panel-domain>/panel-api/xc/panel_api/admin/index.php?api_key=<your-api-key>&action=get_lines&username=alice"
```

Walk through every page:

```bash
BASE="https://<your-panel-domain>/panel-api/xc/panel_api/admin/index.php"
TOKEN="<your-api-key>"
START=0
while :; do
  BODY=$(curl -s "$BASE?api_key=$TOKEN&action=get_lines&limit=100&start=$START")
  echo "$BODY" | jq '.data.items[]'
  NEXT=$(echo "$BODY" | jq -r '.data.next_cursor // empty')
  [ -z "$NEXT" ] && break
  START=$NEXT
done
```

### PHP (raw HTTP)

```php
$url = 'https://<your-panel-domain>/panel-api/xc/panel_api/admin/index.php'
     . '?' . http_build_query([
         'api_key'  => '<your-api-key>',
         'action'   => 'get_lines',
         'limit'    => 50,
         'username' => 'alice',
       ]);
$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($body['data']['message'] ?? 'get_lines failed');
}
foreach ($body['data']['items'] as $line) {
    printf("%d %s exp=%d\n", $line['id'], $line['username'], $line['exp_date']);
}
$next = $body['data']['next_cursor'] ?? null;
```

### Python (raw HTTP)

```python
import requests

r = requests.get(
    "https://<your-panel-domain>/panel-api/xc/panel_api/admin/index.php",
    params={
        "api_key":  "<your-api-key>",
        "action":   "get_lines",
        "limit":    50,
        "username": "alice",
    },
    timeout=30,
)
r.raise_for_status()  # only fires on real HTTP errors, not STATUS_FAILURE
body = r.json()
if body.get("status") != "STATUS_SUCCESS":
    raise RuntimeError(body.get("data", {}).get("message", "get_lines failed"))
for line in body["data"]["items"]:
    print(line["id"], line["username"], line["exp_date"])
next_cursor = body["data"]["next_cursor"]
```

## Errors

Response is always HTTP 200. Branch on `status` and, for failures, read `data.error` for the slug and `data.message` for a human-readable string.

| status | Error slug | When it happens | How to fix |
| ------ | ---------- | --------------- | ---------- |
| `STATUS_FAILURE` | `invalid_key` | The token is missing, unknown, disabled, expired, or the source IP is not in the key's allow-list. | Verify the token. Reissue if rotated. Check the IP allow-list on the key. |
| `STATUS_NO_PERMISSIONS` | `insufficient_scope` | The key does not have `lines:read`. | Grant `lines:read`, or issue a new key with that scope. |
| `STATUS_FAILURE` | `rate_limited` | The key hit its per-minute rate limit or the panel-wide per-IP cap. | Back off. Look at `X-RateLimit-Remaining` on successful responses to know how close you are. |
| `STATUS_FAILURE` | `api_disabled` | An admin has switched the Panel API off for this panel. | Contact the panel admin. |

## See also

- [Get a line, action=get_line](/docs/?page=xc-ref-lines-get)
- [Create a line, action=create_line](/docs/?page=xc-ref-lines-create)
- [Xtream Codes / XUI.one / OTT Panel compatibility](/docs/?page=panel-api-xtream-codes-compatibility)
- [Native list endpoint, GET /lines](/docs/?page=xai-ref-lines-list)
