# Load balancers

Spread incoming traffic across instances using VPC-native or appliance load balancers.

Source: https://docs.pantechdynamics.com/api/load-balancers

A load balancer distributes incoming network connections across multiple instances. Pantech supports two kinds of load balancers: `vpc_native` for instances in a private VPC subnet (using a reserved public IP with purpose `load_balancer`), and `appliance` for standard security group zones (with dedicated public IP allocation and HTTP/HTTPS/TCP support). The ports, zone, kind, and network settings are fixed once created; the name, algorithm, and backend targets can change. See the [load balancers guide](https://docs.pantechdynamics.com/networking/load-balancers).

Endpoints:

- `GET /public/v1/load-balancers`: List load balancers
- `POST /public/v1/load-balancers`: Create a load balancer
- `GET /public/v1/load-balancers/{load_balancer_id}`: Get a load balancer
- `PATCH /public/v1/load-balancers/{load_balancer_id}`: Change a load balancer
- `DELETE /public/v1/load-balancers/{load_balancer_id}`: Delete a load balancer

## The load balancer object



- `id` (string, required): e.g. `lb_...`.
- `name` (string, required)
- `zone_id` (string, required)
- `kind` (string, required): One of `vpc_native`, `appliance`.
- `public_ip_id` (string, required)
- `public_ip_address` (string or null, required): The address clients connect to; null until it is known.
- `network_id` (string or null, required)
- `subnet_id` (string, required)
- `protocol` (string, required): One of `tcp`, `http`, `https`.
- `algorithm` (string, required): One of `roundrobin`, `leastconn`, `source`.
- `public_port` (integer, required)
- `private_port` (integer, required)
- `cidr_list` (array of string, required): Source addresses allowed to connect; empty is anywhere.
- `members` (array of LoadBalancerMember, required)
  - `instance_id` (string, required)
  - `instance_name` (string or null): The instance's name; null when it is gone.
  - `desired_state` (string, required): `present` while it should receive traffic; `deleted` once you removed it and that has not finished. One of `present`, `deleted`.
  - `observed_state` (string, required): `active` once it receives traffic. One of `pending`, `active`, `deleting`, `deleted`, `failed`, `unknown`.
- `desired_state` (string, required): What you asked for: `present`, or `deleted` once you deleted it. One of `present`, `deleted`.
- `observed_state` (string, required): `active` once it serves traffic. One of `pending`, `provisioning`, `active`, `deleting`, `deleted`, `failed`, `unknown`.
- `in_sync` (boolean, required): False while a change you made has not reached the load balancer yet.
- `created_at` (string or null, required)
- `updated_at` (string or null, required)
## List load balancers

```http
GET /public/v1/load-balancers
```

Your load balancers, newest first, each with its targets. Deleted and failed ones are left out. Narrow it with `network_id` or `public_ip_id`.

Scope: `read`.

Query parameters:

- `network_id` (string): Only load balancers in this private network (`net_…`).
- `public_ip_id` (string): Only load balancers on this public IP (`pip_…`).

Example:

```sh
curl "https://api.pantechdynamics.com/public/v1/load-balancers" \
  -H "Authorization: Bearer $PANTECH_API_KEY"
```

Response `200`:

```json
{
  "data": [
    {
      "id": "lb_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "name": "web",
      "zone_id": "af-abj-2",
      "kind": "vpc_native",
      "public_ip_id": "pip_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "public_ip_address": "102.211.122.80",
      "network_id": "net_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "subnet_id": "snet_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "protocol": "tcp",
      "algorithm": "roundrobin",
      "public_port": 443,
      "private_port": 8443,
      "cidr_list": [],
      "members": [
        {
          "instance_id": "vm_06gk3x9tq7m2v8r4y1c5d0zp8w",
          "instance_name": "web-1",
          "desired_state": "present",
          "observed_state": "active"
        }
      ],
      "desired_state": "present",
      "observed_state": "active",
      "in_sync": true,
      "created_at": "2026-10-03T09:30:00Z",
      "updated_at": "2026-10-03T09:30:00Z"
    }
  ],
  "next_cursor": null
}
```

Errors:

- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `NOT_FOUND`: load balancers are not offered on this platform yet.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
## Create a load balancer

```http
POST /public/v1/load-balancers
```

Spreads one TCP port of a `load_balancer` public IP across instances in one subnet of that IP's private network (VPC). The public IP must be active and have `purpose` `load_balancer`; one public IP can carry several load balancers, one per public port. `private_port` defaults to `public_port`, and `algorithm` to `roundrobin`. `cidr_list` limits which source addresses may connect (default: anywhere). The subnet's firewall rules must allow the port.

Returns `202 Accepted` with an `operation_id` (also in the `Location` header). The change happens in the background: poll `GET /public/v1/operations/{operation_id}`, or stream `/events`, until `status` is `succeeded` or `failed`; a failed operation carries a stable `failure.code` and a readable `failure.reason`.

Send an `Idempotency-Key`. A retry with the same key returns the original operation instead of making the change twice. The load balancer's id is the operation's `resource_id`.

Scope: `write`. Send an `Idempotency-Key` header.

Body:

- `name` (string, required): 1 to 63 characters.
- `zone_id` (string): The zone to place the load balancer in.
- `kind` (string): `vpc_native` (a VPC zone: needs `public_ip_id` and `subnet_id`) or `appliance` (a standard zone: needs `zone_id`; a load balancer VM with its own public address, billed as that VM). Left out, it is `vpc_native` when `subnet_id` is given and `appliance` when only `zone_id` is. Anything else is `422 INVALID_LOAD_BALANCER_KIND`; an appliance in a zone that does not offer one fails its operation with `LOAD_BALANCER_APPLIANCE_UNAVAILABLE`. One of `vpc_native`, `appliance`.
- `public_ip_id` (string): An active public IP with `purpose` `load_balancer` (required for `vpc_native`).
- `subnet_id` (string): A subnet of the public IP's private network. Every target must be in it (required for `vpc_native`).
- `algorithm` (string): `roundrobin` takes turns, `leastconn` picks the target with the fewest connections, `source` keeps a client on one target. One of `roundrobin`, `leastconn`, `source`. Defaults to `roundrobin`.
- `public_port` (integer, required): The TCP port clients connect to, 1 to 65535.
- `private_port` (integer): The port on each target. Defaults to `public_port`.
- `cidr_list` (array of string): Source addresses allowed to connect, as IPv4 CIDRs (at most 20). Leave out for anywhere.
- `instance_ids` (array of string): Instances to spread traffic across (at most 50). You can add them later.

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/load-balancers" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "web",
    "public_port": 443,
    "private_port": 8443,
    "instance_ids": [
      "vm_06gk3x9tq7m2v8r4y1c5d0zp8w"
    ]
  }'
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "lb_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "status": "submitting"
}
```

Errors:

- `400`: `IDEMPOTENCY_KEY_REQUIRED`: the `Idempotency-Key` header is missing. Send a new unique value, such as a UUID, with every write.
`INVALID_IDEMPOTENCY_KEY`: the `Idempotency-Key` header is longer than 255 characters. Use a shorter value.
`INVALID_REQUEST_BODY`: the body is not a single valid JSON object. Send one JSON object with the fields documented for this endpoint.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `RESOURCE_NOT_FOUND`: the public IP, subnet or an instance does not exist in your organization. Check the ids.
`NOT_FOUND`: load balancers are not offered on this platform yet.
- `409`: `PUBLIC_IP_NOT_FOR_LOAD_BALANCER`: use an active public IP with `purpose` `load_balancer`.
`PORT_IN_USE`: another load balancer on this public IP already uses this port. Choose another port, or delete that load balancer.
`CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
- `413`: `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
- `422`: `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
`name`, `public_ip_id`, `subnet_id` or `public_port` `REQUIRED`: send them.
`public_port` `INVALID_LOAD_BALANCER`: `name` 1 to 63 characters, `algorithm` `roundrobin`, `leastconn` or `source`, ports 1 to 65535, and at most 20 IPv4 CIDRs in `cidr_list`.
`subnet_id` `SUBNET_NOT_IN_NETWORK`: the subnet is not in the public IP's private network.
`instance_ids` `INSTANCE_NOT_IN_SUBNET`: every instance must be in the load balancer's subnet.
`instance_ids` `TOO_MANY`: at most 50 instances.
`UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
`PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
## Get a load balancer

