---
title: "Quickstart. Your first line in five minutes"
description: "Create a real subscriber line from PHP or Python in five minutes. Get an API key, install the official SDK, run two short scripts, and see the line appear in your panel."
---

This page is the fastest path from a fresh install to a working integration. By the end of it you will have created a real test line from code, and you will see that line appear in your panel. There is no long setup. There is no build tool. There is no configuration file. You copy a token, install one small library, and run two scripts of about twenty lines each.

If something is unclear as you go, follow the page top to bottom. Every step includes a short explanation of what the code is doing and why. When you finish, the section "How does it work, briefly" pulls the pieces together so that you understand what the SDK did on your behalf.

This page is written for someone who has not touched the Panel API before. If you have already read the [Panel API overview](/docs/?page=panel-api-overview) and just want the code, feel free to skim the prose and copy the snippets. Both are exact and both are tested against a real panel.

## What you need

You need admin access to your panel. Only an admin can issue an API key from the panel UI. If you are a reseller and your admin has enabled the permission on your member group, you can issue reseller keys instead, but the very first key of a fresh panel is always issued by an admin.

You need about five minutes. Copying the token takes ten seconds. Installing the SDK takes one minute. Running the two example scripts takes another minute. The rest is reading.

You need PHP 8.1 or newer, or Python 3.10 or newer. Any recent version of either language will do. The PHP SDK needs the `curl` and `json` extensions, both of which ship enabled by default in every Linux distribution and in the official PHP builds for Windows and macOS. The Python SDK depends on the `requests` package and nothing else.

## Step 1. Get your API key

The API key is how your code proves that it is you. Every request the SDK sends carries this key in a header called `Authorization`. The panel checks the key, decides what your code is allowed to do, and answers.

Open your panel and go to **Settings**, then **Panel API Keys**. Click **Create key**. Fill in a **label** so that you can recognize this key later in the audit log. For this page, `quickstart` is a fine label. Pick the **scopes** that the key is allowed to use. For the two example scripts in this page you need four scopes.

- `lines:read` to list lines.
- `lines:write` to create and delete lines.
- `packages:read` to look up the packages available on your panel.
- `resellers:read` to look up the reseller who will own the new line.

Leave the optional fields empty. The IP allow-list, the per-minute rate limit and the expiration date are all useful in production, but they get in the way while you learn the API. You can add them later without regenerating the key.

Click **Save**. The panel shows the token on screen. Copy it now.

> [!WARNING]
> The secret half of the token is shown once. The panel does not store it in a form that would let you retrieve it later. If you close the dialog without copying, you have to rotate the key to get a new secret.

A real token looks like this.

```
pk_live_abc7fake123.EXAMPLE_secret_forty_three_chars_long
```

The `pk_live_` part is a fixed prefix that says "this is a production key for the Panel API". The twelve characters that follow are the public identifier of the key. They are safe to log and safe to include in bug reports. The long string after the dot is the actual secret. Treat that half like a password.

If you ever need to identify a specific token in an audit log or a support ticket, share only the prefix. Support engineers can look up any key from its prefix without ever seeing the secret, and you never expose usable credentials in the process.

From now on this page calls your token `<your-api-key>`. Wherever you see that placeholder in a code sample, paste your own token in its place. The angle brackets go too.

## Step 2. Install the SDK

An SDK is a small library that turns HTTP calls into method calls in your language. Instead of building a URL, adding a header, sending a POST, parsing the JSON and mapping error responses onto exceptions, you write `$client->lines->create(...)` and the SDK does the rest. We publish one SDK for PHP and one for Python. They cover the same endpoints and behave the same way, so pick whichever language you already use.

### PHP

You have two ways to install the PHP SDK. If you have never used Composer, pick Option A. It is nothing more than a folder next to your script. If Composer is already part of your workflow, pick Option B. It fits into any project that already has a `vendor/` directory.

#### Option A. Manual install without Composer

Download the release ZIP from GitHub.

