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

