Delete 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=delete_line removes a subscriber line permanently. The request is translated to the native POST /panel-api/v1/lines/{id}/delete and rewrapped in the classic {"status": "STATUS_SUCCESS", "data": {...}} envelope.

The delete cascade is the same one the CMS uses:

  • Bouquet assignments (user_bouquets) are removed.
  • Contact info rows (admin_user_contact_info, reseller_user_contact_info) are removed.
  • The users row is deleted.

Delete does not:

  • Refund credits already paid to create or extend the line. This matches the classic panels' behavior; deletion is not a compensating transaction. In credits billing mode, the reseller keeps the debit.
  • Preserve the row. There is no soft delete. Once the delete succeeds, the id will not resurface, and re-creating the line requires a fresh create_line (with a new autogenerated id).

In users billing mode, the slot the deleted line occupied is freed. The slot count is derived from the row's existence, so removing the row automatically decrements the reseller's active-user count.

Reseller keys need their member group to have delete_users=1. Groups with delete_users=0 receive STATUS_NO_PERMISSIONS with delete_not_allowed; the panel's own UI hides the delete button for those groups, and the API enforces the same rule server-side so a fabricated POST cannot bypass it.

The endpoint verifies the row disappeared before reporting success. If the underlying delete transaction fails mid-way (deadlock, permissions, socket hiccup), the API returns STATUS_FAILURE with delete_failed instead of claiming success on a line that is still in the database.

Endpoint

POST https://<your-panel-domain>/panel-api/xc/{accesscode}/admin/index.php?action=delete_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 but recommended. Pass rid=<unique-per-operation> in the query string or POST body. Same rid with same body replays the original response instead of running a second delete. Same rid with a different body returns idempotency_conflict. Window is 24 hours. See the idempotency section.

The natural failure mode without rid is a retry after a network timeout: the first delete may have succeeded, and the retry sees a not_found and treats it as a bug. With rid the retry replays the original success.

Request body

Field Type Required Default Description
id int yes The line to delete.
rid string no Idempotency identifier.

Response

data is a small confirmation object.

Field Type Description
id int The id of the deleted line.
username string The username the line had at the moment of deletion. Useful for reconciliation logs.
deleted bool Always true when the response is STATUS_SUCCESS.
{
  "status": "STATUS_SUCCESS",
  "data": {
    "id": 172511994,
    "username": "u_a1b2c3d4",
    "deleted": true
  }
}

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=delete_line" \
  -d "id=172511994" \
  -d "rid=del-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' => 'delete_line']);
$body = http_build_query([
    'id'  => 172511994,
    'rid' => 'del-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'] ?? 'delete_line failed');
}
// $resp['data']['deleted'] === true

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": "delete_line"},
    data={
        "id":  172511994,
        "rid": f"del-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", "delete_line failed"))
assert body["data"]["deleted"] is True

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. data.details.field is "id". Send a numeric id.
STATUS_FAILURE not_found The id does not exist (already deleted, or never existed), or a reseller key targeted a line owned by another reseller. Verify the id and ownership. On idempotent retries, use rid to replay the original success instead of hitting not_found.
STATUS_NO_PERMISSIONS delete_not_allowed Reseller's member group has delete_users=0. Ask the panel admin to enable line deletion for the group, or use an admin key.
STATUS_NO_PERMISSIONS insufficient_scope The key does not have lines:write. Grant the scope.
STATUS_FAILURE delete_failed The delete cascade started but the users row was still present when the API re-checked. The underlying UPDATE stream is not transactional, so a mid-way failure (permissions, deadlock, socket hiccup) leaves the row alive. Retry. If the failure persists, check MySQL error logs and the reseller-user-contact-info table for orphan rows.
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. 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