# Users - X-Radius API

> Base URL: https://x-radius.com/api/v1
> Auth: Authorization: Bearer xrt_...  (a manager API token)
> Envelope: {"data": ...}; lists add {"meta":{page,page_size,total,has_next}}
> Errors: {"error":{"code","message","request_id"}} - branch on code, never on message
> Timestamps: yyyy-MM-dd HH:mm:ss, UTC
> Money: a bare JSON number in major units, with an ISO-4217 currency code beside it
> Idempotency: redeem and activate endpoints take a client-supplied request_id (UUID)
>
> This page: https://x-radius.com/docs/api/users
> Every group: https://x-radius.com/llms.txt

92 endpoints in 1 resource groups. 28 carry a hand-written reference entry with examples; the remaining 64 are generated from the running router and carry method, path, authentication, permission and rate-limit bucket, but no request or response example.

### List subscribers

`GET /api/v1/users`

One page of subscribers under the caller's authority. A manager without tenant-wide visibility sees only subscribers owned by their own manager subtree, and that scope is injected server-side, so a filter can narrow within it but never widen past it.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_index` (View users)
- Risk: read
- Rate limit bucket: `user_list`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `q` | query | string | no | Searches username, email, first and last name, phone, contract id, national id and static IP. A purely numeric term also matches the subscriber id exactly. |
| `filter[enabled]` | query | boolean | no | The admin suspend flag. Not the same thing as expired. |
| `filter[expired]` | query | boolean | no | Subscription window has passed. |
| `filter[online]` | query | boolean | no | An open RADIUS session exists right now. |
| `filter[profile_id]` | query | integer | no | Bound plan. |
| `filter[parent_id]` | query | integer | no | Owning manager. Ignored, not honoured, when it would widen a restricted caller's scope. |
| `filter[username_exact]` | query | string | no | Case-insensitive exact username, for a caller that already knows the identity and wants one deterministic row instead of a page of substring hits. |
| `filter[expiring_within_days]` | query | integer | no | Future expiries only. |
| `filter[created_from]` | query | string | no | yyyy-MM-dd HH:mm:ss in UTC. A malformed value is a 400, not an ignored filter. |

#### Response — 200 OK

```json
{
  "data": [
    {
      "id": 4711,
      "tenant_id": 12,
      "username": "ahmed",
      "email": "ahmed@example.test",
      "enabled": true,
      "status": {
        "enabled": true,
        "deleted": false,
        "online": true,
        "expired": false,
        "have_quota": true,
        "fup": false,
        "can_connect": true
      },
      "profile_id": 7,
      "profile_name": "Home 20M",
      "parent_id": 41,
      "parent_username": "cairo-reseller",
      "bytes_remaining": 48318382080,
      "plan_bytes_limit": 107374182400,
      "used_bytes": 59055800320,
      "expiration": "2026-10-14 00:00:00",
      "balance": 0.00,
      "debt": 0.00,
      "mac_lock_enabled": false,
      "mac_locked_addrs": [],
      "enforces_bytes": true,
      "enforces_seconds": false,
      "created_at": "2026-03-02 11:40:12"
    }
  ],
  "meta": { "page": 1, "page_size": 50, "total": 812, "has_next": true }
}
```

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_VALIDATION` | 400 | filter[created_from] is not a parseable timestamp |

#### Note

The per-subscriber scan token (lookup_token) is deliberately absent from list rows. It is served by GET /users/{id} and GET /users/resolve only, because a page of a thousand rows is a bulk harvest of codes that each unlock a subscriber record.


### Create a subscriber

`POST /api/v1/users`

Creates the account and its identity only. It does not activate a plan, take money or grant quota — binding profile_id here is metadata. Use the activation endpoint for the paid part.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_create` (Create users)
- Risk: write
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `username` | body | string | yes | Unique within the tenant, not globally. |
| `password` | body | string | yes | The RADIUS password. Stored recoverable, because CHAP and MS-CHAPv2 need it that way. |
| `parent_id` | body | integer | no | Owning manager. Defaults to the caller. A subscriber is never parent-less, and a named owner must be a manager the caller may act on. |
| `profile_id` | body | integer | no | Plan to bind. Must be one the acting manager may actually assign. |
| `expiration` | body | string | no | yyyy-MM-dd HH:mm:ss in UTC. |
| `debt_limit` | body | number | no | Credit ceiling. Omitted or null is unlimited, 0 is no credit. |
| `portal_password` | body | string | no | Optional portal-only password, hashed before storage and distinct from the RADIUS password above. |
| `custom_fields` | body | object | no | Tenant-defined form values, validated against the user form schema. |