```http
GET /public/v1/load-balancers/{load_balancer_id}
```

One load balancer, with its targets. `in_sync` is false while a change you made has not reached it yet.

Scope: `read`.

Path parameters:

- `load_balancer_id`: The load balancer's id (`lb_…`).

Example:

```sh
curl "https://api.pantechdynamics.com/public/v1/load-balancers/lb_06gk3x9tq7m2v8r4y1c5d0zp8w" \
  -H "Authorization: Bearer $PANTECH_API_KEY"
```

Response `200`:

```json
{
  "id": "lb_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "name": "web",
  "zone_id": "af-abj-2",
  "kind": "vpc_native",
  "public_ip_id": "pip_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "public_ip_address": "102.211.122.80",
  "network_id": "net_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "subnet_id": "snet_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "protocol": "tcp",
  "algorithm": "roundrobin",
  "public_port": 443,
  "private_port": 8443,
  "cidr_list": [],
  "members": [
    {
      "instance_id": "vm_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "instance_name": "web-1",
      "desired_state": "present",
      "observed_state": "active"
    }
  ],
  "desired_state": "present",
  "observed_state": "active",
  "in_sync": true,
  "created_at": "2026-10-03T09:30:00Z",
  "updated_at": "2026-10-03T09:30:00Z"
}
```

