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. |