---
title: "Create a line, POST /ext/line/create"
description: "Provision a new subscriber line under a package with the OneStream compatibility dialect. Returns the opaque line UUID, the effective expiry, and the credits deducted."
---

# Create a line, POST /ext/line/create

> [!NOTE]
> If you are starting a new integration instead of migrating, prefer the [native v1 API](/docs/?page=xai-ref-lines-create) with the [official SDKs](/docs/?page=panel-api-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](/docs/?page=panel-api-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](/docs/?page=panel-api-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. |

```json
{
  "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](/docs/?page=xai-ref-lines-create) which returns the complete row.

```json
{
  "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

```bash
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

```php
$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

```python
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

- [OneStream compatibility overview](/docs/?page=panel-api-onestream-compatibility)
- [Create a line with an explicit expiry, POST /ext/line/create-advanced](/docs/?page=os-ref-lines-create-advanced)
- [Renew a line, POST /ext/line/{uuid}/renew](/docs/?page=os-ref-line-renew)
- [Native lines API](/docs/?page=xai-ref-lines-list)
- [Rate limits and idempotency](/docs/?page=panel-api-rate-limits-idempotency)