#### Request

```json
{
  "username": "ahmed",
  "password": "s3cret-pass",
  "profile_id": 7,
  "parent_id": 41,
  "email": "ahmed@example.test",
  "phone": "+201000000000",
  "enabled": true,
  "debt_limit": 0
}
```

#### Response — 201 Created

```json
{
  "data": {
    "id": 4712,
    "tenant_id": 12,
    "username": "ahmed",
    "enabled": true,
    "profile_id": 7,
    "parent_id": 41,
    "balance": 0.00,
    "debt": 0.00,
    "created_at": "2026-09-20 09:14:55"
  }
}
```

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_VALIDATION` | 400 | username or password missing, bad expiration, unassignable or inactive plan, unknown parent or group |
| `ERR_CONFLICT` | 409 | the username is taken in this tenant, or the licence subscriber cap is reached |
| `ERR_LICENSE_BLOCKED` | 403 | the tenant's licence has expired |

#### Note

debt_limit is tri-state and the two falsy values mean opposite things. Omitted or null is unlimited credit; 0 is no credit at all. Sending 0 to mean "no opinion" silently blocks every future debit.


### Extend subscription

`POST /api/v1/users/bulk-add-days`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_extend` (Extend subscription)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Extend subscription

`POST /api/v1/users/bulk-add-hours`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_extend` (Extend subscription)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Add traffic

`POST /api/v1/users/bulk-add-traffic`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_add_traffic` (Add traffic)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Delete users

`POST /api/v1/users/bulk-delete`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_delete` (Delete users)
- Risk: danger
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Deposit to user wallet

`POST /api/v1/users/bulk-deposit`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_deposit` (Deposit to user wallet)
- Risk: danger
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Deposit to user wallet

`POST /api/v1/users/bulk-deposit/preview`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_deposit` (Deposit to user wallet)
- Risk: danger
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Suspend many subscribers

`POST /api/v1/users/bulk-disable`

Takes an array of ids and suspends each. One request against the per-tenant write budget instead of one per subscriber, which is the difference between fitting in a minute and not.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_enable_disable` (Enable / suspend users)
- Risk: write
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `ids` | body | array | yes | Subscriber ids. |

#### Request

```json
{
  "ids": [4711, 4712, 4713]
}
```

#### Response — 200 OK

```json
{
  "data": {
    "updated": [4711, 4713],
    "failed": [
      { "id": 4712, "reason": "not_authorized", "message": "You may not act on this record." }
    ]
  }
}
```

#### Note

A bulk call reports per-id outcomes and returns 200 even when some ids failed. failed is always present, as an array, and each entry carries a stable reason (not_found, not_authorized, error) alongside a localized message. Branch on reason; treating the 200 as "all of them worked" is the mistake this shape exists to prevent.


### Live traffic / disconnect

`POST /api/v1/users/bulk-disconnect`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_live_traffic` (Live traffic / disconnect)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Enable / suspend users

`POST /api/v1/users/bulk-enable`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_enable_disable` (Enable / suspend users)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### POST /api/v1/users/bulk-extend

`POST /api/v1/users/bulk-extend`

- Authentication: manager session (JWT) or API token
- Permission: any one of `prm_users_activate`, `prm_users_extend`
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### POST /api/v1/users/bulk-extend/preview

`POST /api/v1/users/bulk-extend/preview`

- Authentication: manager session (JWT) or API token
- Permission: any one of `prm_users_activate`, `prm_users_extend`
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Withdraw / settle user debt

`POST /api/v1/users/bulk-reset-balance`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_withdrawal` (Withdraw / settle user debt)
- Risk: danger
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Reset user quota

`POST /api/v1/users/bulk-reset-quota`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_reset_quota` (Reset user quota)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Extend subscription

