Create a line with an explicit expiry, POST /ext/line/create-advanced

If you are starting a new integration instead of migrating, prefer the native v1 API with the official SDKs. The OneStream dialect exists to let existing OneStream code point at Xtream AI with only a base URL change.

Create a line and pick its expiry explicitly. This is the same endpoint as POST /ext/line/create with two extra fields: expire_at and can_watch_adult. Everything else (billing, ownership, bouquets, rid idempotency, autogenerated username or password) behaves identically.

Use this variant when your billing system needs to override the package's default duration. Typical cases: extending a promo, granting a prorated month, or seeding a trial that should end on a specific calendar date.

Endpoint

POST https://<your-panel-domain>/panel-api/onestream/ext/line/create-advanced

Authentication

Send the token as X-Api-Key. The legacy header X-Auth-User is accepted as an alias. Authorization: Bearer <token> is also accepted. See Authentication.

Required scope

lines:write.

Idempotency

Optional but strongly recommended. Pass a unique rid in the JSON body. Same-body retries return the original response. Different-body retries return 409 Transaction already processed.

Request body

All the fields from POST /ext/line/create, plus:

Field Type Required Default Description
expire_at string or int no derived from package Absolute expiry. ISO 8601 with offset (2026-12-31T00:00:00+00:00) is the canonical form. A unix integer (as string or number) is accepted for legacy clients. Malformed values return 422 validation_error rather than silently falling back to the package duration.
can_watch_adult bool no false Adult-content flag on the resulting line.

0 is not the way to say "no expiration". The engine treats exp_date = 0 as expired. To create a line with no expiration use POST /ext/line/{uuid}/update-advanced with expire_at: null after creation.

{
  "package": 42,
  "member_id": 100,
  "username": "reseller1_vip_20260808",
  "password": "s3cret-pw",
  "expire_at": "2026-12-31T00:00:00+00:00",
  "can_watch_adult": true,
  "rid": "invoice-INV-2026-00815"
}

Response

200 OK with the same compact shape as the plain create. expire_at reflects the value you passed, not the package default.

{
  "line_id": "5f14e08b-a731-4c31-8bcd-0001000000f2",
  "expire_at": "2026-12-31T00:00:00+00:00",
  "transaction_amount": 0,
  "rid": "invoice-INV-2026-00815"
}

Examples

cURL

curl -X POST https://<your-panel-domain>/panel-api/onestream/ext/line/create-advanced \
  -H "X-Api-Key: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "package": 42,
    "member_id": 100,
    "username": "reseller1_vip_20260808",
    "password": "s3cret-pw",
    "expire_at": "2026-12-31T00:00:00+00:00",
    "can_watch_adult": true,
    "rid": "invoice-INV-2026-00815"
  }'

PHP raw

$ch = curl_init('https://<your-panel-domain>/panel-api/onestream/ext/line/create-advanced');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'X-Api-Key: <your-api-key>',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode([
        'package'         => 42,
        'member_id'       => 100,
        'username'        => 'reseller1_vip_20260808',
        'password'        => 's3cret-pw',
        'expire_at'       => '2026-12-31T00:00:00+00:00',
        'can_watch_adult' => true,
        'rid'             => 'invoice-INV-2026-00815',
    ]),
]);
$body   = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);

Python raw

import requests

r = requests.post(
    "https://<your-panel-domain>/panel-api/onestream/ext/line/create-advanced",
    headers={"X-Api-Key": "<your-api-key>"},
    json={
        "package": 42,
        "member_id": 100,
        "username": "reseller1_vip_20260808",
        "password": "s3cret-pw",
        "expire_at": "2026-12-31T00:00:00+00:00",
        "can_watch_adult": True,
        "rid": "invoice-INV-2026-00815",
    },
    timeout=30,
)
r.raise_for_status()
print(r.json())

Errors

HTTP Error slug (or message) When it happens How to fix
401 Invalid API key Token is unknown, expired, disabled, or deleted. Check the token or issue a new one.
402 Insufficient credits balance Reseller in credits mode lacks credits for the package. Top up credits, or pick a cheaper package.
403 admin_only_field Reseller key sent member_id. Remove member_id.
403 insufficient_scope Token lacks lines:write. Issue a key with the scope.
403 package_not_accessible Reseller cannot sell from that package. Use a package inside the reseller's member group.
409 Transaction already processed Same rid was reused with a different body. Use a fresh rid, or replay with the exact original body.
422 Invalid expire_at format: expected ISO 8601 or unix timestamp expire_at is neither a parseable ISO 8601 timestamp nor a positive unix integer. Send 2026-12-31T00:00:00+00:00 or a positive integer.
422 validation_error Missing package, admin key without member_id, unknown package, unknown member_id, username collision, bouquet not accessible. See details.field in the response.
429 Rate limit exceeded The key hit its per-minute cap. Back off and retry after Retry-After seconds.
501 not_implemented A GET request was sent to this URL. Only POST is accepted.

See also