[api-panel-php-sdk v1.0.0 (ZIP)](https://github.com/Xtream-AI/api-panel-php-sdk/archive/refs/tags/v1.0.0.zip)

Extract it next to your script. You get a folder named `api-panel-php-sdk-1.0.0`. Load it with one line at the top of your script.

```php
require __DIR__ . '/api-panel-php-sdk-1.0.0/autoload.php';
```

That is the whole install. There is nothing to run. The `autoload.php` file registers a small function with PHP that looks up SDK classes on disk the first time your code mentions them. If you rename or move the folder, update the path in the `require` line accordingly.

#### Option B. Composer

Two commands.

```bash
composer config repositories.xtream-ai vcs https://github.com/Xtream-AI/api-panel-php-sdk
composer require xtream-ai/api-panel-php-sdk
```

With Composer, the first line of your script becomes `require 'vendor/autoload.php';` instead of the manual path. Composer takes care of updating the SDK when you run `composer update` and it plays nicely with any other dependency you already have in that project.

#### Difference between manual and Composer

The two options end up in the same place: your script can create a `PanelApiClient`. The manual install is a folder that you drop next to your code. The Composer install is a dependency line in your `composer.json`. Composer is the standard tool for managing PHP libraries and it is what you would use in any medium sized project. The manual install exists for the case where you want to run a small script on a server that does not have Composer, or where you are learning the API and do not want to install anything global.

The SDK needs PHP 8.1 or newer with the `curl` and `json` extensions. Both extensions are enabled by default in every reasonable PHP build.

### Python

One command with `pip`.

```bash
pip install "git+https://github.com/Xtream-AI/api-panel-python-sdk.git@v1.0.0"
```

We recommend installing into a virtual environment so that the SDK does not mix with the global Python of your operating system. On a fresh machine you can create one in three lines.

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install "git+https://github.com/Xtream-AI/api-panel-python-sdk.git@v1.0.0"
```

The SDK needs Python 3.10 or newer. Its only external dependency is the well known `requests` package. Pip pulls that in for you.

## Step 3. Say hello to your panel

Before creating anything, we ask the panel two questions. Is the API alive? Who am I? The first call proves that your network can reach the panel and that the endpoint answers. The second call proves that the panel accepted your token and tells you which scopes the token carries. If both answers arrive, everything from Step 4 onwards is guaranteed to work.

You only change these two things in the script.

1. `<your-panel-domain>` is the domain where your panel lives, for example `panel.example.com`.
2. `<your-api-key>` is the token you copied in Step 1.

Save the file as `hello.php` or `hello.py`, then run it with `php hello.php` or `python hello.py`.

```php
<?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>',
);

$health = $client->health();
echo "Panel API status: ", $health['status'], "\n";

$me = $client->me->get();
echo "You are: ", $me->type, "\n";
echo "Your key can: ", implode(', ', $me->scopes), "\n";
```

```python
from xtream_ai_panel_api import PanelApiClient

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

health = client.health()
print("Panel API status:", health["status"])

me = client.me.get()
print("You are:", me.type)
print("Your key can:", ", ".join(me.scopes))
```

You should see this.

```
Panel API status: ok
You are: admin
Your key can: lines:read, lines:write, packages:read, resellers:read
```

Three things are confirmed by that output. The `status: ok` line means the panel answered the health endpoint, so network and TLS are fine. The `You are: admin` line means the panel accepted your token and identified the caller as an admin. The list of scopes matches what you ticked when you created the key in Step 1, so authorization is set up correctly. If any of the three is missing or different, jump to "If something goes wrong" at the bottom of this page.

What just happened, in one sentence. Your code opened an HTTPS connection to your panel, sent the token in the `Authorization` header, and received two JSON responses that the SDK unpacked into a small array and a typed object. You did not write any HTTP code, and you did not parse any JSON. That is the point of the SDK.

If the script printed less than three lines, or if the scopes look different, either the token is wrong or your key was created with different scopes. Compare the printed scopes to what you ticked in Step 1. If they do not match, the safest fix is to go back and create the key again with the four scopes above. Deleting and reissuing a key is a one minute operation and it costs nothing.

## Step 4. Create your first line

A line is a subscriber account, the pair of credentials that your customer types into an IPTV app. Creating one requires two decisions. Which **package** does the line belong to (this fixes the duration, the number of connections, the allowed bouquets and so on). Which **owner** does the line belong to (an admin key must say which reseller becomes the owner of the new line).

If you have not worked with the Panel API before, the vocabulary above may be new. A package is the plan you sell. Think "1 connection, 12 months, standard bouquet". A bouquet is a curated group of channels. An owner is the panel account (admin or reseller) that the line hangs off of, the account whose credit balance was used to create it and whose lines list it will appear in.

The script below asks your panel for the first package and for the first reseller, then creates a line using those. There is a good reason for reading both values from the API instead of hard coding a number. The IDs on your panel are your IDs, not ours. A package that has ID 3 on our test panel might have ID 17 on yours. Reading the IDs from the API keeps the script correct on any panel and it teaches you how to look them up.

Save the file as `first-line.php` or `first_line.py` and run it the same way you ran the previous one.

```php
<?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>',
);

