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_creditsbefore the update. Failure to deduct returns402 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 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.
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. |
{
"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.
{
"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
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
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
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 with an explicit exp_date instead. |
| 429 | rate_limited |
The key hit its per-minute cap. | Back off and retry after Retry-After seconds. |