# Profiles - X-Radius API

> Base URL: https://x-radius.com/api/v1
> Auth: Authorization: Bearer xrt_...  (a manager API token)
> Envelope: {"data": ...}; lists add {"meta":{page,page_size,total,has_next}}
> Errors: {"error":{"code","message","request_id"}} - branch on code, never on message
> Timestamps: yyyy-MM-dd HH:mm:ss, UTC
> Money: a bare JSON number in major units, with an ISO-4217 currency code beside it
> Idempotency: redeem and activate endpoints take a client-supplied request_id (UUID)
>
> This page: https://x-radius.com/docs/api/profiles
> Every group: https://x-radius.com/llms.txt

26 endpoints in 1 resource groups. 13 carry a hand-written reference entry with examples; the remaining 13 are generated from the running router and carry method, path, authentication, permission and rate-limit bucket, but no request or response example.

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


### Create a plan

`POST /api/v1/profiles`

name and type are the only required fields; everything else has a schema default. A plan created with no enforce toggles on is an unlimited plan.

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_create` (Create profiles)
- Risk: write
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `name` | body | string | yes | Display name. |
| `type` | body | enum | yes | prepaid, postpaid, fup, extension or addon. |
| `base_fee` | body | number | no | Wholesale price to the reseller, major units. |
| `end_user_price` | body | number | no | Retail price, major units. |
| `expiration_value` | body | integer | no | Period length, paired with expiration_unit. |
| `expiration_unit` | body | enum | no | hours, days or months. |

#### Request

```json
{
  "name": "Home 20M",
  "type": "prepaid",
  "base_fee": 150.00,
  "end_user_price": 180.00,
  "enforce_expiration": true,
  "expiration_value": 30,
  "expiration_unit": "days",
  "enforce_combined_bytes": true,
  "combined_bytes_limit": 107374182400,
  "download_kbps": 20480,
  "upload_kbps": 4096
}
```

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_VALIDATION` | 400 | name or type missing, or an out-of-range value |

#### Note

A quota limit does nothing until its enforce toggle is on. Setting combined_bytes_limit while enforce_combined_bytes is false leaves the plan unlimited on data, and the number sits there looking like a cap. The two always travel together.


### List plans for a picker

`GET /api/v1/profiles/all`

Id and name only, un-paginated, for populating a select control. Cheaper than the full list when you do not need the plan bodies.

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


### Delete profiles

`POST /api/v1/profiles/bulk-delete`

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_delete` (Delete profiles)
- Risk: danger
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Delete a plan

`DELETE /api/v1/profiles/{id}`

Refused while the plan is still referenced — by a subscriber, a card series or another plan's fall-back chain.

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_delete` (Delete profiles)
- Risk: danger
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Plan id. |

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_CONFLICT` | 409 | the plan is still in use |


### Fetch one plan

`GET /api/v1/profiles/{id}`

The full plan definition: pricing, quota limits and their enforce toggles, speed, lifecycle flags and the fall-back chain.

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

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Plan id. |

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_NOT_FOUND` | 404 | no such plan, or one this manager may not see |

#### Note

A plan you may not see answers 404, identical to one that does not exist. This route carries wholesale terms (base_fee, max_price), so a 403 would have let a reseller walk plan ids and read a rival's private pricing.


### Update a plan

`PATCH /api/v1/profiles/{id}`

Partial update. Numeric and foreign-key fields can be cleared to null by sending null explicitly, which is distinct from omitting them.

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_update` (Edit profiles)
- Risk: write
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Plan id. |

#### Note

Editing a plan does not re-provision the subscribers already on it. Their quota and window were written at activation time; a price or limit change applies to the next activation, not retroactively.


### Manage profile visibility / policy

`GET /api/v1/profiles/{id}/allowed-extensions`

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_policy_manager` (Manage profile visibility / policy)
- Risk: write

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Manage profile visibility / policy

