Create a customer line

Provision a new subscriber account on the panel. A line is a username plus password pair that end customers plug into an IPTV app. Every line belongs to an owner (an admin or a reseller) and inherits its duration, connection cap, default bouquets, and trial flag from the package_id you pick.

On a reseller key, credits are deducted atomically before the line row is written. If the row insert fails (for example, a username collision), the credits are refunded automatically. Admin keys skip the billing checks entirely.

Endpoint

POST https://<your-panel-domain>/panel-api/v1/lines

Authentication

Send your API key as a Bearer token in the Authorization header. See Panel API authentication for how to issue one.

Required scope

lines:write.

Idempotency

Required. Every write must send an Idempotency-Key header (any string up to 128 bytes, one per business intent). Retrying the same key with the same body returns the original response. Retrying with a different body returns 409 idempotency_conflict. Requests without the header are rejected with 400 missing_idempotency_key.

Request body

Field Type Required Default Description
package_id int yes Package the line inherits from. Must exist.
member_id int admin only, required Owner reseller id. Reseller keys MUST NOT send this; the owner is forced to the key's own reg user id, and passing the field returns 403 admin_only_field.
username string no autogenerated u_<8hex> (or trial_<8hex> for trials) Must be unique panel-wide.
password string no autogenerated (8 hex chars) Reseller keys whose member group has allow_change_pass=0 cannot set this and receive 403 password_change_not_allowed.
bouquets int[] no the package's bouquets When bouquets is omitted or empty, the line inherits the package's bouquets. On reseller keys every id must be visible to the reseller's group, inherited ones included.
is_trial bool no package default Only valid on a trial package; sending true on a non-trial package returns 422 trial_flag_requires_trial_package.
email string no Stored on the contact-info row (reseller_user_contact_info for resellers, admin_user_contact_info for admins).
notes string no Same row as email.
exp_date int (UTC epoch) admin only package duration Must be in the future and within 5 years from now.
max_connections int admin only package default Clamped to [1, 100].
is_restreamer bool admin only package default
allowed_ips string[] admin only [] IPv4 allow-list, up to 50 entries. Invalid entries drop silently.
allowed_ua string[] admin only [] User-Agent allow-list, up to 50 entries capped at 500 chars each.
is_isplock bool admin only false

Admin-only fields sent by a reseller key trigger 403 admin_only_field with the offending names in details.fields.

When bouquets is omitted or empty, the line inherits the package's bouquets. Send the field only when the customer is buying a narrower set than the package offers. To change the assignment later, use update to remove bouquets, or renew with a new package_id to add them.

{
  "package_id": 42,
  "member_id": 100,
  "username": "johndoe",
  "password": "s3cret!",
  "bouquets": [1, 4, 9],
  "email": "johndoe@example.com",
  "notes": "created from invoice INV-42"
}

Response

201 Created with the full line object. Password is returned in clear because the panel stores it as plaintext by design (users need it for login).

{
  "id": 172504291,
  "username": "u_a1b2c3d4",
  "password": "4102fec6",
  "member_id": 100,
  "exp_date": 1788885051,
  "max_connections": 1,
  "is_trial": false,
  "is_restreamer": false,
  "enabled": true,
  "admin_enabled": true,
  "bouquets": [],
  "created_at": 1786206651
}

Examples

cURL

curl -X POST https://<your-panel-domain>/panel-api/v1/lines \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: invoice-INV-2026-00814" \
  -d '{
    "package_id": 42,
    "member_id": 100,
    "email": "johndoe@example.com",
    "notes": "created from invoice INV-42"
  }'

PHP SDK

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

$client = new PanelApiClient(baseUrl: 'https://<your-panel-domain>', token: '<your-api-key>');
$line = $client->lines->create(
    packageId:      42,
    memberId:       100,
    email:          'johndoe@example.com',
    notes:          'created from invoice INV-42',
    idempotencyKey: 'invoice-INV-2026-00814',
);
echo $line->id, ' ', $line->username, PHP_EOL;

Python SDK

from xtream_ai_panel_api import PanelApiClient

client = PanelApiClient(base_url="https://<your-panel-domain>", token="<your-api-key>")
line = client.lines.create(
    package_id=42,
    member_id=100,
    email="johndoe@example.com",
    notes="created from invoice INV-42",
    idempotency_key="invoice-INV-2026-00814",
)
print(line.id, line.username)

Errors

HTTP Error slug When it happens How to fix
400 missing_idempotency_key The Idempotency-Key header was not sent. Send a per-intent key on every write.
401 invalid_key Token is unknown, expired, disabled, or deleted. Check the token or issue a new one.
402 billing_expired Reseller subscription has expired. Extend the reseller before creating lines.
402 trial_quota_exceeded Reseller hit the trial-creation cap for the current window. Wait for the window to roll, or raise the group cap.
402 slot_limit_exceeded Reseller (users billing mode) would exceed its own user cap. Delete unused lines or raise the cap.
402 ancestor_cap_reached A parent reseller's cap would be exceeded. Raise the parent cap.
402 insufficient_credits Reseller (credits mode) lacks credits for the package. Top up credits, or pick a cheaper package.
403 admin_only_field Reseller key sent one of member_id, exp_date, max_connections, is_restreamer, allowed_ips, allowed_ua, is_isplock. Remove the field(s). details.fields lists them.
403 package_not_accessible Reseller cannot sell from that package. Use a package inside the reseller's member group.
403 password_change_not_allowed Reseller group has allow_change_pass=0. Omit password.
403 isplock_not_allowed Reseller group has edit_isplock=0. Omit is_isplock.
403 insufficient_scope Token lacks lines:write. Issue a key with the scope.
409 idempotency_conflict Same key was reused with a different body. Use a fresh key.
409 idempotency_in_flight Same key is still processing on another request. Retry after a moment.
422 validation_error Missing / malformed field, unknown package_id, unknown member_id, username collision, bouquet not accessible, invalid exp_date. See details.field (or details.invalid_ids for bouquets) in the response.
422 trial_flag_requires_trial_package is_trial=true was sent for a non-trial package. Remove is_trial, or pick a trial package.
429 rate_limited The key hit its per-minute cap. Back off and retry after Retry-After seconds.

See also