Create a line, POST /ext/line/create

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. New integrations get typed models, automatic retries, and richer error surfaces.

Create a subscriber line under a package. The classic OneStream body shape is honored: package, username, password, bouquets, reseller_notes, max_connections, and rid. If the caller is a reseller key in credits mode the package cost is deducted atomically before the line row is written. Admin keys must pass member_id to say which reseller owns the new line.

The response returns the opaque line UUID that OneStream integrators already expect. Store it next to your own record. See OneStream compatibility for how the UUID is generated and why it is stable across time.

Endpoint

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

Authentication

Send the token as X-Api-Key. The legacy OneStream header X-Auth-User is accepted as an alias. Authorization: Bearer <token> also works for clients that have moved to standard Bearer auth. See Authentication.

Required scope

lines:write.

Idempotency

Optional but strongly recommended. Pass a unique rid (any string up to 255 characters) in the JSON body. Retrying the same rid with the same body returns the original response verbatim. Retrying the same rid with a different body returns 409 Transaction already processed. If rid is omitted the request still executes, but a naive retry after a timeout would create a second line.

Request body

Field Type Required Default Description
package (or package_id) int yes Package the line inherits from. Must exist. Reseller keys must have access.
member_id int admin only, required Owner reseller id. Reseller keys must not send this and receive 403 admin_only_field if they do.
username string no autogenerated u_<8hex> Must be unique panel-wide.
password string no autogenerated (8 hex chars) Reseller keys whose 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.
reseller_notes string no Free-form note stored on the line. Translates to notes internally.
max_connections int admin only package default Clamped to [1, 100].
mac_addr string no Accepted for backwards compatibility with clients that always send it. Silently ignored (the product does not provision physical devices).
rid string no Idempotency key. See above.
{
  "package": 42,
  "member_id": 100,
  "username": "reseller1_20260808",
  "password": "s3cret-pw",
  "bouquets": [1, 2],
  "reseller_notes": "created from invoice INV-42",
  "rid": "invoice-INV-2026-00814"
}

Response

200 OK with a compact object. The full line record is not returned here; if you need every field, call GET /ext/lines?username=<name> right after the create, or use the native create endpoint which returns the complete row.

{
  "line_id": "b32c1a04-11ea-4c67-8fd1-0001000000f1",
  "expire_at": "2026-09-08T00:00:00+00:00",
  "transaction_amount": 0,
  "rid": "invoice-INV-2026-00814"
}

expire_at is ISO 8601 in UTC, derived from the package's official duration added to the current instant. transaction_amount is the credits that were debited (0 for admin keys and users-mode resellers). rid echoes back only if it was sent in the request.

Examples

cURL

curl -X POST https://<your-panel-domain>/panel-api/onestream/ext/line/create \
  -H "X-Api-Key: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "package": 42,
    "member_id": 100,
    "username": "reseller1_20260808",
    "password": "s3cret-pw",
    "reseller_notes": "invoice INV-42",
    "rid": "invoice-INV-2026-00814"
  }'

PHP raw

$ch = curl_init('https://<your-panel-domain>/panel-api/onestream/ext/line/create');
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_20260808',
        'password'       => 's3cret-pw',
        'reseller_notes' => 'invoice INV-42',
        'rid'            => 'invoice-INV-2026-00814',
    ]),
]);
$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",
    headers={"X-Api-Key": "<your-api-key>"},
    json={
        "package": 42,
        "member_id": 100,
        "username": "reseller1_20260808",
        "password": "s3cret-pw",
        "reseller_notes": "invoice INV-42",
        "rid": "invoice-INV-2026-00814",
    },
    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. details.fields lists the offending name. Remove member_id. Reseller keys always create lines under themselves.
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.
403 password_change_not_allowed Reseller group has allow_change_pass=0. Omit password and let the API autogenerate it.
409 Transaction already processed Same rid was reused with a different body. Use a fresh rid, or replay with the exact original body.
422 validation_error Missing package, admin key without member_id, unknown package, unknown member_id, username collision, bouquet not accessible. See details.field (or details.invalid_ids for bouquets).
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