---
title: "Extend a line, action=extend_line"
description: "Xtream Codes compatibility renewal. Pushes exp_date forward by the official duration of the supplied package. Deducts credits in credits mode, free in users mode, and reactivates a disabled line as part of the same call."
---

# Extend a line

> [!NOTE]
> If you are starting a new integration instead of migrating an existing one, prefer the [native v1 API](/docs/?page=xai-ref-lines-renew) with the [official SDKs](/docs/?page=panel-api-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`](/docs/?page=xai-ref-lines-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](/docs/?page=panel-api-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](/docs/?page=panel-api-xtream-codes-compatibility#idempotency-via-rid).

## 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`.

```json
{
  "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

```bash
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)

```php
$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)

```python
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`](/docs/?page=xc-ref-lines-edit) (or the [native update endpoint](/docs/?page=xai-ref-lines-update)) 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

- [Create a line, action=create_line](/docs/?page=xc-ref-lines-create)
- [Edit a line, action=edit_line](/docs/?page=xc-ref-lines-edit)
- [Enable a line, action=enable_line](/docs/?page=xc-ref-lines-enable)
- [Xtream Codes / XUI.one / OTT Panel compatibility](/docs/?page=panel-api-xtream-codes-compatibility)
- [Native renew endpoint, POST /lines/{id}/renew](/docs/?page=xai-ref-lines-renew)