`POST /api/v1/users/bulk-set-expiration`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_extend` (Extend subscription)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Assign groups

`POST /api/v1/users/bulk-set-group`

- Authentication: manager session (JWT) or API token
- Permission: `prm_groups_assign` (Assign groups)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Change user parent

`POST /api/v1/users/bulk-set-parent`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_change_parent` (Change user parent)
- Risk: danger
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Change user profile

`POST /api/v1/users/bulk-set-profile`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_change_profile` (Change user profile)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Withdraw / settle user debt

`POST /api/v1/users/bulk-withdraw`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_withdrawal` (Withdraw / settle user debt)
- Risk: danger
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Withdraw / settle user debt

`POST /api/v1/users/bulk-withdraw/preview`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_withdrawal` (Withdraw / settle user debt)
- Risk: danger
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Create users

`GET /api/v1/users/owner-context`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_create` (Create users)
- Risk: write

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Resolve a subscriber by scan code

`GET /api/v1/users/resolve`

Exchanges the opaque per-subscriber scan token, the one encoded in a printed QR code, for the subscriber record. Scoped to the caller's subtree exactly like a direct fetch.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_index` (View users)
- Risk: read
- Rate limit bucket: `user_resolve`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `code` | query | string | yes | The scan token. The QR payload is prefixed with xr:u: — strip the prefix before sending. |

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_VALIDATION` | 400 | code is missing or blank |
| `ERR_NOT_FOUND` | 404 | no subscriber carries that token, or they are outside the caller's subtree |


### Count subscribers by state

`GET /api/v1/users/summary`

Fleet-health counters over exactly the same authority scope as the list, so a count and the rows behind it always agree.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_index` (View users)
- Risk: read

#### Response — 200 OK

```json
{
  "data": {
    "total": 812,
    "active": 640,
    "disabled": 44,
    "online": 318,
    "expired": 128
  }
}
```

#### Note

active means enabled AND not expired, not the bare enabled flag. total is every non-deleted subscriber, so the four other counters do not sum to it.


### Delete a subscriber

`DELETE /api/v1/users/{id}`

Soft-deletes the account and frees its username slot by renaming the row. The subscriber's live portal sessions are ended.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_delete` (Delete users)
- Risk: danger
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_VALIDATION` | 422 | the wallet balance is not zero — settle or withdraw it first |
| `ERR_NOT_FOUND` | 404 | no such subscriber, or one outside the caller's subtree |


### Fetch one subscriber

`GET /api/v1/users/{id}`

The full record, including the computed status object and the fields the list projection leaves out.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_index` (View users)
- Risk: read

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |

#### Response — 200 OK

```json
{
  "data": {
    "id": 4711,
    "tenant_id": 12,
    "username": "ahmed",
    "enabled": true,
    "status": { "online": true, "expired": false, "have_quota": true, "can_connect": true },
    "profile_id": 7,
    "fup_profile_id": null,
    "bytes_remaining": 48318382080,
    "seconds_remaining": null,
    "expiration": "2026-10-14 00:00:00",
    "balance": 0.00,
    "debt": 0.00,
    "debt_limit": null,
    "mac_lock_enabled": false,
    "mac_locked_addrs": [],
    "simultaneous_sessions": 1,
    "allowed_services": "both",
    "lookup_token": "n8Qk2vRa1pLd7WxYzB0cEf",
    "custom_fields": {},
    "created_at": "2026-03-02 11:40:12"
  }
}
```

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_NOT_FOUND` | 404 | no such subscriber, or one outside the caller's manager subtree |

#### Note

A subscriber that exists but sits outside your subtree returns the same 404 as one that does not exist, byte for byte including details.reason. That is deliberate: a 403 would confirm the id and make the id space enumerable.


### Update a subscriber

`PATCH /api/v1/users/{id}`

Partial update. An omitted field is left alone. Three fields are separately permission-gated and are dropped silently rather than refused when the caller lacks the right: parent_id, profile_id and expiration.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_update` (Edit users)
- Risk: write
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |
| `form_name` | body | string | no | Which custom-field schema to validate custom_fields against. Defaults to the user form. |

#### Request

