List sub-resellers, action=get_users

Return the resellers registered under the panel in the classic Xtream Codes envelope. The compat layer translates this call to the native GET /panel-api/v1/resellers and wraps the result in the XC {"status": "STATUS_SUCCESS", "data": {...}} shape. Rows are ordered by numeric ID and returned with the billing summary already embedded, so a reconciliation loop that walks the whole tree does not need a second call per reseller.

Only admin API keys can call this action. Reseller keys are rejected before the handler runs because they never receive resellers:read at issuance. If you need a reseller-side view of its own account, call action=user_info instead.

Endpoint

GET https://<your-panel-domain>/panel-api/xc/{accesscode}/admin/index.php?action=get_users

The {accesscode} segment is decorative. Any non-empty value works. Security is enforced through the API key, not the path.

Authentication

Send the API key either as api_key=<your-api-key> in the query string or as Authorization: Bearer <your-api-key> in the header. Admin key only. See Xtream Codes compatibility for the full auth contract.

Required scope

resellers:read. Reseller keys never carry this scope, so a reseller-issued key always sees STATUS_NO_PERMISSIONS with insufficient_scope.

Query parameters

Name Type Required Default Description
limit int no 50 Page size. Clamped to [1, 100] on the native handler.
start int no 0 Offset-style cursor from the classic panels. Values greater than zero are forwarded to the native cursor (rows with id > start are returned). Best-effort translation, see the note below.

The XC compat dialect only forwards limit and start. Other filters that some classic panels accept on this action (for example member_group_id, status, or username) are dropped silently and the full list is returned. If you need to filter, call the native GET /panel-api/v1/resellers endpoint directly, which accepts member_group_id, status, and username as first-class query parameters.

start maps to the native keyset cursor. On a healthy panel with dense monotonic IDs this behaves like a plain offset. On a panel where rows have been deleted historically, a given start value may skip more or fewer rows than a pure offset would. If exact pagination matters, migrate this call to the native dialect and consume next_cursor instead.

Response

The data payload is the native list envelope: an items array plus next_cursor. Pass next_cursor as the next call's start value to advance. When next_cursor is null, you have reached the last page.

active_users is populated only for resellers in users billing mode. For credits-mode resellers the field is always null. created_at is a Unix timestamp in seconds, or null for historical rows migrated before that column was added.

{
  "status": "STATUS_SUCCESS",
  "data": {
    "items": [
      {
        "id": 100002432,
        "username": "reseller_alice",
        "email": "alice@example.com",
        "member_group_id": 65,
        "member_group_name": "RESELLER MASTER PREMIUM",
        "status": 1,
        "billing_mode": "credits",
        "credits": 947,
        "max_users": 0,
        "active_users": null,
        "billing_expires": null,
        "created_at": null
      },
      {
        "id": 100260595,
        "username": "reseller_bob",
        "email": "bob@example.com",
        "member_group_id": 4,
        "member_group_name": "RESELLER",
        "status": 1,
        "billing_mode": "credits",
        "credits": 0.25,
        "max_users": 0,
        "active_users": null,
        "billing_expires": null,
        "created_at": 1578098423
      },
      {
        "id": 100260641,
        "username": "reseller_carol",
        "email": "carol@example.com",
        "member_group_id": 65,
        "member_group_name": "RESELLER MASTER PREMIUM",
        "status": 1,
        "billing_mode": "credits",
        "credits": 2538,
        "max_users": 0,
        "active_users": null,
        "billing_expires": 1783911599,
        "created_at": 1579225251
      }
    ],
    "next_cursor": 100260641
  }
}

HTTP status is always 200. Your client must branch on body.status, not on the HTTP code.

Examples

cURL

curl "https://<your-panel-domain>/panel-api/xc/panel_api/admin/index.php?api_key=<your-api-key>&action=get_users&limit=50"

To fetch the next page, pass the previous next_cursor as start:

curl "https://<your-panel-domain>/panel-api/xc/panel_api/admin/index.php?api_key=<your-api-key>&action=get_users&limit=50&start=100260641"

PHP raw

$url = 'https://<your-panel-domain>/panel-api/xc/panel_api/admin/index.php'
     . '?' . http_build_query([
         'api_key' => '<your-api-key>',
         'action'  => 'get_users',
         'limit'   => 50,
       ]);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);
if ($body['status'] !== 'STATUS_SUCCESS') {
    throw new RuntimeException($body['data']['message'] ?? 'get_users failed');
}
foreach ($body['data']['items'] as $reseller) {
    echo $reseller['id'], "\t", $reseller['username'], PHP_EOL;
}

Python raw

import requests

r = requests.get(
    "https://<your-panel-domain>/panel-api/xc/panel_api/admin/index.php",
    params={"api_key": "<your-api-key>", "action": "get_users", "limit": 50},
    timeout=30,
)
r.raise_for_status()
body = r.json()
if body["status"] != "STATUS_SUCCESS":
    raise RuntimeError(body["data"].get("message", "get_users failed"))
for reseller in body["data"]["items"]:
    print(reseller["id"], reseller["username"])

Errors

HTTP is always 200 for this dialect. The status field is the branch signal, and data.error carries the exact slug so your logs can distinguish causes.

status Error slug When it happens How to fix
STATUS_FAILURE invalid_key The api_key (or Authorization: Bearer) is missing, malformed, or unknown. Send a live admin key.
STATUS_NO_PERMISSIONS insufficient_scope The key does not carry resellers:read. Reseller-issued keys always land here. Use an admin key. To view the caller's own reseller account from a reseller-side integration, call action=user_info.
STATUS_NO_PERMISSIONS admin_only_endpoint A key that carried the scope but is still tagged as a reseller reached the handler. Use an admin key.
STATUS_FAILURE rate_limited The per-key request budget for the current minute is spent. Slow the polling loop, then retry after the minute rolls over.

See also