---
title: "Edit a line, action=edit_line"
description: "Xtream Codes compatibility partial update for a subscriber line. Only fields the native update handler accepts are honored; sending only unsupported fields returns STATUS_INVALID_DATA instead of failing silently."
---

# Edit 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-update) 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=edit_line` updates a subset of a subscriber line's fields and returns the full re-read Line object. The request is translated to the native [`POST /panel-api/v1/lines/{id}/update`](/docs/?page=xai-ref-lines-update) and rewrapped in the classic `{"status": "STATUS_SUCCESS", "data": {...}}` envelope.

The native update handler only writes a fixed set of fields: `password`, `exp_date`, `max_connections`, `is_restreamer`, `enabled`, `admin_enabled` (admin key only), `allowed_ips`, `allowed_ua`. Classic XC panels accepted many more fields (`bouquets_selected[]`, `username`, `notes`, `is_trial`, ...) and silently dropped anything the underlying handler did not process. The compat layer keeps that fidelity: it forwards **only** the supported fields to the native handler. Fields the native handler does not accept are ignored, with one important protection.

If **every** field in the request is unsupported (for example, only `bouquets_selected[]`), the compat layer returns `STATUS_INVALID_DATA` up front with an explicit message listing the supported fields, so a call that "changes nothing" is loud instead of silent. But if the request **mixes** supported and unsupported fields, the supported ones are applied and the rest are dropped without warning. Send only fields you know are supported.

Bouquets, username, notes, and is_trial cannot be changed through this endpoint (or through the native update endpoint). The current workaround for changing bouquets is delete + recreate.

## Endpoint

`POST https://<your-panel-domain>/panel-api/xc/{accesscode}/admin/index.php?action=edit_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. Pass `rid=<unique-per-operation>` to opt in. Same `rid` with the 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

The XC dialect posts form-encoded (`application/x-www-form-urlencoded`). Booleans coerce loosely from strings (see [Boolean coercion](/docs/?page=panel-api-xtream-codes-compatibility#boolean-coercion)).

| Field | Type | Required | Default | Description |
| ----- | ---- | -------- | ------- | ----------- |
| `id` | int | yes | | The line to update. |
| `password` | string | no | | New stream password. Reseller keys whose member group has `allow_change_pass=0` cannot set this. |
| `exp_date` | int (UTC epoch) | no | | New expiry. On admin keys it may be `null` to make the line perpetual; the XC dialect cannot express `null` because everything is a string, so use the [native update endpoint](/docs/?page=xai-ref-lines-update) for that specific case. |
| `max_connections` | int | no | | Concurrent-connection cap. Clamped to `[1, 100]`. |
| `is_restreamer` | bool | no | | Whether the line may restream through the panel. |
| `enabled` | bool | no | | Reseller-visible toggle. The dedicated actions [`enable_line`](/docs/?page=xc-ref-lines-enable) and [`disable_line`](/docs/?page=xc-ref-lines-disable) are equivalent shortcuts. |
| `admin_enabled` | bool | admin only | | Admin override. If set to `false`, the line is blocked no matter what `enabled` says. |
| `allowed_ips[]` | string[] | no | | IPv4 allow-list, up to 50 entries. Invalid entries drop silently. |
| `allowed_ua[]` | string[] | no | | User-Agent allow-list, up to 50 entries capped at 500 chars each. |
| `rid` | string | no | | Idempotency identifier. |

At least one editable field is required. A request that sends only fields the native handler ignores (for example, `bouquets_selected[]`, `username`, `notes`, `is_trial`) returns `STATUS_INVALID_DATA` with the message `edit_line: none of the provided fields is editable by this API. Supported: password, exp_date, max_connections, is_restreamer, enabled, allowed_ips, allowed_ua.`

## Response

`data` is the full Line object, re-read from the database after the update. Same shape used by [`action=get_line`](/docs/?page=xc-ref-lines-get).

```json
{
  "status": "STATUS_SUCCESS",
  "data": {
    "id": 172511994,
    "username": "u_a1b2c3d4",
    "password": "Sup3rSecret1",
    "member_id": 100,
    "exp_date": 1817743138,
    "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=edit_line" \
  -d "id=172511994" \
  -d "max_connections=3" \
  -d "enabled=true" \
  -d "rid=edit-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' => 'edit_line']);
$body = http_build_query([
    'id'              => 172511994,
    'max_connections' => 3,
    'enabled'         => 'true',
    'rid'             => 'edit-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'] ?? 'edit_line failed');
}
$line = $resp['data'];
```

### 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": "edit_line"},
    data={
        "id":              172511994,
        "max_connections": 3,
        "enabled":         "true",
        "rid":             f"edit-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", "edit_line failed"))
line = body["data"]
```

## 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 or empty; or the request sent only fields the native handler ignores (message names the supported set). | Send `id` plus at least one supported field. |
| `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` | `admin_only_field` | A reseller key sent `admin_enabled`. | Remove `admin_enabled`, or issue an admin key for this integration. |
| `STATUS_NO_PERMISSIONS` | `insufficient_scope` | The key does not have `lines:write`. | Grant the scope, or issue a new key. |
| `STATUS_NO_PERMISSIONS` | `password_change_not_allowed` | Reseller group has `allow_change_pass=0` and the request set `password`. | Omit `password`. |
| `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 on another request. | 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

- [Get a line, action=get_line](/docs/?page=xc-ref-lines-get)
- [Extend a line, action=extend_line](/docs/?page=xc-ref-lines-extend)
- [Enable a line, action=enable_line](/docs/?page=xc-ref-lines-enable)
- [Disable a line, action=disable_line](/docs/?page=xc-ref-lines-disable)
- [Xtream Codes / XUI.one / OTT Panel compatibility](/docs/?page=panel-api-xtream-codes-compatibility)
- [Native update endpoint, POST /lines/{id}/update](/docs/?page=xai-ref-lines-update)