```json
{
  "email": "new@example.test",
  "enabled": false,
  "mac_lock_enabled": true,
  "mac_locked_addrs": ["AA:BB:CC:DD:EE:FF"],
  "simultaneous_sessions": 2
}
```

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_VALIDATION` | 400 | bad expiration, unassignable or inactive plan, invalid allowed_services, a value Postgres itself refuses |
| `ERR_NOT_FOUND` | 404 | no such subscriber, or one outside the caller's subtree |

#### Note

A stored mac_locked_addrs list is inert until mac_lock_enabled is true. Writing the list alone locks nothing, which reads on screen like the lock silently failed. Set both.


### Un-suspend a subscriber

`POST /api/v1/users/{id}/activate`

Sets enabled back to true. This is the inverse of suspend and has nothing to do with activating a plan — that is the activation endpoint, which charges money.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_enable_disable` (Enable / suspend users)
- Risk: write
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_NOT_FOUND` | 404 | no such subscriber, or one outside the caller's subtree |

#### Note

The name is a historical trap. This endpoint lifts a suspension and takes no money. POST /users/{id}/activation is the one that prices, charges and provisions a plan.


### Charge and provision an activation

`POST /api/v1/users/{id}/activation`

Prices, charges and provisions in one operation. Funds are reserved first, then the plan is applied, and both halves are keyed off your request_id, so a retry after a crash re-runs neither the charge nor the grant.

- Authentication: manager session (JWT) or API token
- Permission: any one of `prm_users_activate`, `prm_users_extend`
- Rate limit bucket: `t_mutate`
- Idempotent on `request_id`: retrying with the same id returns the original result

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |
| `request_id` | body | string | yes | Idempotency key, up to 255 characters, no colon or pipe. Reuse the same one on every retry of one intent. |
| `money_collected` | body | boolean | no | Defaults to true. |
| `card_code` | body | string | no | Required when method is card. The voucher must be in the caller's subtree. |
| `issue_invoice` | body | boolean | no | Also record an invoice document. Skipped on a replay so a retry never double-issues. |

#### Request

```json
{
  "profile_id": 7,
  "operation": "activate",
  "method": "manager_balance",
  "units": 1,
  "money_collected": true,
  "request_id": "0f3b9d4e-9a41-4c83-9b1e-2b6a0f1d88c2"
}
```

#### Response — 200 OK

```json
{
  "data": {
    "quote": { "profile_id": 7, "required_amount": 171.00, "new_expiration": "2026-10-14 00:00:00" },
    "activation": {
      "UserID": 4711,
      "ProfileID": 7,
      "NewSubscriptionEnd": "2026-10-14T00:00:00Z",
      "BytesRemaining": 107374182400,
      "SecondsRemaining": -1,
      "PointsAwarded": 10,
      "QuotaCarried": false,
      "Replay": false
    },
    "manager_balance": 4029.00,
    "replay": false
  }
}
```

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_VALIDATION` | 400 | request_id missing, too long, or in a reserved namespace |
| `ERR_CONFLICT` | 409 | the request_id is already bound to an activation of a different subscriber |
| `ERR_FORBIDDEN` | 403 | the body's operation needs a permission the caller does not hold |

#### Note

The nested activation object is serialised with Go field names (UserID, NewSubscriptionEnd) and RFC3339 timestamps, unlike every other object on this API. Everything around it uses snake_case and yyyy-MM-dd HH:mm:ss. Do not write a parser that assumes one convention.


### Price an activation or extension

`POST /api/v1/users/{id}/activation-quote`

Read-only preview of what an activation would cost and grant: unit price, VAT, the computed new expiration, the plan's quota, both wallets, reward points and whether the chosen method can cover it. Writes nothing, so it is safe to call on every keystroke of a form.

- Authentication: manager session (JWT) or API token
- Permission: any one of `prm_users_activate`, `prm_users_extend`
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |
| `profile_id` | body | integer | no | Plan to price. Defaults to the subscriber's current plan. |
| `operation` | body | enum | no | activate or extend. Defaults to activate. |
| `method` | body | enum | no | manager_balance, user_balance, reward_points or card. |
| `units` | body | integer | no | How many periods to buy. |
| `user_price` | body | number | no | Retail price override. Silently ignored for a caller without the price-override permission, which then falls back to the plan or pricing-tree price. |

