---
title: "Renew a line, POST /ext/line/{uuid}/renew"
description: "Extend an existing line by another package cycle. Deducts credits in credits mode. Returns the new expiry and the transaction amount."
---

# Renew a line, POST /ext/line/{uuid}/renew

> [!NOTE]
> If you are starting a new integration instead of migrating, prefer the [native v1 API](/docs/?page=xai-ref-lines-renew) with the [official SDKs](/docs/?page=panel-api-sdks). The OneStream dialect exists to let existing OneStream code point at Xtream AI with only a base URL change.

Push a line's expiry forward by the official duration of a package. The new expiry is `max(current_expire_at, now) + package.official_duration`. An already expired line restarts from now, an active line accumulates the extension on top of its current expiry so no days are lost.

Renewing also reactivates the line: it comes back enabled even if it had been disabled. That way one call to renew is enough to bring a paying customer back after a lapse.

Billing behavior mirrors the create endpoint: reseller keys in credits mode pay the package's official credits, users-mode resellers renew for free (the slot is already accounted for), and admin keys skip billing checks entirely.

## Endpoint

`POST https://<your-panel-domain>/panel-api/onestream/ext/line/{uuid}/renew`

## Authentication

`X-Api-Key`. `X-Auth-User` and `Authorization: Bearer` also accepted. See [Authentication](/docs/?page=panel-api-authentication).

## Required scope

`lines:write`.

## Idempotency

Optional but strongly recommended. Renewals are the classic double-charge risk: send a unique `rid` in the JSON body so a retry after a timeout returns the cached response instead of charging twice. If your billing system did not see the response, use [GET /ext/transaction/{rid}](/docs/?page=panel-api-onestream-compatibility#idempotency-via-rid) to look up the outcome before retrying.

## Path parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `uuid` | string | The opaque line UUID returned by `/ext/line/create`, `/ext/lines`, or `/ext/line/find`. |

## Request body

| Field | Type | Required | Default | Description |
| ----- | ---- | -------- | ------- | ----------- |
| `package` (or `package_id`) | int | no | line's current package | Package to renew with. Must exist. Reseller keys must have access. If omitted the caller's current package cost is not automatically re-applied, so most integrations pass it explicitly. |
| `rid` | string | no | | Idempotency key. See above. |

```json
{
  "package": 42,
  "rid": "renew-user-42-2026-08"
}
```

## Response

`200 OK` with the same compact shape as create: the opaque `line_id`, the new `expire_at` as an ISO 8601 UTC string, the `transaction_amount` debited, and the echoed `rid`.

```json
{
  "line_id": "b32c1a04-11ea-4c67-8fd1-0001000000f1",
  "expire_at": "2026-10-08T00:00:00+00:00",
  "transaction_amount": 0,
  "rid": "renew-user-42-2026-08"
}
```

## Examples

### cURL

```bash
curl -X POST "https://<your-panel-domain>/panel-api/onestream/ext/line/b32c1a04-11ea-4c67-8fd1-0001000000f1/renew" \
  -H "X-Api-Key: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "package": 42,
    "rid": "renew-user-42-2026-08"
  }'
```

### PHP raw

```php
$uuid = 'b32c1a04-11ea-4c67-8fd1-0001000000f1';
$ch = curl_init("https://<your-panel-domain>/panel-api/onestream/ext/line/{$uuid}/renew");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'X-Api-Key: <your-api-key>',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode([
        'package' => 42,
        'rid'     => 'renew-user-42-2026-08',
    ]),
]);
$body   = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
```

### Python raw

```python
import requests

uuid_ = "b32c1a04-11ea-4c67-8fd1-0001000000f1"
r = requests.post(
    f"https://<your-panel-domain>/panel-api/onestream/ext/line/{uuid_}/renew",
    headers={"X-Api-Key": "<your-api-key>"},
    json={"package": 42, "rid": "renew-user-42-2026-08"},
    timeout=30,
)
r.raise_for_status()
print(r.json())
```

## Errors

| HTTP | Error slug (or message) | When it happens | How to fix |
| ---- | ----------------------- | --------------- | ---------- |
| 401 | `Invalid API key` | Token is unknown, expired, disabled, or deleted. | Check the token or issue a new one. |
| 402 | `Insufficient credits balance` | Reseller in credits mode lacks credits for the package. | Top up credits, or pick a cheaper package. |
| 403 | `insufficient_scope` | Token lacks `lines:write`. | Issue a key with the scope. |
| 403 | `package_not_accessible` | Reseller cannot sell from that package. | Use a package inside the reseller's member group. |
| 409 | `Transaction already processed` | Same `rid` was reused with a different body. | Use a fresh `rid`, or replay with the exact original body. |
| 422 | `line_id not found or invalid` | The UUID in the URL is malformed, was not issued by this panel, or belongs to a different panel. This response is uniform on purpose so it cannot be used as an enumeration oracle. | Verify the UUID you stored on line-creation. |
| 422 | `validation_error` | Unknown `package`, trial package used for renew, missing required field in the underlying handler. | See `details.field` in the response. |
| 429 | `Rate limit exceeded` | The key hit its per-minute cap. | Back off and retry after `Retry-After` seconds. |
| 501 | `not_implemented` | A `GET` request was sent to this URL. | Only `POST` is accepted. This blocks browser prefetch and misbehaving retry loops from renewing lines by walking URLs. |

## See also

- [Create a line, POST /ext/line/create](/docs/?page=os-ref-lines-create)
- [Disable a line, POST /ext/line/{uuid}/disable](/docs/?page=os-ref-line-disable)
- [Update a line, POST /ext/line/{uuid}/update-advanced](/docs/?page=os-ref-line-update-advanced)
- [OneStream compatibility overview](/docs/?page=panel-api-onestream-compatibility)
- [Native renew endpoint](/docs/?page=xai-ref-lines-renew)