Errors:

- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `RESOURCE_NOT_FOUND`: no load balancer with this id in your organization, or the id is not a load balancer id (`lb_…`). Check the id.
`NOT_FOUND`: load balancers are not offered on this platform yet.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
## Change a load balancer

```http
PATCH /public/v1/load-balancers/{load_balancer_id}
```

Renames it, changes its `algorithm`, or replaces its targets: `instance_ids`, when sent (even empty), is the whole new set. Fields you leave out stay as they are. The ports, the public IP, the subnet and `cidr_list` cannot change: create a new load balancer for those. A change that changes nothing returns an empty `operation_id`.

Returns `202 Accepted` with an `operation_id` (also in the `Location` header). The change happens in the background: poll `GET /public/v1/operations/{operation_id}`, or stream `/events`, until `status` is `succeeded` or `failed`; a failed operation carries a stable `failure.code` and a readable `failure.reason`.

Send an `Idempotency-Key`. A retry with the same key returns the original operation instead of making the change twice.

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `load_balancer_id`: The load balancer's id (`lb_…`).

Body:

- `name` (string): A new name, 1 to 63 characters.
- `algorithm` (string): One of `roundrobin`, `leastconn`, `source`.
- `instance_ids` (array of string): The whole new set of targets (at most 50). Send `[]` to remove them all.

Example:

```sh
curl -X PATCH "https://api.pantechdynamics.com/public/v1/load-balancers/lb_06gk3x9tq7m2v8r4y1c5d0zp8w" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "instance_ids": [
      "vm_06gk3x9tq7m2v8r4y1c5d0zp8w"
    ]
  }'
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "lb_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "status": "submitting"
}
```

Errors:

- `400`: `IDEMPOTENCY_KEY_REQUIRED`: the `Idempotency-Key` header is missing. Send a new unique value, such as a UUID, with every write.
`INVALID_IDEMPOTENCY_KEY`: the `Idempotency-Key` header is longer than 255 characters. Use a shorter value.
`INVALID_REQUEST_BODY`: the body is not a single valid JSON object. Send one JSON object with the fields documented for this endpoint.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `RESOURCE_NOT_FOUND`: no load balancer with this id in your organization, or the id is not a load balancer id (`lb_…`). Check the id.
`NOT_FOUND`: load balancers are not offered on this platform yet.
- `409`: `LOAD_BALANCER_NOT_CHANGEABLE`: the load balancer is being deleted or has failed.
`CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
- `413`: `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
- `422`: `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
`public_port` `INVALID_LOAD_BALANCER`: `name` 1 to 63 characters and `algorithm` `roundrobin`, `leastconn` or `source`.
`instance_ids` `INSTANCE_NOT_IN_SUBNET`: every instance must be in the load balancer's subnet.
`instance_ids` `TOO_MANY`: at most 50 instances.
`UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
`PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
## Delete a load balancer

```http
DELETE /public/v1/load-balancers/{load_balancer_id}
```

Deletes the load balancer. Traffic to its port stops once the operation succeeds; the instances and the public IP are kept.

Returns `202 Accepted` with an `operation_id` (also in the `Location` header). The change happens in the background: poll `GET /public/v1/operations/{operation_id}`, or stream `/events`, until `status` is `succeeded` or `failed`; a failed operation carries a stable `failure.code` and a readable `failure.reason`.

Send an `Idempotency-Key`. A retry with the same key returns the original operation instead of making the change twice.

**Scope:** `write`. Destructive: an AI app's key also needs `delete`.

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `load_balancer_id`: The load balancer's id (`lb_…`).

Example:

```sh
curl -X DELETE "https://api.pantechdynamics.com/public/v1/load-balancers/lb_06gk3x9tq7m2v8r4y1c5d0zp8w" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "lb_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "status": "submitting"
}
```

Errors:

- `400`: `IDEMPOTENCY_KEY_REQUIRED`: the `Idempotency-Key` header is missing. Send a new unique value, such as a UUID, with every write.
`INVALID_IDEMPOTENCY_KEY`: the `Idempotency-Key` header is longer than 255 characters. Use a shorter value.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `RESOURCE_NOT_FOUND`: no load balancer with this id in your organization, or the id is not a load balancer id (`lb_…`). Check the id.
`NOT_FOUND`: load balancers are not offered on this platform yet.
- `409`: `CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
`PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