#### Request

```json
{
  "profile_id": 7,
  "operation": "activate",
  "method": "manager_balance",
  "units": 1
}
```

#### Response — 200 OK

```json
{
  "data": {
    "user_id": 4711,
    "username": "ahmed",
    "profile_id": 7,
    "profile_name": "Home 20M",
    "operation": "activate",
    "method": "manager_balance",
    "units": 1,
    "unit_price": 150.00,
    "end_user_price": 180.00,
    "vat_percent": 14.00,
    "vat_amount": 21.00,
    "required_amount": 171.00,
    "price_overridden": false,
    "duration_value": 30,
    "duration_unit": "days",
    "total_bytes": 107374182400,
    "uptime_seconds": -1,
    "old_expiration": "2026-09-14 00:00:00",
    "new_expiration": "2026-10-14 00:00:00",
    "manager_balance": 4200.00,
    "manager_available": 4200.00,
    "user_balance": 0.00,
    "points_awarded": 10,
    "can_afford": true,
    "warnings": [],
    "debitable": false
  }
}
```

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_FORBIDDEN` | 403 | the body's operation needs the extend permission and the caller only holds activate, or the reverse |

#### Note

A quota figure of -1 means unlimited, and 0 means none. They are not interchangeable and the difference is load-bearing on the RADIUS gate. Read uptime_seconds -1 above as "no time cap on this plan", not "no time left".


### Grant quota directly

`POST /api/v1/users/{id}/add-traffic`

Adds bytes and/or seconds to the subscriber's remaining period quota without pricing or charging anything, and drops the hot-path quota cache so the grant applies on the next RADIUS request.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_add_traffic` (Add traffic)
- Risk: write
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |
| `bytes` | body | integer | no | Bytes to add. At least one of bytes or seconds must be present and positive. |
| `seconds` | body | integer | no | Seconds to add. |

#### Request

```json
{
  "bytes": 10737418240,
  "request_id": "7c2a0e91-6d44-4f9b-a1b7-0c5d3e8f2a10"
}
```

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_VALIDATION` | 400 | a negative value, or neither bytes nor seconds supplied |
| `ERR_NOT_FOUND` | 404 | no such subscriber |

#### Note

request_id is accepted here for your own tracing but is not enforced, because adding traffic is not naturally idempotent. Two calls add twice. Do not build a retry loop on this endpoint the way you would on deposit.


### Sell add-ons

`POST /api/v1/users/{id}/addon-quote`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_addon` (Sell add-ons)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Sell add-ons

`POST /api/v1/users/{id}/addons`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_addon` (Sell add-ons)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Approve pending signups

`POST /api/v1/users/{id}/approve`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_approve_signup` (Approve pending signups)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Edit users

`POST /api/v1/users/{id}/assign-profile`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_update` (Edit users)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Set the bucket drain order

`PUT /api/v1/users/{id}/bucket-order`

Replaces the subscriber's custom drain order. Every id must be one of their active buckets, except the sentinel 0, which stands for the plan's own main quota and may be placed anywhere in the sequence.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_buckets` (Manage quota buckets)
- Risk: write
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |
| `order` | body | array | yes | Bucket ids in drain order. 0 is the main quota, not a missing value. |

#### Request

```json
{
  "order": [331, 0, 402]
}
```

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_VALIDATION` | 400 | an id is not one of this subscriber's active buckets, or the array is too long |

#### Note

The 0 in that array is meaningful and positional. It is the main plan quota, so stripping non-positive ids as "empty" would silently reorder the drain sequence rather than reject it.


### Cancel service

`POST /api/v1/users/{id}/cancel-service`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_cancel` (Cancel service)
- Risk: danger
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Move a subscriber to another owner

`POST /api/v1/users/{id}/change-parent`

Re-points the subscriber at a different owning manager. The destination must be a manager the caller may act on, because the owner is both the creditor on a debt and the revenue attribution. Naturally idempotent, so request_id is accepted for your own tracing but is not enforced.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_change_parent` (Change user parent)
- Risk: danger
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |
| `parent_id` | body | integer | yes | The new owning manager. |

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_VALIDATION` | 400 | parent_id missing or not a manager in this tenant |
| `ERR_NOT_FOUND` | 404 | the subscriber or the destination manager is outside the caller's subtree |