`PUT /api/v1/profiles/{id}/allowed-extensions`

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_policy_manager` (Manage profile visibility / policy)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Read a private plan's manager allow-list

`GET /api/v1/profiles/{id}/allowed-managers`

Which managers may see and assign this plan. Only meaningful while the plan is private; a public plan is visible to everyone regardless of this list.

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_policy_manager` (Manage profile visibility / policy)
- Risk: write

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Plan id. |


### Replace a private plan's manager allow-list

`PUT /api/v1/profiles/{id}/allowed-managers`

Replaces the whole list. Managers omitted from the payload lose access.

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_policy_manager` (Manage profile visibility / policy)
- Risk: write
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Plan id. |

#### Note

The allow-list is inert while private is false. Setting it on a public plan stores the rows and changes nothing, which looks exactly like the restriction failing to apply.


### Manage profile visibility / policy

`GET /api/v1/profiles/{id}/allowed-parents`

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_policy_manager` (Manage profile visibility / policy)
- Risk: write

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Manage profile visibility / policy

`PUT /api/v1/profiles/{id}/allowed-parents`

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_policy_manager` (Manage profile visibility / policy)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Clone a plan

`POST /api/v1/profiles/{id}/clone`

Copies the plan, including its RADIUS attributes and policies, under a new name. The usual way to build a variant without re-entering forty fields.

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_create` (Create profiles)
- Risk: write
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Plan to copy. |


### Manage plan policy rules

`GET /api/v1/profiles/{id}/policies`

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_policies` (Manage plan policy rules)
- Risk: write

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Manage plan policy rules

`POST /api/v1/profiles/{id}/policies`

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_policies` (Manage plan policy rules)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Manage plan policy rules

`POST /api/v1/profiles/{id}/policies/bulk-delete`

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_policies` (Manage plan policy rules)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Manage plan policy rules

`DELETE /api/v1/profiles/{id}/policies/{policyID}`

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_policies` (Manage plan policy rules)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Manage plan policy rules

`PATCH /api/v1/profiles/{id}/policies/{policyID}`

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_policies` (Manage plan policy rules)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Read a plan's per-reseller prices

`GET /api/v1/profiles/{id}/price-list`

The pricing-tree overrides for this plan: what each manager in the tree pays and may charge, where it differs from the plan default.

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_pricing` (Manage profile pricing)
- Risk: write

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Plan id. |


### Replace a plan's per-reseller prices

`PUT /api/v1/profiles/{id}/price-list`

Replaces the whole override set for this plan in one call. An entry omitted from the payload is removed, not left alone.

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_pricing` (Manage profile pricing)
- Risk: write
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Plan id. |

#### Note

A price cap of 0 and an absent cap mean different things. Absent is "no cap"; 0 is a cap of zero, which blocks every sale of that plan below the manager it is set on and reads on screen as "Over cap 0.00".


### List a plan's RADIUS attributes

`GET /api/v1/profiles/{id}/radius-attrs`

The reply attributes this plan adds to an Access-Accept, beyond the ones derived from its speed and quota settings.

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_radius_attributes` (Manage RADIUS attributes)
- Risk: write

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Plan id. |


### Add a RADIUS attribute to a plan

`POST /api/v1/profiles/{id}/radius-attrs`

Adds one reply attribute. Attributes set here ride on every session of every subscriber on the plan.

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_radius_attributes` (Manage RADIUS attributes)
- Risk: write
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Plan id. |


### Manage RADIUS attributes

`POST /api/v1/profiles/{id}/radius-attrs/bulk-delete`

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_radius_attributes` (Manage RADIUS attributes)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Manage RADIUS attributes

`DELETE /api/v1/profiles/{id}/radius-attrs/{attrId}`

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_radius_attributes` (Manage RADIUS attributes)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Manage RADIUS attributes

`PATCH /api/v1/profiles/{id}/radius-attrs/{attrId}`

- Authentication: manager session (JWT) or API token
- Permission: `prm_profiles_radius_attributes` (Manage RADIUS attributes)
- Risk: write
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


