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 package default On reseller keys every id must be visible to the reseller's group.
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.

{
  "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.0.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