### Change user profile

`POST /api/v1/users/{id}/change-profile`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_change_profile` (Change user profile)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Compensate users

`POST /api/v1/users/{id}/compensate`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_compensate` (Compensate users)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Approve/reject compensation requests

`POST /api/v1/users/{id}/compensations/{reqId}/approve`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_compensate_approve` (Approve/reject compensation requests)
- Risk: danger
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Approve/reject compensation requests

`POST /api/v1/users/{id}/compensations/{reqId}/reject`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_compensate_approve` (Approve/reject compensation requests)
- Risk: danger
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Remote access to user's CPE (router admin UI)

`POST /api/v1/users/{id}/cpe-console`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_remote` (Remote access to user's CPE (router admin UI))
- Risk: danger
- Rate limit bucket: `t_probe`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Read a subscriber's debt

`GET /api/v1/users/{id}/debt`

Total outstanding debt across all creditors, plus the slice of it owed to the calling manager specifically, read live off the loan ledger.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_index` (View users)
- Risk: read

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |

#### Response — 200 OK

```json
{
  "data": { "total": 340.00, "owed_to_me": 120.00 }
}
```


### Credit a subscriber wallet

`POST /api/v1/users/{id}/deposit`

Moves money into the subscriber's wallet and writes the matching ledger line. Idempotent on request_id through a unique constraint on the ledger itself, so the charge and the record of it cannot disagree.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_deposit` (Deposit to user wallet)
- Risk: danger
- Rate limit bucket: `t_mutate`
- Idempotent on `request_id`: retrying with the same id returns the original result

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |
| `amount` | body | number | yes | Major units, a bare JSON number. 150.50, not "150.50" and not 15050. |
| `request_id` | body | string | yes | Idempotency key. |
| `issue_invoice` | body | boolean | no | Also record an invoice document. Skipped on a replay. |

#### Request

```json
{
  "amount": 250.00,
  "request_id": "b41cf0a2-33de-4b57-8a7a-9e3c5d1f0c77"
}
```

#### Response — 200 OK

```json
{
  "data": {
    "journal_id": 90412,
    "balance": 250.00,
    "debt": 0.00,
    "replay": false
  }
}
```

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_VALIDATION` | 400 | request_id missing, too long or reserved |
| `ERR_CONFLICT` | 409 | the request_id is already held by a different ledger line |

#### Note

replay true means this exact request already happened and nothing moved this time. It is a success, and the balance shown is the one from the original operation.


### Manage user documents

`GET /api/v1/users/{id}/documents`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_documents` (Manage user documents)
- Risk: write

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Manage user documents

`POST /api/v1/users/{id}/documents`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_documents` (Manage user documents)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Manage user documents

`POST /api/v1/users/{id}/documents/bulk-delete`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_documents` (Manage user documents)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Manage user documents

`DELETE /api/v1/users/{id}/documents/{docId}`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_documents` (Manage user documents)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Manage user documents

`PATCH /api/v1/users/{id}/documents/{docId}`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_documents` (Manage user documents)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Manage user documents

`GET /api/v1/users/{id}/documents/{docId}/download`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_documents` (Manage user documents)
- Risk: write

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### View free-zone traffic

`POST /api/v1/users/{id}/free-zone-traffic`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_freezone_traffic` (View free-zone traffic)
- Risk: read
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### View free-zone traffic

`GET /api/v1/users/{id}/free-zones`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_freezone_traffic` (View free-zone traffic)
- Risk: read

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### View usage history

`GET /api/v1/users/{id}/history`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_history` (View usage history)
- Risk: read

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### List one subscriber's invoices

`GET /api/v1/users/{id}/invoices`

Invoices belonging to a single subscriber, newest first.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_invoices_view` (View user invoices)
- Risk: read

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |


### Create an invoice for a subscriber

`POST /api/v1/users/{id}/invoices`

The per-subscriber form of issuing an invoice. Same validation and the same server-computed total; the subscriber comes from the path rather than the body.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_invoices_create` (Create user invoices)
- Risk: write
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |
| `items` | body | array | yes | At least one line item. |


