---
title: "Create a sub-reseller, POST /ext/user/create"
description: "Provision a sub-reseller under the caller. Same field names as OneStream, including the optional password_confirmation check."
---

# Create a sub-reseller, POST /ext/user/create

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

Create a sub-reseller. Admin keys can create sub-resellers freely. Reseller keys can create sub-resellers only if their member group has `create_sub_resellers = 1`, and the panel operator has configured a sub-reseller setup for that group. On reseller keys the new sub-reseller inherits the parent's billing mode (credits or users) and is charged the group's `create_sub_resellers_price` in credits mode.

The OneStream body shape is honored, including the optional `password_confirmation` field. If your client sends both `password` and `password_confirmation` and they do not match, the request is rejected before any row is written.

## Endpoint

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

## Authentication

`X-Api-Key`. `X-Auth-User` and `Authorization: Bearer` also accepted. See [Authentication](/docs/?page=panel-api-authentication).

## Required scope

`resellers:write`.

## Idempotency

Optional but recommended. A retried `rid` returns the cached response instead of creating a second sub-reseller. In credits mode this is what prevents a network retry from charging the caller twice.

## Request body

| Field | Type | Required | Default | Description |
| ----- | ---- | -------- | ------- | ----------- |
| `name` (or `username`) | string | yes | | Login name for the new sub-reseller. Must be unique panel-wide. |
| `email` | string | no | | Contact email stored on the reg-user row. |
| `password` | string | yes | | Plain-text password. Stored as the panel stores every user credential. |
| `password_confirmation` | string | no | | If sent, must equal `password`. If omitted, the check is skipped. |
| `credits` | number | no | 0 | Initial credit balance. On reseller keys this field is ignored: the child is created with 0 credits and the caller must top up separately using [POST /ext/user/{id}/credit](/docs/?page=panel-api-onestream-compatibility#post-extuseridcredit). |
| `notes` | string | no | | Free-form note stored on the reg-user row. |
| `member_group_id` | int | admin, yes | | Reseller group the new user joins. Admin keys must pick a group with `is_reseller = 1`. On reseller keys the group is forced to the value the panel operator configured for the parent's group, and any value sent in the body is discarded. |
| `rid` | string | no | | Idempotency key. |

The OneStream API does not expose `member_group_id` in its documented shape. If you are migrating a client that never sent this field, add it now for admin keys; a plain create without a group name is rejected with `422 Member group not found`.

```json
{
  "name": "reseller_billing",
  "email": "reseller_billing@example.com",
  "password": "topsecret",
  "password_confirmation": "topsecret",
  "credits": 100,
  "notes": "created by billing system",
  "member_group_id": 4,
  "rid": "user-create-billing-1"
}
```

## Response

`200 OK` with the new sub-reseller's internal integer id and the echoed `rid`. Sub-resellers do not use the opaque UUID format that lines use, because the integer id is safe to expose (it is not a target of enumeration attacks the way live subscriber lines are).

```json
{
  "id": 5555,
  "rid": "user-create-billing-1"
}
```

Store the returned `id` next to your own billing record; it is the value you pass to [POST /ext/user/{id}/update](/docs/?page=panel-api-onestream-compatibility#post-extuseridupdate) and [POST /ext/user/{id}/credit](/docs/?page=panel-api-onestream-compatibility#post-extuseridcredit) later on.

## Examples

### cURL

```bash
curl -X POST https://<your-panel-domain>/panel-api/onestream/ext/user/create \
  -H "X-Api-Key: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "reseller_billing",
    "email": "reseller_billing@example.com",
    "password": "topsecret",
    "password_confirmation": "topsecret",
    "credits": 100,
    "notes": "created by billing system",
    "member_group_id": 4,
    "rid": "user-create-billing-1"
  }'
```

### PHP raw

```php
$ch = curl_init('https://<your-panel-domain>/panel-api/onestream/ext/user/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([
        'name'                  => 'reseller_billing',
        'email'                 => 'reseller_billing@example.com',
        'password'              => 'topsecret',
        'password_confirmation' => 'topsecret',
        'credits'               => 100,
        'notes'                 => 'created by billing system',
        'member_group_id'       => 4,
        'rid'                   => 'user-create-billing-1',
    ]),
]);
$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/user/create",
    headers={"X-Api-Key": "<your-api-key>"},
    json={
        "name": "reseller_billing",
        "email": "reseller_billing@example.com",
        "password": "topsecret",
        "password_confirmation": "topsecret",
        "credits": 100,
        "notes": "created by billing system",
        "member_group_id": 4,
        "rid": "user-create-billing-1",
    },
    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 the credits configured as `create_sub_resellers_price`. | Top up credits before creating. |
| 403 | `insufficient_scope` | Token lacks `resellers:write`. | Issue a key with the scope. |
| 403 | `create_sub_resellers_not_allowed` | Reseller key belongs to a group with `create_sub_resellers = 0`, or the panel operator has not configured a sub-reseller setup for the group. | Ask the panel operator to enable sub-reseller creation for the 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 | `password and password_confirmation do not match` | Both fields were sent and their values differ. | Send them equal, or omit `password_confirmation` to skip the check. |
| 422 | `Member group not found` | Admin key did not pass `member_group_id`, or the value points to a non-reseller group. | Pass a valid reseller group id (`is_reseller = 1`). |
| 422 | `validation_error` | Missing `name` or `password`, username collision, malformed email. | 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

- [OneStream compatibility overview](/docs/?page=panel-api-onestream-compatibility)
- [Native reseller create endpoint](/docs/?page=xai-ref-resellers-create)
- [Native reseller list endpoint](/docs/?page=xai-ref-resellers-list)
- [Rate limits and idempotency](/docs/?page=panel-api-rate-limits-idempotency)
