---
title: "List packages"
description: "Returns every package the calling key is authorized to sell. Read-only. The building block for signup, upgrade and renewal flows."
---

# List packages

Returns every package the calling key can use when creating a line. Packages are the price sheet of the panel: each row carries a set of `official_*` fields describing the paid subscription (credit cost, duration, max connections) and an optional set of `trial_*` fields describing the trial variant of the same package.

The endpoint is scoped silently by caller type. An admin key sees every package in the panel. A reseller key sees only the packages whose `groups` list contains the reseller's `member_group_id` (in other words, the packages the admin has authorized their group to sell). No error is raised when the visible set is empty; the response is `{"items": []}`.

## Endpoint

`GET https://<your-panel-domain>/panel-api/v1/packages`

## Authentication

Bearer token in the `Authorization` header. See [Authentication](/docs/?page=panel-api-authentication) for how to mint and rotate keys.

## Required scope

`packages:read`.

## Query parameters

This endpoint takes no query parameters. Packages are ordered alphabetically by `package_name` and the full list is returned in one call. There are typically fewer than a few hundred packages per panel, so pagination is not applied.

## Response

The response wraps the list in an `items` array. Each item has the following fields.

| Field | Type | Description |
| ----- | ---- | ----------- |
| `id` | int | Numeric package id. Stable across the panel's lifetime. |
| `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.

```json
{
  "items": [
    {
      "id": 76,
      "package_name": "Package 12M Adult 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 Adult 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": ""
    },
    {
      "id": 69,
      "package_name": "Package 3M Adult 1 Connection",
      "is_trial": false,
      "is_official": true,
      "official_credits": 3,
      "official_duration": 3,
      "official_duration_in": "months",
      "trial_credits": 0,
      "trial_duration": 0,
      "trial_duration_in": "hours",
      "max_connections": 1,
      "is_restreamer": false,
      "forced_country": ""
    }
  ]
}
```

## Examples

### cURL

```bash
curl -H "Authorization: Bearer <your-api-key>" \
     https://<your-panel-domain>/panel-api/v1/packages
```

### PHP SDK

```php
require __DIR__ . '/api-panel-php-sdk-1.0.0/autoload.php';
use XtreamAI\PanelApi\PanelApiClient;

$client = new PanelApiClient(baseUrl: 'https://<your-panel-domain>', token: '<your-api-key>');

$packages = $client->catalog->packages();
foreach ($packages as $p) {
    echo $p->id, ' ', $p->packageName, ' (', $p->officialCredits, " credits)\n";
}
```

### Python SDK

```python
from xtream_ai_panel_api import PanelApiClient

client = PanelApiClient(base_url="https://<your-panel-domain>", token="<your-api-key>")

packages = client.catalog.packages()
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` | The `Authorization` header is missing, malformed, or names a key that does not exist. | Check the header. See [Authentication](/docs/?page=panel-api-authentication). |
| 403 | `insufficient_scope` | The key does not carry `packages:read`. | Regenerate the key with `packages:read` in its scope list, or use a key that has it. |
| 429 | `rate_limited` | The per key rate limit has been exceeded. Response carries `Retry-After: 60` and `X-RateLimit-*` headers. | Back off for the number of seconds in `Retry-After` and retry. |

## See also

- [List bouquets](/docs/?page=xai-ref-catalog-bouquets)
- [Create a line](/docs/?page=xai-ref-lines-create)
- [Catalog overview](/docs/?page=xai-ref-overview)