### Edit / pay user invoices

`POST /api/v1/users/{id}/invoices/bulk-pay`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_invoices_edit` (Edit / pay user invoices)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Edit / pay user invoices

`POST /api/v1/users/{id}/invoices/bulk-unpay`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_invoices_edit` (Edit / pay user invoices)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Delete an invoice

`DELETE /api/v1/users/{id}/invoices/{invoiceId}`

Deletes a hand-created invoice. Auto-issued documents cannot be deleted here, for the same reason they cannot be edited.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_invoices_delete` (Delete user invoices)
- Risk: danger
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |
| `invoiceId` | path | integer | yes | Invoice id. |


### Fetch one invoice

`GET /api/v1/users/{id}/invoices/{invoiceId}`

The invoice with its line items.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_invoices_view` (View user invoices)
- Risk: read

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |
| `invoiceId` | path | integer | yes | Invoice id. |


### Edit an invoice

`PATCH /api/v1/users/{id}/invoices/{invoiceId}`

Edits a hand-created invoice. Auto-issued documents — activations, extensions, add-ons, deposits and withdrawals — are not editable through this path.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_invoices_edit` (Edit / pay user invoices)
- Risk: write
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |
| `invoiceId` | path | integer | yes | Invoice id. |


### Mark an invoice paid

`POST /api/v1/users/{id}/invoices/{invoiceId}/pay`

Records the invoice as settled and stamps paid_on. This is bookkeeping: it does not move money between wallets.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_invoices_edit` (Edit / pay user invoices)
- Risk: write
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |
| `invoiceId` | path | integer | yes | Invoice id. |

#### Note

Marking an invoice paid and taking a payment are different operations. This one changes a status; a deposit changes a balance. Doing one does not do the other.


### View user invoices

`GET /api/v1/users/{id}/invoices/{invoiceId}/render-data`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_invoices_view` (View user invoices)
- Risk: read

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Mark an invoice unpaid

`POST /api/v1/users/{id}/invoices/{invoiceId}/unpay`

Reverses the paid stamp. Again, bookkeeping only.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_invoices_edit` (Edit / pay user invoices)
- Risk: write
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |
| `invoiceId` | path | integer | yes | Invoice id. |


### View users

`GET /api/v1/users/{id}/journal`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_index` (View users)
- Risk: read

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Log in as subscriber

`POST /api/v1/users/{id}/login-as`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_login_as` (Log in as subscriber)
- Risk: danger
- Rate limit bucket: `user_login_as`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Edit users

`POST /api/v1/users/{id}/lookup-token/rotate`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_update` (Edit users)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Manage parental controls

`GET /api/v1/users/{id}/parental`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_parental` (Manage parental controls)
- Risk: write

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Manage parental controls

`POST /api/v1/users/{id}/parental/reset-pin`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_parental` (Manage parental controls)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Withdraw / settle user debt

`POST /api/v1/users/{id}/pay-debt`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_withdrawal` (Withdraw / settle user debt)
- Risk: danger
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### View users

`GET /api/v1/users/{id}/payments`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_index` (View users)
- Risk: read

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### List a subscriber's quota buckets

`GET /api/v1/users/{id}/quota-buckets`

Separately-metered grants layered on top of the plan's own quota — add-ons, extensions, card top-ups — in the order they will be drained.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_index` (View users)
- Risk: read

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |

#### Response — 200 OK

```json
{
  "data": [
    {
      "id": 331,
      "source": "addon",
      "label": "Night 50GB",
      "priority": 10,
      "bytes_granted": 53687091200,
      "bytes_remaining": 41231686144,
      "seconds_granted": 0,
      "seconds_remaining": 0,
      "activated_at": "2026-09-12 20:04:01",
      "valid_until": "2026-10-12 20:04:01",
      "works_when_expired": false,
      "status": "active"
    }
  ],
  "meta": { "page": 1, "page_size": 1, "total": 1, "has_next": false }
}
```

#### Note

Un-paginated: the whole list is page 1 and has_next is always false. Do not write a paging loop against it. A subscriber may hold at most 50 active buckets, and a grant that would exceed the cap is refused before any money moves.


