GET /ext/lines and /ext/lines/index

Two endpoints, one dataset, two response shapes. Both list subscriber lines the calling key can see and both return each line as an object with the OneStream line_id UUID contract, ISO 8601 timestamps, and real JSON booleans.

  • GET /ext/lines returns a plain JSON array of line objects. Use it when your integration expects the classic OneStream shape.
  • GET /ext/lines/index wraps the same array in an envelope with pagination metadata. Use it when your integration reads has_more and walks pages until the flag comes back false.

The list is auto-scoped by caller 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, and a cursor value that points at another tenant's line simply returns an empty page.

Endpoints

GET https://<your-panel-domain>/panel-api/onestream/ext/lines
GET https://<your-panel-domain>/panel-api/onestream/ext/lines/index

Authentication

Send the API key in X-Api-Key, X-Auth-User, or Authorization: Bearer. See the OneStream overview.

Required scope

lines:read.

Query parameters

Name Type Required Default Description
username string no (none) Exact-match filter on username. Case-sensitive.
per_page int no 50 Page size. Clamped internally to [1, 100].
limit int no 50 Alias of per_page. If both are supplied, limit wins.
page int no 1 Page number for callers that walk pages by index. Translated internally as offset (see the pagination note below).

Pagination

The OneStream dialect exposes page + per_page. Under the hood, the panel uses cursor-based pagination on line id. The dialect translates by computing an offset ((page - 1) * per_page) and passing it as cursor to the underlying v1 endpoint.

This translation is exact when line ids are dense (a freshly seeded panel) and approximate when line ids are sparse (a mature panel where rows have been deleted over time). On sparse panels, walking page=1, page=2, page=3 can return overlapping items, because a cursor value equal to (page - 1) * per_page does not necessarily land on the boundary between two consecutive pages.

Two safe patterns:

  1. Call /ext/lines/index and honor the has_more field in the pagination envelope. Iterate until it is false.
  2. Move the listing flow to the native GET /panel-api/v1/lines?cursor=<value> endpoint, which returns a next_cursor you pass into the next request. It is exact under any distribution of ids.

Response shapes

/ext/lines

A plain JSON array. Each element is a line object with the fields below.

[
  {
    "line_id": "550e8400-e29b-41d4-a716-446655440000",
    "username": "customer_001",
    "password": "d7dca035",
    "expire_at": "2027-06-20T00:00:00+00:00",
    "is_enabled": true,
    "is_restreamer": false,
    "is_trial": false,
    "package_id": null,
    "bouquets": [2, 4, 14, 15, 82, 107, 108, 110, 112, 114, 115, 118, 120, 134],
    "max_connections": 4,
    "reseller_notes": "",
    "mac_addr": null,
    "owner": "billing",
    "type": "regular"
  },
  {
    "line_id": "6ba7b810-9dad-41d1-80b4-00c04fd430c8",
    "username": "customer_002",
    "password": "a3b91c74",
    "expire_at": "2026-10-03T00:00:00+00:00",
    "is_enabled": true,
    "is_restreamer": false,
    "is_trial": false,
    "package_id": null,
    "bouquets": [2, 4, 15, 82, 107, 108, 110, 112, 114, 115, 118, 119, 120, 134],
    "max_connections": 3,
    "reseller_notes": "",
    "mac_addr": null,
    "owner": "billing",
    "type": "regular"
  }
]

/ext/lines/index

An object with a status string and a data object containing pagination metadata and the items.

{
  "status": "success",
  "data": {
    "pagination": {
      "current_page": 1,
      "per_page": 2,
      "total": 2,
      "has_more": true
    },
    "items": [
      {
        "line_id": "550e8400-e29b-41d4-a716-446655440000",
        "username": "customer_001",
        "password": "d7dca035",
        "expire_at": "2027-06-20T00:00:00+00:00",
        "is_enabled": true,
        "is_restreamer": false,
        "is_trial": false,
        "package_id": null,
        "bouquets": [2, 4, 14, 15, 82, 107, 108, 110, 112, 114, 115, 118, 120, 134],
        "max_connections": 4,
        "reseller_notes": "",
        "mac_addr": null,
        "owner": "billing",
        "type": "regular"
      }
    ]
  }
}

pagination.total is set to the count of items on the current page, not the total row count of the tenant. The underlying cursor pagination does not compute a global total, and computing one on demand would be expensive on large panels. Trust has_more as the signal for "keep walking".

Line object fields

Every line, whether returned bare in /ext/lines or inside data.items of /ext/lines/index, has this shape.

Field Type Description
line_id string (UUID) Opaque, license-scoped identifier. See Line IDs are opaque.
username string Line username.
password string Line password.
expire_at string or null ISO 8601 timestamp with offset, or null if the line has no expiration. null here is distinct from 0, which the engine would treat as an expired line.
is_enabled bool Reseller-visible toggle. false blocks logins without deleting the line.
is_restreamer bool true when the line has restreamer permissions.
is_trial bool true when the line was created off a trial variant of a package.
package_id int or null Currently always null. The underlying v1 line object does not carry the source package; if you need it, store it locally at creation time.
bouquets int[] Array of bouquet ids attached to the line.
max_connections int Maximum concurrent devices allowed to be logged in.
reseller_notes string Currently always empty (""). The v1 line formatter does not project reseller notes into this field.
mac_addr null Always null. This product does not provision physical MAG or Enigma devices.
owner string Fixed value "billing".
type string Fixed value "regular".

Examples

cURL

# Plain array
curl -H "X-Api-Key: <your-api-key>" \
     "https://<your-panel-domain>/panel-api/onestream/ext/lines?per_page=50"

# Paginated envelope
curl -H "X-Api-Key: <your-api-key>" \
     "https://<your-panel-domain>/panel-api/onestream/ext/lines/index?per_page=50&page=1"

PHP raw

$url = 'https://<your-panel-domain>/panel-api/onestream/ext/lines/index?per_page=50&page=1';
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-Api-Key: <your-api-key>']);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);

foreach ($body['data']['items'] as $line) {
    echo $line['line_id'] . ' ' . $line['username'] . "\n";
}
$hasMore = $body['data']['pagination']['has_more'] ?? false;

Python raw

import requests

r = requests.get(
    "https://<your-panel-domain>/panel-api/onestream/ext/lines/index",
    headers={"X-Api-Key": "<your-api-key>"},
    params={"per_page": 50, "page": 1},
    timeout=30,
)
r.raise_for_status()
body = r.json()

for line in body["data"]["items"]:
    print(line["line_id"], line["username"])

has_more = body["data"]["pagination"]["has_more"]

Errors

HTTP Error slug When it happens How to fix
401 invalid_key Header is missing, the token is unknown, the key was disabled, expired, deleted, or the caller IP is not on the key's IP allow-list. Check the header. If the key was rotated, mint a new one from the panel.
403 insufficient_scope The key does not carry lines:read. Regenerate the key with lines:read, or use a key that has it.
429 rate_limited The per-key or per-IP rate limit was hit. Response carries Retry-After and X-RateLimit-* headers. Back off for the number of seconds in Retry-After.
403 api_disabled An admin has turned the Panel API off for this panel. Ask the admin to re-enable it.

See also