## TCP/UDP forwarding (`streams`)

TCP/UDP port forwarding rules. Each rule has listen ports and protocols, an origin list and a balancing method; (protocol, port) is unique platform-wide.

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

### GET `/api/v2/streams`

List streams · 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 |
| `port` | integer |  | Only list forwarding rules listening on this port. |
| `group_id` | integer |  | Only list rules in this group. |
| `upid` | integer |  | Only list rules under this user package. |
| `enabled` | boolean |  | Filter by enabled state. |
| `id` | integer |  | Filter by exact forwarding rule ID. |
| `origin` | string |  | Filter by substring of the origin IP. |
| `protocol` | string |  | Only list rules that have a listener with this protocol. Values: ["tcp","udp"] |
| `stop_code` | integer |  | Filter by stop code; 0 = normal. |
| `package_id` | integer |  | Filter by base package (the owning user package was bought from it). |
| `region_id` | integer |  | Filter by region (the CNAME's primary line group belongs to it); owners without their own CNAME use their user package's shared one. |
| `node_group_id` | integer |  | Filter by line group (the CNAME's primary or backup line group). |
| `cname_domain_id` | integer |  | Filter by CNAME root domain. |
| `cname_hostname` | string |  | Filter by CNAME host label, exact match (case-insensitive, without the root domain). |
| `sync_state` | string |  | Filter by config sync state: behind = some online node has not yet installed the config version containing its latest change; Values: ["behind","synced"] |

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

### POST `/api/v2/streams`

Create stream · Authentication: User (some parameters admin only)

Query parameters:

| Field | Type | Required | Description |
|---|---|---|---|
| `tenant_id` | integer |  | Owner tenant when an admin creates on behalf of a tenant (required); ignored for tenant callers, where the owner is always the caller. |

Request body:

| Field | Type | Required | Description |
|---|---|---|---|
| `acl` | StreamAcl |  | Layer-4 access control. Rules match the source IP or CIDR in order and the first hit decides; if none match, default_action applies; deny drops the connection immediately. |
| `balance` | StreamBalance |  | Load balancing method. rr is round robin, ip_hash hashes the source IP, least_conn picks the fewest connections, random picks at random. Values: ["rr","ip_hash","least_conn","random"] |
| `cname` | CnameBinding |  | Access name (CNAME). Users point their domain to fqdn with a CNAME record; the platform generates its DNS records from the node group. null when there is no access name. |
| `conn_limit` | integer |  | Per-IP concurrent connection limit; 0 = unlimited. |
| `enabled` | boolean |  | Whether enabled |
| `expected_updated_at` | string |  | Optimistic lock (used on update) - the updated_at of the resource as the caller read it. If it differs from the stored one, 409 stale_version is returned and nothing is saved; omit to skip the check; ignored on create. |
| `group_ids` | integer[] |  | IDs of the forwarding groups this rule belongs to. |
| `listen` | StreamListen[] | Yes | Listener list. Each item is a port and a protocol. |
| `name` | string |  | Name |
| `origin_port` | integer | Yes | Origin port to connect to. |
| `origins` | StreamOrigin[] | Yes | Origin list. Each item has an origin IP, a weight, and backup/down flags. |
| `proxy_protocol` | boolean |  | Whether to send a PROXY protocol header to the origin. |
| `stop_code` | integer |  | 0 = normal; non-zero = new connections are dropped immediately (set by admin or billing; 512 in the snapshot when the user package has expired). |
| `udp_responses` | integer |  | UDP responses. 1-100 = end each UDP session after this many packets from the origin (instead of waiting for the UDP idle timeout); 0 = not set. Only for request/response traffic (DNS, probes); do not set it for games or voice that send many replies per request, or their sessions get cut short. |
| `upid` | integer |  | User package ID (empty = no quota check; empty falls back to tenant_id in the snapshot). |

```sh
curl -X POST "https://panel.example.com/api/v2/streams" \
  -H "X-API-Key: $GOCDN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"listen":[{"port":30001}],"name":"demo","origin_port":22,"origins":[{"host":"203.0.113.10"}]}'
```

### POST `/api/v2/streams/batch`

Batch operate streams · Authentication: User (some parameters admin only)

Query parameters:

| Field | Type | Required | Description |
|---|---|---|---|
| `tenant_id` | integer |  | Owner tenant when an admin creates on behalf of a tenant (required); ignored for tenant callers, where the owner is always the caller. |

Request body:

| Field | Type | Required | Description |
|---|---|---|---|
| `action` | string | Yes | Operation. set_enabled toggles enabled state, delete removes, set_groups replaces groups, update edits fields in bulk, create adds new ones, set_lines changes the CNAME's primary/backup line groups (admin only), set_cname_domain changes the CNAME root domain (admin only, host label kept). Values: ["set_enabled","delete","set_groups","update","create","set_lines","set_cname_domain"] |
| `backup_group_id` | integer |  | Used by set_lines: backup line group; 0 clears it, omitted leaves it unchanged. |
| `cname_domain_id` | integer |  | Used by set_cname_domain: the new CNAME root domain. |
| `enabled` | boolean |  | Whether enabled |
| `group_ids` | integer[] |  | IDs of the forwarding groups this rule belongs to. |
| `ids` | integer[] |  | IDs of the forwarding rules to operate on, at most 100 per call (required by every action except create). |
| `items` | StreamWrite[] |  | Used by create: the forwarding rules to create (up to 100), each shaped like the POST /streams request body. |
| `node_group_id` | integer |  | Used by set_lines: primary line group (required). |
| `patch` | StreamPatch |  | For batch update. Fields that are given are applied to every rule, with the same rules as a single update. |
| `region_id` | integer |  | Used by set_lines: region; when given, both line groups must belong to it. |

```sh
curl -X POST "https://panel.example.com/api/v2/streams/batch" \
  -H "X-API-Key: $GOCDN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action":"set_enabled","enabled":false,"ids":[1]}'
```

### GET `/api/v2/streams/{id}`

Get stream · Authentication: User (some parameters admin only)

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

### PUT `/api/v2/streams/{id}`

Update stream · Authentication: User (some parameters admin only)

Request body:

| Field | Type | Required | Description |
|---|---|---|---|
| `acl` | StreamAcl |  | Layer-4 access control. Rules match the source IP or CIDR in order and the first hit decides; if none match, default_action applies; deny drops the connection immediately. |
| `balance` | StreamBalance |  | Load balancing method. rr is round robin, ip_hash hashes the source IP, least_conn picks the fewest connections, random picks at random. Values: ["rr","ip_hash","least_conn","random"] |
| `cname` | CnameBinding |  | Access name (CNAME). Users point their domain to fqdn with a CNAME record; the platform generates its DNS records from the node group. null when there is no access name. |
| `conn_limit` | integer |  | Per-IP concurrent connection limit; 0 = unlimited. |
| `enabled` | boolean |  | Whether enabled |
| `expected_updated_at` | string |  | Optimistic lock (used on update) - the updated_at of the resource as the caller read it. If it differs from the stored one, 409 stale_version is returned and nothing is saved; omit to skip the check; ignored on create. |
| `group_ids` | integer[] |  | IDs of the forwarding groups this rule belongs to. |
| `listen` | StreamListen[] | Yes | Listener list. Each item is a port and a protocol. |
| `name` | string |  | Name |
| `origin_port` | integer | Yes | Origin port to connect to. |
| `origins` | StreamOrigin[] | Yes | Origin list. Each item has an origin IP, a weight, and backup/down flags. |
| `proxy_protocol` | boolean |  | Whether to send a PROXY protocol header to the origin. |
| `stop_code` | integer |  | 0 = normal; non-zero = new connections are dropped immediately (set by admin or billing; 512 in the snapshot when the user package has expired). |
| `udp_responses` | integer |  | UDP responses. 1-100 = end each UDP session after this many packets from the origin (instead of waiting for the UDP idle timeout); 0 = not set. Only for request/response traffic (DNS, probes); do not set it for games or voice that send many replies per request, or their sessions get cut short. |
| `upid` | integer |  | User package ID (empty = no quota check; empty falls back to tenant_id in the snapshot). |

```sh
curl -X PUT "https://panel.example.com/api/v2/streams/{id}" \
  -H "X-API-Key: $GOCDN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"listen":[{"port":30001}],"name":"demo","origin_port":22,"origins":[{"host":"203.0.113.10"}]}'
```

### DELETE `/api/v2/streams/{id}`

Delete stream · Authentication: User (some parameters admin only)

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

TCP/UDP forwarding object:

| Field | Type | Required | Description |
|---|---|---|---|
| `acl` | StreamAcl |  | Layer-4 access control. Rules match the source IP or CIDR in order and the first hit decides; if none match, default_action applies; deny drops the connection immediately. |
| `action` | string | Yes | Operation. set_enabled toggles enabled state, delete removes, set_groups replaces groups, update edits fields in bulk, create adds new ones, set_lines changes the CNAME's primary/backup line groups (admin only), set_cname_domain changes the CNAME root domain (admin only, host label kept). Values: ["set_enabled","delete","set_groups","update","create","set_lines","set_cname_domain"] |
| `affected` | integer |  | Number of affected records |
| `backup_group_id` | integer |  | Used by set_lines: backup line group; 0 clears it, omitted leaves it unchanged. |
| `balance` | StreamBalance |  | Load balancing method. rr is round robin, ip_hash hashes the source IP, least_conn picks the fewest connections, random picks at random. Values: ["rr","ip_hash","least_conn","random"] |
| `cname` | CnameBinding |  | Access name (CNAME). Users point their domain to fqdn with a CNAME record; the platform generates its DNS records from the node group. null when there is no access name. |
| `cname_domain_id` | integer |  | Used by set_cname_domain: the new CNAME root domain. |
| `conn_limit` | integer |  | Per-IP concurrent connection limit; 0 = unlimited. |
| `created_at` | string |  | Created at (read-only) |
| `enabled` | boolean |  | Whether enabled |
| `expected_updated_at` | string |  | Optimistic lock (used on update) - the updated_at of the resource as the caller read it. If it differs from the stored one, 409 stale_version is returned and nothing is saved; omit to skip the check; ignored on create. |
| `group_ids` | integer[] |  | IDs of the forwarding groups this rule belongs to. |
| `id` | integer |  | Record ID (read-only) |
| `ids` | integer[] |  | IDs of the forwarding rules to operate on, at most 100 per call (required by every action except create). |
| `items` | StreamWrite[] |  | Used by create: the forwarding rules to create (up to 100), each shaped like the POST /streams request body. |
| `listen` | StreamListen[] |  | Listener list. Each item is a port and a protocol. |
| `name` | string |  | Name |
| `node_group_id` | integer |  | Used by set_lines: primary line group (required). |
| `node_issues` | NodeIssue[] |  | Ports or reasons for which the forward is not fully in effect on online nodes (in the list and the detail); omitted when everything is in effect. (read-only) |
| `origin_port` | integer |  | Origin port to connect to. |
| `origins` | StreamOrigin[] |  | Origin list. Each item has an origin IP, a weight, and backup/down flags. |
| `patch` | StreamPatch |  | For batch update. Fields that are given are applied to every rule, with the same rules as a single update. |
| `proxy_protocol` | boolean |  | Whether to send a PROXY protocol header to the origin. |
| `region_id` | integer |  | Used by set_lines: region; when given, both line groups must belong to it. |
| `stop_code` | integer |  | 0 = normal; non-zero = new connections are dropped immediately (set by admin or billing; 512 in the snapshot when the user package has expired). |
| `tenant_id` | integer |  | Owner tenant (user) ID (read-only) |
| `udp_responses` | integer |  | UDP responses. 1-100 = end each UDP session after this many packets from the origin (instead of waiting for the UDP idle timeout); 0 = not set. Only for request/response traffic (DNS, probes); do not set it for games or voice that send many replies per request, or their sessions get cut short. |
| `updated_at` | string |  | Updated at (read-only) |
| `upid` | integer |  | User package ID (empty = no quota check; empty falls back to tenant_id in the snapshot). |
