### Issue an invoice

`POST /api/v1/billing/invoices`

Creates an invoice against a named subscriber from line items. discount and vat are percentages from 0 to 100, and the total is computed from the items — never taken from the request.

- Authentication: manager session (JWT) or API token
- Permission: `prm_billing` (Billing & invoices)
- Risk: write
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `user_id` | body | integer | yes | The subscriber being invoiced. |
| `items` | body | array | yes | At least one line, each with name, qty, unit_price and an optional per-line tax. |
| `discount` | body | number | no | Percentage from 0 to 100, not an amount. |
| `vat` | body | number | no | Percentage from 0 to 100, not an amount. |
| `due_date` | body | string | no | yyyy-MM-dd HH:mm:ss in UTC. |
| `mark_paid` | body | boolean | no | Record the new invoice as already settled. |

#### Request

```json
{
  "user_id": 4711,
  "description": "September installation",
  "discount": 0,
  "vat": 14,
  "due_date": "2026-10-01 00:00:00",
  "items": [
    { "name": "Router CPE", "qty": 1, "unit_price": 900.00, "tax": 0 },
    { "name": "Installation", "qty": 1, "unit_price": 250.00, "tax": 0 }
  ]
}
```

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_VALIDATION` | 400 | user_id missing, no items, an unnamed item, or a discount or VAT outside 0 to 100 |
| `ERR_NOT_FOUND` | 404 | the subscriber does not exist or is outside the caller's subtree |

#### Note

discount and vat are PERCENTAGES, and the validator rejects anything above 100. Sending 171.00 in the vat field to mean "171 in tax" is a 400, not a silently large invoice.