// A line needs two things: a package and an owner.
// We read the first package and the first reseller from your panel.
$package = $client->catalog->packages()[0];
$owner   = $client->resellers->list(limit: 1)->items[0];

$line = $client->lines->create(
    packageId: $package->id,
    memberId:  $owner->id,
);

echo "Line created\n";
echo "ID:       ", $line->id, "\n";
echo "Username: ", $line->username, "\n";
echo "Password: ", $line->password, "\n";
echo "Package:  ", $package->packageName, "\n";
echo "Owner:    ", $owner->username, "\n";
```

```python
from xtream_ai_panel_api import PanelApiClient

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

# A line needs two things: a package and an owner.
# We read the first package and the first reseller from your panel.
package = client.catalog.packages()[0]
owner = client.resellers.list(limit=1).items[0]

line = client.lines.create(
    package_id=package.id,
    member_id=owner.id,
)

print("Line created")
print("ID:      ", line.id)
print("Username:", line.username)
print("Password:", line.password)
print("Package: ", package.package_name)
print("Owner:   ", owner.username)
```

You should see this.

```
Line created
ID:       102481337
Username: u_59afbdab
Password: 679c07a0
Package:  Basic | 1 Connection | 12M
Owner:    demo_reseller
```

Now open your panel and go to **Lines**. Your new line is there, with the exact username and password that the script printed. That username and password are what your customer would type into their IPTV app. You just built a working integration.

A few notes are worth reading, because they save you time later.

The script picks the first package and the first reseller it finds, so your panel needs at least one of each. In a real integration you would pass your own package ID and member ID from your billing system instead of grabbing the first thing available. Nothing changes in the rest of the code. Only the two values change.

The admin key requires the `member_id` field on every line, because an admin can create lines under any reseller and the panel needs to know which one. A reseller key can omit `member_id`, because the panel already knows who the caller is.

The line object returned by `create` is a real object of the SDK's `Line` class. Its fields are typed. Your editor autocompletes them. `$line->id`, `$line->username`, `$line->password`, `$line->packageId`, `$line->memberId` and the rest are all there. If you try to read a field that does not exist you get an error at parse time in PHP with strict mode, and immediately at run time in Python.

Storing the returned username and password on your side is important. The panel will never regenerate them for you. If you lose both values you can reset the password with a separate call, but the username stays with the line for the rest of its life.

### Delete the test line

You probably do not want a test line sitting in your panel. Delete it using the ID that Step 4 printed.

```php
<?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>',
);

$deleted = $client->lines->delete(102481337);
echo $deleted ? "Test line deleted\n" : "Nothing was deleted\n";
```

```python
from xtream_ai_panel_api import PanelApiClient

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

