Edit a line

If you are starting a new integration instead of migrating an existing one, prefer the native v1 API with the official SDKs. The XC dialect keeps compatibility with legacy tooling; the native dialect gives you typed models, header-based idempotency, and structured HTTP status codes.

action=edit_line updates a subset of a subscriber line's fields and returns the full re-read Line object. The request is translated to the native POST /panel-api/v1/lines/{id}/update and rewrapped in the classic {"status": "STATUS_SUCCESS", "data": {...}} envelope.

The native update handler only writes a fixed set of fields: password, exp_date, max_connections, is_restreamer, enabled, admin_enabled (admin key only), allowed_ips, allowed_ua. Classic XC panels accepted many more fields (bouquets_selected[], username, notes, is_trial, ...) and silently dropped anything the underlying handler did not process. The compat layer keeps that fidelity: it forwards only the supported fields to the native handler. Fields the native handler does not accept are ignored, with one important protection.

If every field in the request is unsupported (for example, only bouquets_selected[]), the compat layer returns STATUS_INVALID_DATA up front with an explicit message listing the supported fields, so a call that "changes nothing" is loud instead of silent. But if the request mixes supported and unsupported fields, the supported ones are applied and the rest are dropped without warning. Send only fields you know are supported.

Bouquets, username, notes, and is_trial cannot be changed through this endpoint (or through the native update endpoint). The current workaround for changing bouquets is delete + recreate.

Endpoint

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

Both /admin/index.php and /reseller/index.php are accepted. The admin-versus-reseller decision comes from the key.

Authentication

Any one of these three forms:

  • ?api_key=<your-api-key> in the query string.
  • api_key=<your-api-key> in the POST body form field.
  • Authorization: Bearer <your-api-key> HTTP header.

See Authentication.

Required scope

lines:write.

Idempotency

Optional. Pass rid=<unique-per-operation> to opt in. Same rid with the same body replays the original response; same rid with a different body returns idempotency_conflict. Window is 24 hours. See the idempotency section on the compatibility overview.

Request body

The XC dialect posts form-encoded (application/x-www-form-urlencoded). Booleans coerce loosely from strings (see Boolean coercion).

Field Type Required Default Description
id int yes The line to update.
password string no New stream password. Reseller keys whose member group has allow_change_pass=0 cannot set this.
exp_date int (UTC epoch) no New expiry. On admin keys it may be null to make the line perpetual; the XC dialect cannot express null because everything is a string, so use the native update endpoint for that specific case.
max_connections int no Concurrent-connection cap. Clamped to [1, 100].
is_restreamer bool no Whether the line may restream through the panel.
enabled bool no Reseller-visible toggle. The dedicated actions enable_line and disable_line are equivalent shortcuts.
admin_enabled bool admin only Admin override. If set to false, the line is blocked no matter what enabled says.
allowed_ips[] string[] no IPv4 allow-list, up to 50 entries. Invalid entries drop silently.
allowed_ua[] string[] no User-Agent allow-list, up to 50 entries capped at 500 chars each.
rid string no Idempotency identifier.

At least one editable field is required. A request that sends only fields the native handler ignores (for example, bouquets_selected[], username, notes, is_trial) returns STATUS_INVALID_DATA with the message edit_line: none of the provided fields is editable by this API. Supported: password, exp_date, max_connections, is_restreamer, enabled, allowed_ips, allowed_ua.

Response

data is the full Line object, re-read from the database after the update. Same shape used by action=get_line.

{
  "status": "STATUS_SUCCESS",
  "data": {
    "id": 172511994,
    "username": "u_a1b2c3d4",
    "password": "Sup3rSecret1",
    "member_id": 100,
    "exp_date": 1817743138,
    "max_connections": 3,
    "is_trial": false,
    "is_restreamer": false,
    "enabled": true,
    "admin_enabled": true,
    "bouquets": [2, 4],
    "created_at": 1786207138
  }
}

HTTP status is always 200, even on failure.

Examples

cURL

curl -X POST "https://<your-panel-domain>/panel-api/xc/panel_api/admin/index.php?api_key=<your-api-key>&action=edit_line" \
  -d "id=172511994" \
  -d "max_connections=3" \
  -d "enabled=true" \
  -d "rid=edit-172511994-2026-01-15"

PHP (raw HTTP)

$url = 'https://<your-panel-domain>/panel-api/xc/panel_api/admin/index.php'
     . '?' . http_build_query(['api_key' => '<your-api-key>', 'action' => 'edit_line']);
$body = http_build_query([
    'id'              => 172511994,
    'max_connections' => 3,
    'enabled'         => 'true',
    'rid'             => 'edit-172511994-' . bin2hex(random_bytes(8)),
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $body,
]);
$resp = json_decode(curl_exec($ch), true);
curl_close($ch);
if (($resp['status'] ?? '') !== 'STATUS_SUCCESS') {
    throw new RuntimeException($resp['data']['message'] ?? 'edit_line failed');
}
$line = $resp['data'];

Python (raw HTTP)

import requests, secrets

r = requests.post(
    "https://<your-panel-domain>/panel-api/xc/panel_api/admin/index.php",
    params={"api_key": "<your-api-key>", "action": "edit_line"},
    data={
        "id":              172511994,
        "max_connections": 3,
        "enabled":         "true",
        "rid":             f"edit-172511994-{secrets.token_hex(8)}",
    },
    timeout=30,
)
r.raise_for_status()
body = r.json()
if body.get("status") != "STATUS_SUCCESS":
    raise RuntimeError(body["data"].get("message", "edit_line failed"))
line = body["data"]

Errors

Response is always HTTP 200. Branch on status, then data.error.

status Error slug When it happens How to fix
STATUS_INVALID_DATA validation_error id is missing or empty; or the request sent only fields the native handler ignores (message names the supported set). Send id plus at least one supported field.
STATUS_FAILURE not_found The id does not exist, or a reseller key targeted a line owned by another reseller. Verify the id and ownership.
STATUS_NO_PERMISSIONS admin_only_field A reseller key sent admin_enabled. Remove admin_enabled, or issue an admin key for this integration.
STATUS_NO_PERMISSIONS insufficient_scope The key does not have lines:write. Grant the scope, or issue a new key.
STATUS_NO_PERMISSIONS password_change_not_allowed Reseller group has allow_change_pass=0 and the request set password. Omit password.
STATUS_FAILURE idempotency_conflict Same rid reused with a different body. Pick a new rid, or send the original body.
STATUS_FAILURE idempotency_in_flight Same rid is still processing on another request. Retry after a moment.
STATUS_FAILURE invalid_key Token missing, unknown, disabled, expired, or IP not in allow-list. Verify the token and the IP allow-list.
STATUS_FAILURE rate_limited Per-minute cap or per-IP cap exceeded. Back off.
STATUS_FAILURE api_disabled Panel API is switched off. Contact the panel admin.

See also