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/linesreturns a plain JSON array of line objects. Use it when your integration expects the classic OneStream shape.GET /ext/lines/indexwraps the same array in an envelope with pagination metadata. Use it when your integration readshas_moreand walks pages until the flag comes backfalse.
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:
- Call
/ext/lines/indexand honor thehas_morefield in the pagination envelope. Iterate until it isfalse. - Move the listing flow to the native
GET /panel-api/v1/lines?cursor=<value>endpoint, which returns anext_cursoryou 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
- Find a line by username
- OneStream overview
- Native lines listing for exact cursor pagination.