Create a reseller
Create a new account inside the reseller tree. The behavior depends on the calling key. An admin key can create a top-level reseller in either billing mode with any starting balance. A reseller key can only create a sub-reseller under its own account, in the same billing mode as the parent, with a zero starting balance.
The endpoint runs the check and the insert inside a single transaction with a SELECT ... FOR UPDATE on the caller's own row, so two concurrent creates can never over-allocate slots or double-spend credits. Retries covered by the idempotency key never provision a duplicate account.
Endpoint
POST https://<your-panel-domain>/panel-api/v1/resellers
Authentication
Send the API key in the Authorization: Bearer <your-api-key> header. Both admin and reseller keys can call this endpoint. Which parameters they can send differs (see below).
Required scope
subresellers:write
Admin keys also carry this scope by default. It is called subresellers:write (not resellers:write) so a reseller key can hold the write half of the reseller graph without being able to touch anyone else's account.
Idempotency
Every POST must include an Idempotency-Key header. Reusing the same key with the same body replays the cached response; reusing it with a different body returns 409 idempotency_conflict. Use a stable value such as the invoice ID or CRM record ID that triggered the provision, so a network retry never creates a second reseller with a suffixed username.
Request body
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
username |
string | yes | Login name. Minimum 3 characters. Cannot contain % & ? # / \ = + @ : ;. |
|
password |
string | yes | Login password. Minimum 8 characters, same character restrictions as username. |
|
email |
string | yes | Validated with FILTER_VALIDATE_EMAIL. |
|
member_group_id |
int | admin only | Which member group the new reseller belongs to. Ignored on reseller keys (the target group is fixed by the panel admin's sub-reseller setup). | |
credits |
float | admin only | 0 |
Starting credit balance. Forced to 0.0 for reseller-key callers; top up later with POST /resellers/{id}/billing/adjust. |
billing_mode |
string | admin only | "credits" |
Either "credits" or "users". Any other value falls back to "credits". Reseller-key callers inherit the parent's mode; the field is ignored. |
max_users |
int | conditional | 0 |
Required when billing_mode is "users", and must be positive. |
billing_expires |
int | conditional | Unix timestamp in seconds. Required when a reseller-key caller in users mode creates a sub-reseller. Optional for admin-key calls. |
|
owner_id |
int | admin only | panel admin | Parent account for the new reseller. Reseller-key callers must either omit this field or send their own ID; any other value returns 403 owner_must_be_self. |
notes |
string | no | "" |
Free-form label kept alongside the reseller record. |
{
"username": "reseller_new",
"password": "s3cret-p4ssword",
"email": "reseller_new@example.com",
"member_group_id": 4,
"credits": 10.0,
"billing_mode": "credits",
"notes": "docs batch fixture"
}
Response
On success the endpoint returns 201 Created with the new ID, the billing snapshot the account starts with, and credits_charged, which reflects how many credits were deducted from the caller. For admin-key calls that field is always 0. For reseller-key calls in credits mode it matches the per-creation price configured on the caller's group.
{
"id": 262260,
"username": "reseller_new",
"email": "reseller_new@example.com",
"member_group_id": 4,
"owner_id": 260897,
"credits_charged": 0,
"billing": {
"mode": "credits",
"credits": 10,
"max_users": null,
"active_users": null,
"billing_expires": null
}
}
Examples
cURL
curl -X POST https://<your-panel-domain>/panel-api/v1/resellers \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: crm-signup-90142" \
-d '{
"username": "reseller_new",
"password": "s3cret-p4ssword",
"email": "reseller_new@example.com",
"member_group_id": 4,
"credits": 10.0,
"billing_mode": "credits",
"notes": "docs batch fixture"
}'
PHP SDK
require __DIR__ . '/api-panel-php-sdk-1.0.0/autoload.php';
use XtreamAI\PanelApi\PanelApiClient;
$client = new PanelApiClient(baseUrl: 'https://<your-panel-domain>', token: '<your-api-key>');
$reseller = $client->resellers->create(
username: 'reseller_new',
password: 's3cret-p4ssword',
email: 'reseller_new@example.com',
memberGroupId: 4,
credits: 10.0,
billingMode: 'credits',
notes: 'docs batch fixture',
idempotencyKey: 'crm-signup-90142',
);
echo $reseller->id, PHP_EOL;
Python SDK
from xtream_ai_panel_api import PanelApiClient
client = PanelApiClient(base_url="https://<your-panel-domain>", token="<your-api-key>")
reseller = client.resellers.create(
username="reseller_new",
password="s3cret-p4ssword",
email="reseller_new@example.com",
member_group_id=4,
credits=10.0,
billing_mode="credits",
notes="docs batch fixture",
idempotency_key="crm-signup-90142",
)
print(reseller.id)
Errors
| HTTP | Error slug | When it happens | How to fix |
|---|---|---|---|
| 400 | missing_idempotency_key |
The Idempotency-Key header was not sent. |
Add the header on every POST. See the Rate limits and Idempotency page. |
| 401 | invalid_key |
Missing, malformed, or unknown API key. | Send a live key in Authorization: Bearer <token>. |
| 402 | insufficient_credits |
A reseller-key caller in credits mode does not have enough balance to pay the per-creation price. |
Top up the caller with POST /resellers/{id}/billing/adjust, then retry. |
| 403 | insufficient_scope |
The key does not carry subresellers:write. |
Rotate the key with the correct scopes. |
| 403 | owner_must_be_self |
A reseller-key caller passed an owner_id other than their own. |
Omit owner_id, or set it to the caller's own reg-user ID. |
| 403 | sub_reseller_creation_not_allowed |
The caller's group does not have create_sub_resellers = 1. |
The panel admin turns this on per group in the CMS. |
| 409 | idempotency_conflict |
The Idempotency-Key was reused with a different body. |
Generate a new key or resend the original body. |
| 422 | validation_error |
Username, password, or email fails the validators (length, forbidden characters, invalid email). The message carries the reason. This endpoint returns a human message, not a structured details.field. |
Fix the offending field and resend. |
| 422 | insufficient_slots |
A reseller-key caller in users mode asked for more max_users than they have available. |
Free slots on the caller, or lower max_users. |
| 422 | no_sub_reseller_setup |
The panel admin has not configured a sub-reseller package for the caller's group. | The admin configures it in the CMS. |
| 422 | billing_expires_required |
A reseller-key caller in users mode did not pass billing_expires. |
Add a positive Unix timestamp. |
| 429 | rate_limited |
The per-key request budget for this minute is spent. | Back off and retry after the minute rolls over. |