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.

Admin keys see every reseller on the panel. Since the panel update of 2026-09-25 a reseller key with the subresellers:read scope can call it too, through the same URL or through /reseller/index.php: it gets only its own sub-resellers and theirs, the same accounts it sees under Resellers Network, and its member group needs Can Create Subresellers. 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, or reseller key with subresellers:read. See Xtream Codes compatibility for the full auth contract.

Required scope

resellers:read for an admin key, subresellers:read for a reseller key. A key without the scope for its type gets STATUS_NO_PERMISSIONS with insufficient_scope, and details.required_scope names the scope it lacks.

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 An admin key without resellers:read, or a reseller key without subresellers:read. Issue a new key with the scope for its type. From a reseller integration, action=user_info reads the caller's own account without any scope.
STATUS_NO_PERMISSIONS sub_reseller_view_not_allowed Reseller key whose member group does not have Can Create Subresellers. Ask the panel administrator to enable it for the group.
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