## Purchased packages (`user-packages`)

Packages a tenant has bought: purchase, renew, change, add upgrade packs and view usage.

Authentication: User (some parameters admin only) · Docs: https://www-demo.kuxitong.com/en/docs/api/user-packages

### GET `/api/v2/user-packages`

List purchased packages · Authentication: User (some parameters admin only)

Query parameters:

| Field | Type | Required | Description |
|---|---|---|---|
| `page` | integer |  | Page number, starting from 1 Default: 1 |
| `page_size` | integer |  | Items per page Default: 20 |
| `sort` | string |  | Sort fields, comma-separated; prefix `-` for descending, e.g. `-created_at,id` |
| `tenant_id` | integer |  | Admin only: filter by tenant. Ignored for tenant credentials, which only see their own tenant's data |
| `ids` | string |  | Only list these IDs: comma-separated positive integers, at most 200 after deduplication (same as the page_size limit; more returns 400); an empty value is the same as omitting it. Combined with other filters as an intersection; IDs you cannot see are silently left out. Pagination is unchanged: set page_size to at least the number of IDs to get them in one page |
| `package_id` | integer |  | Only list purchased packages of this base package. |
| `expire_within_days` | integer |  | Only list purchased packages expiring within this many days (including already expired ones). |
| `name` | string |  | Purchased package name contains (% and _ match literally). |
| `customized` | boolean |  | true lists only purchased packages with individually edited quotas or not following the base package; false lists only those following it unchanged. |

```sh
curl "https://panel.example.com/api/v2/user-packages" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

### POST `/api/v2/user-packages`

Purchase package · Authentication: User (some parameters admin only)

Query parameters:

| Field | Type | Required | Description |
|---|---|---|---|
| `tenant_id` | integer |  | Admin only: filter by tenant. Ignored for tenant credentials, which only see their own tenant's data |

Request body:

| Field | Type | Required | Description |
|---|---|---|---|
| `dry_run` | boolean |  | Quote only, do not save. |
| `duration` | string |  | Duration: month/quarter/year (adds 1/3/12 months; at month end the last day of the target month is used). Values: ["month","quarter","year"] |
| `end_at` | string |  | Expiry time of the purchased package. |
| `name` | string |  | Name |
| `package_id` | integer | Yes | Base package id. |

```sh
curl -X POST "https://panel.example.com/api/v2/user-packages" \
  -H "X-API-Key: $GOCDN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"duration":"month","package_id":1}'
```

### GET `/api/v2/user-packages/{id}`

Get purchased package · Authentication: User (some parameters admin only)

```sh
curl "https://panel.example.com/api/v2/user-packages/{id}" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

### PUT `/api/v2/user-packages/{id}`

Update purchased package · Authentication: User (some parameters admin only)

Request body:

