action=packages
Lists every package visible to the calling key. Admin keys see every package on the panel. Reseller keys see only packages whose groups list contains their member_group_id, meaning packages the admin has authorized the reseller's group to sell. The response is unpaginated; a healthy panel has fewer than a few hundred packages and the full list ships in one call.
The action name get_packages is accepted as an alias and returns the same shape. Some classic XC clients used one, some used the other. Both work.
Endpoint
GET https://<your-panel-domain>/panel-api/xc/{accesscode}/admin/index.php?action=packages
Also accepted:
action=get_packages(alias, identical response).- POST with
action=packagesoraction=get_packagesin the body. /reseller/index.phpsub-path (identical behavior).
Authentication
Send the API key as ?api_key=<token>, as an api_key=<token> POST field, or as Authorization: Bearer <token>.
Required scope
packages:read. A key without the scope gets STATUS_NO_PERMISSIONS with error: "insufficient_scope".
Query parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
action |
string | Yes | packages or get_packages. |
|
api_key |
string | Yes (if not using Bearer) | The API key. |
There are no pagination parameters. The full package list is returned in a single response.
Response
data.items is the list of Package objects. There is no next_cursor field.
{
"status": "STATUS_SUCCESS",
"data": {
"items": [
{
"id": 76,
"package_name": "Package 42",
"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 43",
"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": ""
}
]
}
}
Field-by-field.
| Field | Type | Description |
|---|---|---|
id |
int | Package id, used as package in create_line and extend_line. |
package_name |
string | Human-readable name shown in the panel UI. |
is_trial |
bool | true for trial packages. Trial packages cannot be used in extend_line. |
is_official |
bool | Whether the panel admin flagged the package as an "official" catalog entry. |
official_credits |
int | Credits charged when the package is used to create a non-trial line (credits billing mode). |
official_duration |
int | Numeric part of the package validity. Combined with official_duration_in. |
official_duration_in |
string | Time unit for official_duration. Typical values: months, days, hours. |
trial_credits |
int | Credits charged for the trial variant of the package. 0 when the package has no trial variant. |
trial_duration |
int | Numeric part of the trial validity. |
trial_duration_in |
string | Time unit for trial_duration. |
max_connections |
int | Concurrent connection cap that will be stamped on new lines created from this package. |
is_restreamer |
bool | Whether lines from this package are flagged as restreamers (allowed to be used from a datacenter). |
forced_country |
string | ISO country code that forces lines created from this package to a specific region, or empty string. |
Examples
cURL
curl "https://<your-panel-domain>/panel-api/xc/panel_api/admin/index.php?api_key=<your-api-key>&action=packages"
# Alias
curl "https://<your-panel-domain>/panel-api/xc/panel_api/admin/index.php?api_key=<your-api-key>&action=get_packages"
PHP raw
$url = 'https://<your-panel-domain>/panel-api/xc/panel_api/admin/index.php'
. '?' . http_build_query([
'api_key' => '<your-api-key>',
'action' => 'packages',
]);
$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('packages failed: ' . ($body['data']['message'] ?? 'unknown'));
}
foreach ($body['data']['items'] as $p) {
printf("%d\t%s\t%d credits\t%s\n",
$p['id'], $p['package_name'], $p['official_credits'],
$p['is_trial'] ? 'TRIAL' : ''
);
}
Python raw
import requests
r = requests.get(
"https://<your-panel-domain>/panel-api/xc/panel_api/admin/index.php",
params={"api_key": "<your-api-key>", "action": "packages"},
timeout=30,
)
r.raise_for_status()
body = r.json()
if body["status"] != "STATUS_SUCCESS":
raise RuntimeError(f"packages failed: {body['data'].get('message')}")
for p in body["data"]["items"]:
print(p["id"], p["package_name"], p["official_credits"], "credits",
"TRIAL" if p["is_trial"] else "")
Errors
status |
error slug |
When it happens | How to fix |
|---|---|---|---|
STATUS_INVALID_DATA |
validation_error |
action parameter is missing. |
Include action=packages. |
STATUS_NO_PERMISSIONS |
insufficient_scope |
The key does not carry packages:read. |
Issue a new key with the scope, or add it in the panel. |
STATUS_FAILURE |
invalid_key |
Key not sent or not recognized. | Check the key value and that it was not rotated. |
STATUS_FAILURE |
rate_limited |
Per-minute request budget exceeded. | Slow down. See Rate limits & Idempotency. |
STATUS_FAILURE |
api_disabled |
Panel operator flipped the API kill switch. | Wait for the API to be re-enabled. |
Difference between the XC list and the native list
The XC dialect returns the full package list in a single response with no pagination. The native GET /panel-api/v1/packages endpoint returns the same shape. The compat layer is passing the response through unchanged inside the XC envelope. If you need any of the richer fields (bouquets attached to the package, groups authorized, quotas), migrate the call to the native endpoint (see Panel API Catalog).
See also
- XC and XUI API Reference. Envelope, authentication, action list.
- action=get_bouquets. Companion listing of the bouquets a package can assign.
- Panel API Catalog. Native v1 read-only endpoints with richer detail.