### 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".