| Field | Type | Required | Description |
|---|---|---|---|
| `backup_group_id` | integer |  | Backup node group; null = none. |
| `bandwidth_mbps` | integer |  | Bandwidth in Mbps, -1 = unlimited. |
| `cname_domain_id` | integer |  | CNAME root domain; null = the package binds no lines. |
| `cname_hostname` | string |  | Host record of the shared access name (used by sites and streams in per-package mode); empty = none yet. |
| `cname_mode` | PackageCnameMode |  | CNAME mode. site = one access name per site or stream; package = sites and streams of a purchased package share one access name. Values: ["site","package"] |
| `connections` | integer |  | Concurrent connections, -1 = unlimited. |
| `custom_cc_rule` | boolean |  | Allows custom CC rules. |
| `domains` | integer |  | Number of domains (each IP site counts as 1), -1 = unlimited. |
| `end_at` | string |  | Expiry time of the purchased package. |
| `follow_base` | boolean |  | Follows the base package; quota changes on the base package are synced here automatically (except fields in overrides). |
| `http_ports` | integer |  | Number of non-standard HTTP/HTTPS ports (other than 80/443), -1 = unlimited. |
| `ip_num` | integer |  | Number of selectable IPs ("My IPs"), -1 = unlimited. |
| `ip_pool_id` | integer |  | IP pool used to provision IP sites (IP product line); null = not provisioned. |
| `ip_site_port` | integer |  | IP-site ports: distinct ports across all IP sites of the package (80/443 included), -1 = unlimited; IP-site ports do not count toward non-standard ports. |
| `l2` | boolean |  | Includes tiered origin (tenant switch of the paid feature l2): tenants can turn on tiered origin for sites, and nodes use it, only while the master license l2 is active; existing packages are false (on upgrade, purchased packages of tenants already using tiered origin are set to true). |
| `name` | string |  | Name |
| `node_group_id` | integer |  | Primary node group; null = the package binds no lines. |
| `price_month_cents` | integer |  | Monthly price locked at purchase. |
| `price_quarter_cents` | integer |  | Quarterly price locked at purchase. |
| `price_year_cents` | integer |  | Yearly price locked at purchase. |
| `region_id` | integer |  | Region; null = the package binds no lines. |
| `reset_to_base` | boolean |  | Admin only; true = reset quota fields to the base package values, clear overrides and follow again (cannot be combined with other fields). |
| `stream_ports` | integer |  | Number of TCP/UDP forwarding ports, -1 = unlimited. |
| `traffic_gb` | integer |  | Traffic in GB per billing cycle, -1 = unlimited. |
| `waf_enhanced` | boolean |  | Includes WAF Enhanced (tenant switch of the paid feature waf_enhanced): tenants can use standard/strict levels, big screen, deep Bot, anti-tamper and so on only while the master license WAF Enhanced is not degraded; existing packages are false. |
| `websocket` | boolean |  | Allows WebSocket. |

```sh
curl -X PUT "https://panel.example.com/api/v2/user-packages/{id}" \
  -H "X-API-Key: $GOCDN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"主站套餐"}'
```

### DELETE `/api/v2/user-packages/{id}`

Delete purchased package · Authentication: User (some parameters admin only)

```sh
curl -X DELETE "https://panel.example.com/api/v2/user-packages/{id}" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

### POST `/api/v2/user-packages/{id}/changes`

Change package (upgrade or downgrade) · Authentication: User (some parameters admin only)

Request body:

| Field | Type | Required | Description |
|---|---|---|---|
| `dry_run` | boolean |  | Quote only, do not save. |
| `package_id` | integer | Yes | Base package id. |

```sh
curl -X POST "https://panel.example.com/api/v2/user-packages/{id}/changes" \
  -H "X-API-Key: $GOCDN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"package_id":2}'
```

### GET `/api/v2/user-packages/{id}/ips`

My IPs · Authentication: User (some parameters admin only)

```sh
curl "https://panel.example.com/api/v2/user-packages/{id}/ips" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

### POST `/api/v2/user-packages/{id}/ips`

Select IPs · Authentication: User (some parameters admin only)

Request body:

| Field | Type | Required | Description |
|---|---|---|---|
| `ip_ids` | integer[] | Yes | IDs of the pool IPs to select. |

```sh
curl -X POST "https://panel.example.com/api/v2/user-packages/{id}/ips" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

### PUT `/api/v2/user-packages/{id}/ips/{ip_id}`

Update note of selected IP · Authentication: User (some parameters admin only)

Request body:

| Field | Type | Required | Description |
|---|---|---|---|
| `remark` | string | Yes | Remark |

```sh
curl -X PUT "https://panel.example.com/api/v2/user-packages/{id}/ips/{ip_id}" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

### DELETE `/api/v2/user-packages/{id}/ips/{ip_id}`

Remove selected IP · Authentication: User (some parameters admin only)

```sh
curl -X DELETE "https://panel.example.com/api/v2/user-packages/{id}/ips/{ip_id}" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

### GET `/api/v2/user-packages/{id}/node-groups`

Node groups of a purchased package · Authentication: User (some parameters admin only)

```sh
curl "https://panel.example.com/api/v2/user-packages/{id}/node-groups" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

### POST `/api/v2/user-packages/{id}/renewals`

Renew purchased package · Authentication: User (some parameters admin only)

Request body:

| Field | Type | Required | Description |
|---|---|---|---|
| `dry_run` | boolean |  | Quote only, do not save. |
| `duration` | string | Yes | Duration: month/quarter/year (adds 1/3/12 months; at month end the last day of the target month is used). Values: ["month","quarter","year"] |