### Manage quota buckets

`POST /api/v1/users/{id}/quota-buckets/{bid}/cancel`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_buckets` (Manage quota buckets)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### View usage history

`GET /api/v1/users/{id}/quota-history`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_history` (View usage history)
- Risk: read

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Manage user RADIUS attributes

`GET /api/v1/users/{id}/radius-attrs`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_radius_attributes` (Manage user RADIUS attributes)
- Risk: write

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Manage user RADIUS attributes

`POST /api/v1/users/{id}/radius-attrs`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_radius_attributes` (Manage user RADIUS attributes)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Manage user RADIUS attributes

`POST /api/v1/users/{id}/radius-attrs/bulk-delete`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_radius_attributes` (Manage user RADIUS attributes)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Manage user RADIUS attributes

`DELETE /api/v1/users/{id}/radius-attrs/{attrId}`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_radius_attributes` (Manage user RADIUS attributes)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Manage user RADIUS attributes

`PATCH /api/v1/users/{id}/radius-attrs/{attrId}`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_radius_attributes` (Manage user RADIUS attributes)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### View users

`GET /api/v1/users/{id}/receipts`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_index` (View users)
- Risk: read

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Redeem reward points

`POST /api/v1/users/{id}/redeem-points`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_reward_points` (Redeem reward points)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Refund last activation

`POST /api/v1/users/{id}/refund-activation`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_refund` (Refund last activation)
- Risk: danger
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Rename users

`POST /api/v1/users/{id}/rename`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_rename` (Rename users)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Edit users

`POST /api/v1/users/{id}/reset-password`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_update` (Edit users)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Reset quota counters

`POST /api/v1/users/{id}/reset-quota`

Always zeroes the daily counters. With clear_period true it also nulls the period quota, which is the destructive form. Lifts a daily FUP throttle and kicks the live session so full speed returns immediately. An empty body is accepted and means clear_period false.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_reset_quota` (Reset user quota)
- Risk: write
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |
| `clear_period` | body | boolean | no | Also null the period quota counters. Defaults to false. |
| `reason` | body | string | no | Free-text note recorded on the quota-history audit row. |

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_NOT_FOUND` | 404 | no such subscriber |


### List a subscriber's session history

`GET /api/v1/users/{id}/sessions`

Accounting history for this subscriber, newest first, paginated with the standard list parameters.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_sessions_index` (View user sessions)
- Risk: read

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |
| `filter[status]` | query | string | no | Narrows to open or closed sessions. |


### Edit users

`DELETE /api/v1/users/{id}/speed`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_update` (Edit users)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### View users

`GET /api/v1/users/{id}/speed`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_index` (View users)
- Risk: read

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Edit users

`PUT /api/v1/users/{id}/speed`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_update` (Edit users)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Suspend a subscriber

`POST /api/v1/users/{id}/suspend`

Sets enabled to false and, when the tenant has disconnect-on-update enabled, kicks any live session so the suspension takes effect now rather than at the next re-auth.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_enable_disable` (Enable / suspend users)
- Risk: write
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |

#### Response — 200 OK

```json
{
  "data": {
    "id": 4711,
    "username": "ahmed",
    "enabled": false,
    "status": { "enabled": false, "can_connect": false }
  }
}
```

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_NOT_FOUND` | 404 | no such subscriber, or one outside the caller's subtree |


### View usage history

`GET /api/v1/users/{id}/usage-history`

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_history` (View usage history)
- Risk: read

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Debit a subscriber wallet

`POST /api/v1/users/{id}/withdraw`

The inverse of deposit. Refuses to take the balance below zero unless allow_negative is set.

- Authentication: manager session (JWT) or API token
- Permission: `prm_users_withdrawal` (Withdraw / settle user debt)
- Risk: danger
- Rate limit bucket: `t_mutate`
- Idempotent on `request_id`: retrying with the same id returns the original result

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Subscriber id. |
| `amount` | body | number | yes | Major units. |
| `request_id` | body | string | yes | Idempotency key. |
| `allow_negative` | body | boolean | no | Permit the withdrawal to drive the balance below zero. |

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_CONFLICT` | 409 | the request_id is already held by a different ledger line |


