Extend 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=extend_line pushes a line's exp_date forward by the official duration of a package. The request is translated to the native POST /panel-api/v1/lines/{id}/renew and rewrapped in the classic envelope.

The new expiry is max(current_exp_date, now) + package.official_duration. If the line already expired, the countdown restarts from now. If it is still active, the extension is added on top of the current expiry so no days are lost.

Extending also reactivates the line: admin_enabled and enabled are both set to true in the same statement, so an extend brings back a previously disabled or admin-blocked line without a second call.

Billing:

  • Reseller in credits mode deducts package.official_credits before the update. Failure to deduct returns STATUS_INSUFFICIENT_CREDITS. If the update fails after the deduction, the credits are refunded.
  • Reseller in users mode extends for free (the slot is already accounted for as long as the line exists).
  • Admin keys skip the billing checks entirely.

Perpetual lines (exp_date == null) cannot be extended. Extending would set an expiry and effectively degrade the line, so the endpoint returns STATUS_INVALID_DATA with line_has_no_expiry.

Endpoint

POST https://<your-panel-domain>/panel-api/xc/{accesscode}/admin/index.php?action=extend_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 in the XC dialect, but strongly recommended for extensions because they are the classic double-charge risk on network retries. Pass rid=<unique-per-operation> in the query string or POST body. Same rid with 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

Field Type Required Default Description
id int yes The line to extend.
package int yes Package to extend with. Must exist, must not be a trial package. Aliased to package_id; you may send package_id directly.
rid string no Idempotency identifier.

The classic panels also accepted months=N, days=N, or a computed exp_date on this action. The native renew handler extends by the official duration attached to the package, so those alternative forms are not translated. If your integration currently computes the expiry itself, create the equivalent package in the Xtream AI panel and send its id instead. This keeps renewal duration and cost in one place (the package definition).

Response

data is the full Line object with the new exp_date, and with enabled and admin_enabled both true.

{
  "status": "STATUS_SUCCESS",
  "data": {
    "id": 172511994,
    "username": "u_a1b2c3d4",
    "password": "Sup3rSecret1",
    "member_id": 100,
    "exp_date": 1849365538,
    "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=extend_line" \
  -d "id=172511994" \
  -d "package=42" \
  -d "rid=renew-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' => 'extend_line']);
$body = http_build_query([
    'id'      => 172511994,
    'package' => 42,
    'rid'     => 'renew-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'] ?? 'extend_line failed');
}
$line = $resp['data'];
echo 'new expiry ', date(DATE_ATOM, $line['exp_date']), PHP_EOL;

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": "extend_line"},
    data={
        "id":      172511994,
        "package": 42,
        "rid":     f"renew-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", "extend_line failed"))
line = body["data"]
print("new expiry", line["exp_date"])

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; package is missing (message says package_id); or the package id does not exist. Send both id and package.
STATUS_INVALID_DATA line_has_no_expiry The line is perpetual (exp_date == null). Extending would set an expiry and degrade the line. Use action=edit_line (or the native update endpoint) if you actually want to convert a perpetual line into a timed one.
STATUS_INVALID_PACKAGE renew_with_trial_package_not_allowed package points to a trial package. Pick a non-trial package.
STATUS_INVALID_PACKAGE package_not_accessible The reseller cannot sell from that package. Use a package inside the reseller's member group.
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 insufficient_scope The key does not have lines:write. Grant the scope.
STATUS_FAILURE billing_expired Reseller subscription has expired. Extend the reseller subscription first.
STATUS_INSUFFICIENT_CREDITS insufficient_credits Reseller in credits mode lacks credits for the package cost. Top up credits, or pick a cheaper package.
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