```sh
curl -X POST "https://panel.example.com/api/v2/user-packages/{id}/renewals" \
  -H "X-API-Key: $GOCDN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"duration":"month"}'
```

### GET `/api/v2/user-packages/{id}/upgrades`

List purchased upgrade packs · Authentication: User (some parameters admin only)

```sh
curl "https://panel.example.com/api/v2/user-packages/{id}/upgrades" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

### POST `/api/v2/user-packages/{id}/upgrades`

Buy upgrade pack · Authentication: User (some parameters admin only)

Request body:

| Field | Type | Required | Description |
|---|---|---|---|
| `amount` | integer |  | Number of units, default 1. |
| `dry_run` | boolean |  | Quote only, do not save. |
| `upgrade_id` | integer | Yes | Id of the upgrade pack to buy. |

```sh
curl -X POST "https://panel.example.com/api/v2/user-packages/{id}/upgrades" \
  -H "X-API-Key: $GOCDN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount":1,"upgrade_id":1}'
```

### DELETE `/api/v2/user-packages/{id}/upgrades/{upgrade_id}`

Reduce or remove purchased upgrade pack · Authentication: User (some parameters admin only)

Query parameters:

| Field | Type | Required | Description |
|---|---|---|---|
| `amount` | integer |  | Number of units to reduce; omitted = remove the whole item. |

```sh
curl -X DELETE "https://panel.example.com/api/v2/user-packages/{id}/upgrades/{upgrade_id}" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

### GET `/api/v2/user-packages/{id}/usage`

Purchased package usage · Authentication: User (some parameters admin only)

