GET /ext/packages
/ext/packages returns every package the calling key can sell. Packages are the price sheet of the panel: each row describes a subscription (credit cost, duration, max connections) and, optionally, its trial variant. Storefronts, dashboards, and any flow that creates a line off a package name reads this list.
The response shape is the OneStream shape: a plain JSON array of package objects. There is no envelope. If your integration handles the native v1 API too, note that this endpoint corresponds to GET /panel-api/v1/packages, but the v1 flavor wraps the same rows in {"items": [...]}. Here you get the rows directly.
Endpoint
GET https://<your-panel-domain>/panel-api/onestream/ext/packages
Authentication
Send the API key in X-Api-Key, X-Auth-User, or Authorization: Bearer. See the OneStream overview.
Required scope
packages:read.
Query parameters
None. The full list of visible packages is returned in one call. There are typically fewer than a few hundred packages per panel, so pagination is not applied. The list is scoped silently by caller type: an admin key sees every package, a reseller key sees only packages whose groups list contains the reseller's member_group_id. No error is raised when the visible set is empty; the response is [].
Response
A plain JSON array. Each element has the following fields.
| Field | Type | Description |
|---|---|---|
id |
int | Numeric package id. Stable across the panel's lifetime. Pass this as package when creating a line. |
package_name |
string | Human-readable name shown in the panel UI. |
is_trial |
bool | true when the package can only be sold as a trial. Paid fields will be zero. |
is_official |
bool | false marks the package as internal or hidden from public storefronts. |
official_credits |
float | Credits charged when the package is sold as a paid subscription. |
official_duration |
int | Length of the paid subscription in official_duration_in units. |
official_duration_in |
string | One of hours, days, weeks, months, years. |
trial_credits |
float | Credits charged when the caller requests a trial. 0 if trials are disabled. |
trial_duration |
int | Length of the trial in trial_duration_in units. 0 if trials are disabled. |
trial_duration_in |
string | Same allowed values as official_duration_in. |
max_connections |
int | Maximum concurrent devices a subscriber on this package can have logged in. |
is_restreamer |
bool | true when the package enables restreamer permissions. |
forced_country |
string | Two-letter ISO country code that forces every line created under this package to that country. Empty string means no restriction. |
Example response:
[
{
"id": 76,
"package_name": "Package 12M 1 Connection",
"is_trial": false,
"is_official": true,
"official_credits": 12,
"official_duration": 12,
"official_duration_in": "months",
"trial_credits": 0,
"trial_duration": 0,
"trial_duration_in": "hours",
"max_connections": 1,
"is_restreamer": false,
"forced_country": ""
},
{
"id": 67,
"package_name": "Package 1M 1 Connection",
"is_trial": false,
"is_official": true,
"official_credits": 1,
"official_duration": 1,
"official_duration_in": "months",
"trial_credits": 0,
"trial_duration": 0,
"trial_duration_in": "hours",
"max_connections": 1,
"is_restreamer": false,
"forced_country": ""
}
]
Examples
cURL
curl -H "X-Api-Key: <your-api-key>" \
https://<your-panel-domain>/panel-api/onestream/ext/packages
PHP raw
$ch = curl_init('https://<your-panel-domain>/panel-api/onestream/ext/packages');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-Api-Key: <your-api-key>']);
$packages = json_decode(curl_exec($ch), true);
curl_close($ch);
foreach ($packages as $p) {
echo $p['id'] . ' ' . $p['package_name'] . ' (' . $p['official_credits'] . " credits)\n";
}
Python raw
import requests
packages = requests.get(
"https://<your-panel-domain>/panel-api/onestream/ext/packages",
headers={"X-Api-Key": "<your-api-key>"},
timeout=30,
).json()
for p in packages:
print(p["id"], p["package_name"], p["official_credits"])
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 packages:read. |
Regenerate the key with packages: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. Cache the response. |
| 403 | api_disabled |
An admin has turned the Panel API off for this panel. | Ask the admin to re-enable it. |