### List plans

`GET /api/v1/profiles`

One page of service plans, scoped to what the calling manager may actually see. A private plan is visible only to managers it was explicitly allow-listed for.

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

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `filter[type]` | query | enum | no | prepaid, postpaid, fup, extension or addon. |
| `filter[status]` | query | string | no | Plan status. |
| `filter[assignable]` | query | boolean | no | Only plans this caller may bind to a subscriber. Narrower than visibility, and the right filter behind a plan picker. |
| `filter[allowed_services]` | query | enum | no | ppp, hotspot or both. |
| `filter[show_in_ucp]` | query | boolean | no | Plans the subscriber portal offers for self-service renewal. |
| `filter[private]` | query | boolean | no | Plans restricted to an explicit manager allow-list. |
| `filter[has_fup_chain]` | query | boolean | no | Plans that fall back to another plan on expiry or quota exhaustion. |
| `sort` | query | string | no | id, created_at, name, type or status. Defaults to name ascending. |

#### Response — 200 OK

```json
{
  "data": [
    {
      "id": 7,
      "tenant_id": 12,
      "name": "Home 20M",
      "type": "prepaid",
      "base_fee": 150.00,
      "end_user_price": 180.00,
      "vat_percent": "14.00",
      "enforce_expiration": true,
      "expiration_value": 30,
      "expiration_unit": "days",
      "combined_bytes_limit": 107374182400,
      "enforce_combined_bytes": true,
      "enforce_period_seconds": false,
      "download_kbps": 20480,
      "upload_kbps": 4096,
      "speed_share_mode": "per_session",
      "allowed_services": "both",
      "private": false,
      "show_in_ucp": true,
      "carry_over_remaining_quota": false,
      "auto_renew_supported": true,
      "points_award": 10
    }
  ],
  "meta": { "page": 1, "page_size": 50, "total": 18, "has_next": false }
}
```

#### Note

vat_percent is a STRING, not a number. It is a NUMERIC(5,2) in the database and is serialised as text so the exact value survives the round trip. Every other money field on the plan is a bare JSON number.