deleted = client.lines.delete(102481337)
print("Test line deleted" if deleted else "Nothing was deleted")
```

You should see this.

```
Test line deleted
```

The delete call answers `true` when the line existed and is now gone. If you run it a second time on the same ID it answers `false`, because there is nothing left to delete.

## How does it work, briefly

Under the hood the SDK is doing very small, very ordinary things. It builds an HTTPS request. It sets the `Authorization` header to `Bearer <your-api-key>`. It sends JSON for writes and reads JSON back. If the panel answers with a JSON error, the SDK translates the error into a typed exception in your language, so you can catch it by name and react to it. When the network hiccups on a read, the SDK retries with a short wait between attempts, up to three times, before giving up.

The SDK also adds a receipt number to every write. If your connection drops right after the panel accepted a new line, the SDK retries. The panel sees the same receipt number a second time and answers with the line it already created instead of creating a duplicate. Programmers call this idempotency. You did not need to know that, and you did not have to write a single line of code to enable it.

You did not have to think about token refresh, about content types, about status codes, about JSON parsing, or about the shape of the response. The Line object that Step 4 printed is a real PHP or Python object with typed fields. Your editor can autocomplete them. Your type checker can catch typos before you run the script.

Every write also carries an idempotency key by default. You can pass your own if you want the panel to treat two calls as "the same operation, please only run it once" even across process restarts. That is useful when a background worker retries a failed job hours later and you want to be sure it will not create a second copy of whatever it created the first time.

If you ever want to see exactly what the SDK sent and received, both languages let you pass your own HTTP client into the constructor. In practice you rarely need to. In the beginning it is enough to know that the network side is handled. Two calls to `me()`, one call to `create()`, and the panel now has a real line on file. That is the whole trip.

Everything else you might want to do next is a variation on the same pattern. Renewing a line calls `renew()`. Suspending it calls `disable()`. Resetting a password calls `resetPassword()`. Listing lines for a reseller calls `list()` with a filter. The shape of the call is always the same, and every method returns a typed object or a boolean that says whether it worked.

## If something goes wrong

The SDK raises a typed exception for every kind of failure. Each class has a name that says what happened, so you can catch specific problems with a specific `catch` block. All the exception classes extend a single base called `PanelApiException` in PHP or `PanelApiException` in Python, so a broad safety net around your code stays easy to write.

| What you see | What it usually means | What to do |
|---|---|---|
| `AuthenticationException` or HTTP 401 | The key was mis-copied, or it has been deleted or has expired. | Create a new key using Step 1 and paste the whole token, including the `pk_live_` prefix. |
| `NotFoundException` or HTTP 404 on every call | The base URL is wrong. The domain is not really the panel, or the `https://` is missing. | Verify the panel URL. Opening `https://<your-panel-domain>/panel-api/v1/health` in a browser should answer `{"status":"ok","version":"v1"}`. |
| `ValidationException` mentioning a package | That package ID does not exist on your panel. | List the real packages of your panel with the snippet below and use one of the IDs it prints. |
| A connection error or a timeout waiting for the panel | Your server cannot reach the panel. A firewall is blocking outbound traffic, or the domain is unreachable. | Run `curl https://<your-panel-domain>/panel-api/v1/health` from the same machine that runs your script. If it also fails, the issue is at the network layer. |
| `Class "XtreamAI\PanelApi\PanelApiClient" not found` in PHP, or `Failed opening required ... autoload.php` | PHP cannot find the SDK. The path in your `require` line does not match the folder on disk. | Check that the folder next to your script is really named `api-panel-php-sdk-1.0.0`, and that the `require` line points at its `autoload.php`. |

**List the packages of your panel.** Add these lines to the script from Step 3.

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

```python
for p in client.catalog.packages():
    print(p.id, p.package_name)
```

**Check that your panel answers.**

```bash
curl https://<your-panel-domain>/panel-api/v1/health
```

A healthy panel answers `{"status":"ok","version":"v1"}`. If the answer is anything else, the URL is wrong or the panel is offline.

If the curl call works from your terminal but the script does not, the network between your script and the panel is different from the network between your terminal and the panel. Common causes include a proxy in the environment of the script, a firewall on the machine that runs the script, or a DNS resolver that only resolves your panel from certain networks.

## Next steps

The Quickstart got you to a working call. The rest of the documentation shows you how to build a real integration on top of it.

- [Common Tasks](/docs/?page=panel-api-migration-recipes) gives you ready made recipes for the things a billing system actually does. Renewing a line at the end of a period, suspending a line that failed to pay, running a free trial, adjusting a reseller's credits, and moving customers over from an old panel.
- [SDKs](/docs/?page=panel-api-sdks) walks through every method of the PHP and Python SDKs, with typed exceptions, automatic retries, cursor based pagination, and configuration for timeouts and proxies.
- [Authentication](/docs/?page=panel-api-authentication) covers the token in depth, the scopes, the IP allow-list, per key rate limits, key rotation, and how a reseller can be allowed to issue their own keys.

If none of that is what you need next, close this page and start building. The API is the same shape everywhere. Every write returns the object it created or updated. Every read returns a typed model. Every failure is a typed exception. Once the first line is created, the rest is just more of the same.
