---
title: "Renew a line"
description: "Extend a line's expiry by the package's official duration. Deducts credits in credits mode. Free in users mode."
---

# Renew a line

Push a line's `exp_date` forward by the official duration of a package. 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.

Renewing also reactivates the line: `admin_enabled` and `enabled` are both set to `true` in the same statement. That way a renew brings back a previously disabled or admin-blocked line without a second call.

Billing behavior:

- Reseller in **credits mode** deducts `package.official_credits` before the update. Failure to deduct returns `402 insufficient_credits`. If the update fails after the deduction, the credits are refunded.
- Reseller in **users mode** renews for free (a 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 renewed. Renewing would set an expiry and effectively degrade the line, so the endpoint returns `422 line_has_no_expiry`. Use [update](/docs/?page=xai-ref-lines-update) with a chosen `exp_date` if you really want to convert a perpetual line into a timed one.

## Endpoint

`POST https://<your-panel-domain>/panel-api/v1/lines/{id}/renew`

## Authentication

Bearer token in the `Authorization` header. See [Panel API authentication](/docs/?page=panel-api-authentication).

## Required scope

`lines:write`.

## Idempotency

Required. Renewals are the classic double-charge risk: sending the same `Idempotency-Key` on a retry returns the original response instead of charging twice. A missing header returns `400 missing_idempotency_key`.

## Path parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `id` | int | The line id to renew. |

## Request body

| Field | Type | Required | Default | Description |
| ----- | ---- | -------- | ------- | ----------- |
| `package_id` | int | yes | | Package to renew with. Must exist, must not be a trial package. Reseller keys must have access to the package. |

```json
{
  "package_id": 42
}
```

## Response

`200 OK` with the full line object. `exp_date` reflects the new expiry, and `enabled` and `admin_enabled` are both `true`.

```json
{
  "id": 172504295,
  "username": "u_a1b2c3d4",
  "password": "c30b22e4",
  "member_id": 100,
  "exp_date": 1791477054,
  "max_connections": 1,
  "is_trial": false,
  "is_restreamer": false,
  "enabled": true,
  "admin_enabled": true,
  "bouquets": [],
  "created_at": 1786206654
}
```

## Examples

### cURL

```bash
curl -X POST https://<your-panel-domain>/panel-api/v1/lines/172504295/renew \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: invoice-INV-2026-00915" \
  -d '{"package_id": 42}'
```

### PHP SDK

```php
require __DIR__ . '/api-panel-php-sdk-1.0.0/autoload.php';
use XtreamAI\PanelApi\PanelApiClient;

$client = new PanelApiClient(baseUrl: 'https://<your-panel-domain>', token: '<your-api-key>');
$line = $client->lines->renew(
    id:             172504295,
    packageId:      42,
    idempotencyKey: 'invoice-INV-2026-00915',
);
echo 'new expiry ', $line->expDate?->format(DATE_ATOM), PHP_EOL;
```

### Python SDK

```python
from xtream_ai_panel_api import PanelApiClient

client = PanelApiClient(base_url="https://<your-panel-domain>", token="<your-api-key>")
line = client.lines.renew(
    id=172504295,
    package_id=42,
    idempotency_key="invoice-INV-2026-00915",
)
print("new expiry", line.exp_date.isoformat() if line.exp_date else None)
```

## Errors

| HTTP | Error slug | When it happens | How to fix |
| ---- | ---------- | --------------- | ---------- |
| 400 | `missing_idempotency_key` | The `Idempotency-Key` header was not sent. | Send a per-intent key on every write. |
| 401 | `invalid_key` | Token is unknown, expired, disabled, or deleted. | Check the token or issue a new one. |
| 402 | `billing_expired` | Reseller subscription has expired. | Extend the reseller subscription. |
| 402 | `insufficient_credits` | Reseller (credits mode) lacks credits for the package. | Top up credits, or pick a cheaper package. |
| 403 | `package_not_accessible` | Reseller cannot sell from that package. | Use a package inside the reseller's member group. |
| 403 | `insufficient_scope` | Token lacks `lines:write`. | Issue a key with the scope. |
| 404 | `not_found` | The line id does not exist, or a reseller key targeted a line it does not own. | Verify the id and ownership. |
| 409 | `idempotency_conflict` | Same key was reused with a different body. | Use a fresh key. |
| 409 | `idempotency_in_flight` | Same key is still processing. | Retry after a moment. |
| 422 | `validation_error` | Missing `package_id`, or the package id does not exist. | See `details.field` in the response. |
| 422 | `renew_with_trial_package_not_allowed` | `package_id` points to a trial package. | Pick a non-trial package. |
| 422 | `line_has_no_expiry` | The line is perpetual (`exp_date == null`) and renewing would set an expiry. | Use [update](/docs/?page=xai-ref-lines-update) with an explicit `exp_date` instead. |
| 429 | `rate_limited` | The key hit its per-minute cap. | Back off and retry after `Retry-After` seconds. |

## See also

- [Create a line](/docs/?page=xai-ref-lines-create)
- [Update a line](/docs/?page=xai-ref-lines-update)
- [Panel API lines overview](/docs/?page=xai-ref-lines-list)
- [Rate limits and idempotency](/docs/?page=panel-api-rate-limits-idempotency)