```sh
curl "https://panel.example.com/api/v2/user-packages/{id}/usage" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

Purchased packages object:

| Field | Type | Required | Description |
|---|---|---|---|
| `action` | string |  | Operation: buy, renew, change or upgrade. Values: ["buy","renew","change","upgrade"] |
| `added` | integer[] |  | IDs of the pool IPs newly selected by this call. |
| `amount` | integer |  | Number of units, default 1. |
| `amount_cents` | integer |  | Positive = charged, negative = refunded to balance (downgrade), 0 = no charge (including admin operations). |
| `backup_group_id` | integer |  | Backup node group; null = none. |
| `bandwidth_mbps` | integer |  | Bandwidth in Mbps, -1 = unlimited. |
| `cert_auto` | IpCertState |  | Platform IP certificate status. |
| `cname_domain_id` | integer |  | CNAME root domain; null = the package binds no lines. |
| `cname_hostname` | string |  | Host record of the shared access name (used by sites and streams in per-package mode); empty = none yet. |
| `cname_mode` | PackageCnameMode |  | CNAME mode. site = one access name per site or stream; package = sites and streams of a purchased package share one access name. Values: ["site","package"] |
| `connections` | integer |  | Concurrent connections, -1 = unlimited. |
| `created_at` | string |  | Created at (read-only) |
| `custom_cc_rule` | boolean |  | Allows custom CC rules. |
| `cycle_end` | string |  | End of the current billing cycle. |
| `cycle_start` | string |  | Start of the current billing cycle. |
| `domains` | integer |  | Number of domains (each IP site counts as 1), -1 = unlimited. |
| `dry_run` | boolean |  | Quote only, do not save. |
| `duration` | string |  | Duration: month/quarter/year (adds 1/3/12 months; at month end the last day of the target month is used). Values: ["month","quarter","year"] |
| `enabled` | boolean |  | Whether enabled |
| `end_at` | string |  | Expiry time of the purchased package. |
| `follow_base` | boolean |  | Follows the base package; quota changes on the base package are synced here automatically (except fields in overrides). (read-only) |
| `http_ports` | integer |  | Number of non-standard HTTP/HTTPS ports (other than 80/443), -1 = unlimited. |
| `id` | integer |  | Record ID (read-only) |
| `in_pool` | boolean |  | Still in the package's current IP pool. |
| `ip` | string |  | IP address. |
| `ip_ids` | integer[] | Yes | IDs of the pool IPs to select. |
| `ip_num` | integer |  | Number of selectable IPs ("My IPs"), -1 = unlimited. |
| `ip_pool_id` | integer |  | IP pool used to provision IP sites (IP product line); null = not provisioned. |
| `ip_site_port` | integer |  | IP-site ports: distinct ports across all IP sites of the package (80/443 included), -1 = unlimited; IP-site ports do not count toward non-standard ports. |
| `items` | UserPackageIp[] |  | Items on this page |
| `l2` | boolean |  | Includes tiered origin (tenant switch of the paid feature l2): tenants can turn on tiered origin for sites, and nodes use it, only while the master license l2 is active; existing packages are false (on upgrade, purchased packages of tenants already using tiered origin are set to true). |
| `limit` | integer |  | Number of IPs that can be selected; -1 means unlimited. |
| `limits` | PackageLimits |  | Effective limits (package values plus upgrade packs; -1 = unlimited, 0/1 for feature items). |
| `name` | string |  | Name |
| `node_group_id` | integer |  | Primary node group; null = the package binds no lines. |
| `node_state` | IpNodeState |  | State of the owning primary node: disabled if the node is disabled; otherwise online if a heartbeat arrived in the last 2 minutes, else offline. Values: ["online","offline","disabled"] |
| `order_id` | integer |  | Order id (when a charge or refund happened). |
| `overrides` | PackageQuotaField[] |  | Quota fields edited individually by an admin that differ from the base package; skipped by automatic sync. (read-only) |
| `package_id` | integer |  | Base package id. |
| `package_name` | string |  | Base package name (shown in package lists and orders). (read-only) |
| `period` | string |  | Price period used for proration when changing package. Values: ["month","quarter","year"] |
| `price_month_cents` | integer |  | Monthly price locked at purchase. |
| `price_quarter_cents` | integer |  | Quarterly price locked at purchase. |
| `price_year_cents` | integer |  | Yearly price locked at purchase. |
| `region` | string |  | Name of the region it belongs to. |
| `region_id` | integer |  | Region; null = the package binds no lines. |
| `remain_days` | integer |  | Remaining days used for proration. |
| `remark` | string | Yes | Remark |
| `requests` | integer |  | Number of requests in this billing cycle (sealed hours plus a live count of hours not yet sealed). |
| `reset_to_base` | boolean |  | Admin only; true = reset quota fields to the base package values, clear overrides and follow again (cannot be combined with other fields). |
| `selected` | integer |  | Number of IPs already selected. |
| `sites` | object[] |  | IP sites in this package that bind it. |
| `start_at` | string |  | Start time of the purchased package. |
| `status` | string |  | expired = expired (the tenant's sites are suspended with 512; renewing restores them). Values: ["active","expired"] |
| `stream_ports` | integer |  | Number of TCP/UDP forwarding ports, -1 = unlimited. |
| `tenant_id` | integer |  | Owner tenant (user) ID (read-only) |
| `traffic_bytes` | integer |  | Billable traffic used in this billing cycle (bytes, already multiplied by the compensation factor) — sealed hours plus a live count of hours not yet sealed, the same figure the over-quota stop uses. |
| `traffic_gb` | integer |  | Traffic in GB per billing cycle, -1 = unlimited. |
| `traffic_limit_gb` | integer |  | Effective traffic limit (GB, 1 GB = 1024^3 bytes, including upgrade packs); -1 = unlimited. |
| `updated_at` | string |  | Updated at (read-only) |
| `upgrade_id` | integer | Yes | Id of the upgrade pack to buy. |
| `user_package` | UserPackage |  | The purchased package after the operation. |
| `user_package_id` | integer |  | Id of the purchased package this operation applied to (for a purchase, the newly created one; equals user_package.id). Absent for a dry run, which saves nothing. |
| `waf_enhanced` | boolean |  | Includes WAF Enhanced (tenant switch of the paid feature waf_enhanced): tenants can use standard/strict levels, big screen, deep Bot, anti-tamper and so on only while the master license WAF Enhanced is not degraded; existing packages are false. |
| `websocket` | boolean |  | Allows WebSocket. |
