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

