# Public IPs

Public addresses for VPC instances, with static NAT or port forwarding.

Source: https://docs.pantechdynamics.com/api/public-ips

A public IP belongs to a VPC. With purpose `static_nat` every port of the address maps to one instance. With `port_forwarding` the address carries rules, each forwarding a port or range to an instance in the VPC. With `load_balancer` it carries [load balancers](https://docs.pantechdynamics.com/api/load-balancers), one per public port.

Endpoints:

- `GET /public/v1/public-ips`: List public IPs
- `POST /public/v1/public-ips`: Allocate a public IP
- `GET /public/v1/public-ips/quote`: Price a public IP
- `GET /public/v1/public-ips/{public_ip_id}`: Get a public IP
- `DELETE /public/v1/public-ips/{public_ip_id}`: Release a public IP
- `POST /public/v1/public-ips/{public_ip_id}/attach`: Attach a public IP to an instance
- `GET /public/v1/public-ips/{public_ip_id}/port-forwarding-rules`: List port forwarding rules
- `POST /public/v1/public-ips/{public_ip_id}/detach`: Detach a public IP from its instance
- `POST /public/v1/public-ips/{public_ip_id}/port-forwarding-rules`: Add a port forwarding rule
- `GET /public/v1/public-ips/{public_ip_id}/port-forwarding-rules/{rule_id}`: Get a port forwarding rule
- `DELETE /public/v1/port-forwarding-rules/{rule_id}`: Delete a port forwarding rule

## The public IP object



- `id` (string, required): e.g. `pip_...`.
- `network_id` (string, required)
- `network_name` (string or null): The VPC's name.
- `purpose` (string, required): `static_nat` maps every port to one instance; `port_forwarding` carries per-port rules. One of `static_nat`, `port_forwarding`, `load_balancer`.
- `address` (string or null): The public IPv4 address; null until allocated.
- `instance_id` (string or null): static_nat: the instance it maps to; null while it is detached (still yours, and billed once).
- `instance_name` (string or null): The mapped instance's name; null when none.
- `region` (string or null)
- `zone` (string or null)
- `desired_state` (string, required): What you asked for: `present`, or `deleted` once you deleted it. One of `present`, `deleted`.
- `observed_state` (string, required): What the platform last observed: `pending` and `provisioning` while it is being created, `active` once ready, `deleting`/`deleted` on the way out, `failed` if it could not be made, `unknown` when it could not be checked. One of `pending`, `provisioning`, `active`, `deleting`, `deleted`, `failed`, `unknown`.
- `in_sync` (boolean, required): False while an attach or detach has not reached the address yet.
- `created_at` (string or null): When it was created (RFC 3339, UTC).
- `updated_at` (string or null): When it last changed (RFC 3339, UTC).
## The port forwarding rule object



- `id` (string, required): e.g. `pfr_...`.
- `public_ip_id` (string, required)
- `instance_id` (string, required)
- `instance_name` (string or null): The target instance's name; null when it is gone.
- `protocol` (string, required): `tcp` or `udp`. One of `tcp`, `udp`.
- `public_port_start` (integer, required)
- `public_port_end` (integer, required)
- `private_port_start` (integer, required)
- `private_port_end` (integer, required)
- `desired_state` (string, required): What you asked for: `present`, or `deleted` once you deleted it. One of `present`, `deleted`.
- `observed_state` (string, required): What the platform last observed: `pending` and `provisioning` while it is being created, `active` once ready, `deleting`/`deleted` on the way out, `failed` if it could not be made, `unknown` when it could not be checked. One of `pending`, `provisioning`, `active`, `deleting`, `deleted`, `failed`, `unknown`.
- `created_at` (string or null): When it was created (RFC 3339, UTC).
- `updated_at` (string or null): When it last changed (RFC 3339, UTC).
## List public IPs

```http
GET /public/v1/public-ips
```

Your VPC public IPs, or one VPC's with `network_id`.

Scope: `read`.

Query parameters:

- `network_id` (string)

Example:

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

Response `200`:

```json
{
  "data": [
    {
      "id": "pip_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "network_id": "net_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "network_name": "prod",
      "purpose": "static_nat",
      "address": "102.211.122.80",
      "instance_id": "vm_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "instance_name": "web-1",
      "region": "af-abj",
      "zone": "af-abj-1",
      "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`: public IPs 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.
## Allocate a public IP

```http
POST /public/v1/public-ips
```

Allocates a public IPv4 address for a VPC. `static_nat` (the default) maps every port of the address to one instance in `network_id`, sent as `instance_id`; `port_forwarding`, where offered, carries per-port rules instead (add them with *Add a port forwarding rule*). The network must be active. A public IP is billed monthly: see *Price a public IP*.

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`. The address is in the public IP's `address` once it succeeds.

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.

Body:

- `network_id` (string, required): The VPC network to allocate the address in. It must be active.
- `purpose` (string): `static_nat` maps every port to one instance (`instance_id`). `port_forwarding` carries per-port rules instead, and `load_balancer` carries load balancers. Defaults to `static_nat`. One of `static_nat`, `port_forwarding`, `load_balancer`. Defaults to `static_nat`.
- `instance_id` (string): For `static_nat` only: an instance in `network_id` without a public IP. Leave it out to reserve the address now and attach it later (`POST /public/v1/public-ips/{public_ip_id}/attach`). Leave out for `port_forwarding` and `load_balancer`.

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/public-ips" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "network_id": "net_06gk3x9tq7m2v8r4y1c5d0zp8w",
    "instance_id": "vm_06gk3x9tq7m2v8r4y1c5d0zp8w"
  }'
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "pip_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`: this API key has only the `read` scope. Use a key with `write` access.
`FORBIDDEN`: the member who created this key may not do this in the organization, or has left it. 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. An owner can turn it on in the console under Organization › AI apps.
`STATIC_NAT_LIMIT_EXCEEDED`: your organization has reached its limit of static NAT public IPs. Release one you no longer need, or contact support for a higher limit.
`PUBLIC_IP_LIMIT_EXCEEDED`: your organization has reached its limit of public IPs of any purpose (20 by default). Release one you no longer need, or contact support for a higher limit.
- `404`: `NOT_FOUND`: public IPs are not offered on this platform yet.
`RESOURCE_NOT_FOUND`: `network_id` is not one of your VPC networks.
- `409`: `NETWORK_NOT_READY`: public IPs can only be added to an active VPC network. Wait for the network to be active.
`INSTANCE_ALREADY_HAS_PUBLIC_IP`: the instance already has a public IP. Release it before adding another.
`ACCOUNT_SUSPENDED`: an invoice is overdue, or the organization was suspended for non-payment. Pay the outstanding invoice in the console, then retry.
`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`:
`network_id` `REQUIRED`: send the VPC network.
`purpose` `PURPOSE_NOT_AVAILABLE`: use `static_nat`, `port_forwarding` or `load_balancer`.
`instance_id` `INSTANCE_NOT_IN_NETWORK`: the instance is not in `network_id`.
`instance_id` `NOT_ALLOWED`: a `port_forwarding` address takes no instance; each rule names its own.
`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`.
## Price a public IP

```http
GET /public/v1/public-ips/quote
```

What one more public IP costs per month, in your organization's currency.

Scope: `read`.

Example:

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

Response `200`:

```json
{
  "currency": "NGN",
  "monthly_minor": 1700000,
  "nominal_month_hours": 730
}
```

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`: cost quotes are not available 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.
## Get a public IP

```http
GET /public/v1/public-ips/{public_ip_id}
```

One public IP, with the instance it maps to.

Scope: `read`.

Path parameters:

- `public_ip_id`: The resource's id.

Example:

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

Response `200`:

```json
{
  "id": "pip_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "network_id": "net_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "network_name": "prod",
  "purpose": "static_nat",
  "address": "102.211.122.80",
  "instance_id": "vm_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "instance_name": "web-1",
  "region": "af-abj",
  "zone": "af-abj-1",
  "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 public IP with this id in your organization, or the id is not a public IP id (`pip_…`). Check the id.
`NOT_FOUND`: public IPs 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.
## Release a public IP

```http
DELETE /public/v1/public-ips/{public_ip_id}
```

Releases a public IP. The address is returned to the pool and may be given to someone else; billing stops when the operation succeeds. Delete its port forwarding rules first.

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:

- `public_ip_id`: The resource's id.

Example:

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

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "pip_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 public IP with this id in your organization, or the id is not a public IP id (`pip_…`). Check the id.
`NOT_FOUND`: public IPs are not offered on this platform yet.
- `409`: `PUBLIC_IP_HAS_PORT_FORWARDS`: delete this address's port forwarding rules first.
`PUBLIC_IP_HAS_LOAD_BALANCERS`: delete this address's load balancers first.
`INVALID_RESOURCE_STATE`: the public IP cannot do this in its current state, for example while it is being created or deleted. Wait for its current operation to finish, then retry.
`CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
- `422`: `VALIDATION_FAILED` with no `errors` entry: the platform refused the request, for example because the resource has already been deleted. Read the resource to check its state.
- `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`.
## Attach a public IP to an instance

```http
POST /public/v1/public-ips/{public_ip_id}/attach
```

Points a static NAT public IP at an instance in the same private network (VPC), keeping the address. Use it to move an address from one instance to another without releasing it: you keep the same IP and pay for it once. If the address is attached elsewhere it moves; the instance must not already have a static NAT address of its own.

Returns `202 Accepted` with an `operation_id` (also in the `Location` header). Poll `GET /public/v1/operations/{operation_id}`, or stream `/events`, until `status` is `succeeded` or `failed`. While it runs the address shows `in_sync: false`. Send an `Idempotency-Key`; a change that changes nothing returns an empty `operation_id`.

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

Path parameters:

- `public_ip_id`: The resource's id.

Body:

- `instance_id` (string, required): An instance in the address's private network.

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/public-ips/pip_06gk3x9tq7m2v8r4y1c5d0zp8w/attach" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "instance_id": "vm_06gk3x9tq7m2v8r4y1c5d0zp8w"
  }'
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "pip_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 public IP with this id in your organization, or the id is not a public IP id (`pip_…`). Check the id.
`NOT_FOUND`: public IPs are not offered on this platform yet.
- `409`: `PUBLIC_IP_NOT_STATIC_NAT`: only a static NAT address can be attached or detached; port forwarding and load balancer addresses use their rules.
`INSTANCE_ALREADY_HAS_PUBLIC_IP`: the instance already has a static NAT address. Detach or release that one first.
`INVALID_RESOURCE_STATE`: the public IP cannot do this in its current state, for example while it is being created or deleted. Wait for its current operation to finish, then retry.
`CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
- `422`: `VALIDATION_FAILED`: `instance_id` `REQUIRED`, or `INSTANCE_NOT_IN_NETWORK` (the instance must be in the address's private network). With no `errors` entry: the platform refused the request, for example because the instance is deleted.
- `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`.
## List port forwarding rules

```http
GET /public/v1/public-ips/{public_ip_id}/port-forwarding-rules
```

The address's port forwarding rules.

Scope: `read`.

Path parameters:

- `public_ip_id`: The resource's id.

Example:

```sh
curl "https://api.pantechdynamics.com/public/v1/public-ips/pip_06gk3x9tq7m2v8r4y1c5d0zp8w/port-forwarding-rules" \
  -H "Authorization: Bearer $PANTECH_API_KEY"
```

Response `200`:

```json
{
  "data": [
    {
      "id": "pfr_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "public_ip_id": "pip_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "instance_id": "vm_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "instance_name": "web-1",
      "protocol": "tcp",
      "public_port_start": 443,
      "public_port_end": 443,
      "private_port_start": 8443,
      "private_port_end": 8443,
      "desired_state": "present",
      "observed_state": "active",
      "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`: `RESOURCE_NOT_FOUND`: no public IP with this id in your organization, or the id is not a public IP id (`pip_…`). Check the id.
`NOT_FOUND`: port forwarding is 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.
## Detach a public IP from its instance

```http
POST /public/v1/public-ips/{public_ip_id}/detach
```

Unmaps a static NAT public IP from its instance. The address stays yours (and is billed once, as before) until you attach it to another instance or release it. The instance keeps running and can then be deleted.

Returns `202 Accepted` with an `operation_id` (also in the `Location` header). Poll `GET /public/v1/operations/{operation_id}`, or stream `/events`, until `status` is `succeeded` or `failed`. While it runs the address shows `in_sync: false`. Send an `Idempotency-Key`; a change that changes nothing returns an empty `operation_id`.

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

Path parameters:

- `public_ip_id`: The resource's id.

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/public-ips/pip_06gk3x9tq7m2v8r4y1c5d0zp8w/detach" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "pip_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 public IP with this id in your organization, or the id is not a public IP id (`pip_…`). Check the id.
`NOT_FOUND`: public IPs are not offered on this platform yet.
- `409`: `PUBLIC_IP_NOT_STATIC_NAT`: only a static NAT address can be attached or detached; port forwarding and load balancer addresses use their rules.
`INVALID_RESOURCE_STATE`: the public IP cannot do this in its current state, for example while it is being created or deleted. Wait for its current operation to finish, then retry.
`CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
- `422`: `VALIDATION_FAILED` with no `errors` entry: the platform refused the request, for example because the resource has already been deleted. Read the resource to check its state.
- `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`.
## Add a port forwarding rule

```http
POST /public/v1/public-ips/{public_ip_id}/port-forwarding-rules
```

Forwards a port or range of a `port_forwarding` public IP to an instance in its VPC. Defaults: `public_port_end` equals `public_port_start`, and the private range equals the public one (or starts at `private_port_start` with the same width).

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`. The rule's id is the operation's `resource_id`.

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:

- `public_ip_id`: The resource's id.

Body:

- `instance_id` (string, required): An instance in the public IP's VPC.
- `protocol` (string, required): `tcp` or `udp`. One of `tcp`, `udp`.
- `public_port_start` (integer, required): First public port, 1 to 65535.
- `public_port_end` (integer): Last public port. Defaults to `public_port_start` (one port).
- `private_port_start` (integer): First port on the instance. Defaults to `public_port_start`.
- `private_port_end` (integer): Last port on the instance; the private range must be as wide as the public one. Defaults to match it.

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/public-ips/pip_06gk3x9tq7m2v8r4y1c5d0zp8w/port-forwarding-rules" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "instance_id": "vm_06gk3x9tq7m2v8r4y1c5d0zp8w",
    "protocol": "tcp",
    "public_port_start": 443
  }'
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "pfr_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 public IP with this id in your organization, or the id is not a public IP id (`pip_…`). Check the id.
`NOT_FOUND`: port forwarding is not offered on this platform yet.
- `409`: `PUBLIC_IP_NOT_FOR_PORT_FORWARDING`: use an active public IP with `purpose` `port_forwarding`.
`PORTS_IN_USE`: another rule on this address already forwards some of these ports for this protocol. Choose other ports, or delete that rule.
`INVALID_RESOURCE_STATE`: the public IP cannot do this in its current state, for example while it is being created or deleted. Wait for its current operation to finish, then retry.
`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`:
`instance_id` or `protocol` `REQUIRED`: send both.
`instance_id` `INSTANCE_NOT_IN_NETWORK`: the instance is not in the address's VPC.
`public_port_start` `INVALID_PORTS`: use `tcp` or `udp`, ports 1 to 65535, and public and private ranges of the same width.
`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 port forwarding rule

```http
GET /public/v1/public-ips/{public_ip_id}/port-forwarding-rules/{rule_id}
```

One port forwarding rule on the address.

Scope: `read`.

Path parameters:

- `public_ip_id`: The resource's id.
- `rule_id`: The rule's id (`aclr_...` for a firewall rule, `pfr_...` for a port forwarding rule).

Example:

```sh
curl "https://api.pantechdynamics.com/public/v1/public-ips/pip_06gk3x9tq7m2v8r4y1c5d0zp8w/port-forwarding-rules/pfr_06gk3x9tq7m2v8r4y1c5d0zp8w" \
  -H "Authorization: Bearer $PANTECH_API_KEY"
```

Response `200`:

```json
{
  "id": "pfr_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "public_ip_id": "pip_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "instance_id": "vm_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "instance_name": "web-1",
  "protocol": "tcp",
  "public_port_start": 443,
  "public_port_end": 443,
  "private_port_start": 8443,
  "private_port_end": 8443,
  "desired_state": "present",
  "observed_state": "active",
  "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 rule with this id on this address, or no such address in your organization. Check both ids.
`NOT_FOUND`: port forwarding is 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.
## Delete a port forwarding rule

```http
DELETE /public/v1/port-forwarding-rules/{rule_id}
```

Deletes one port forwarding rule. Traffic to those ports stops once the operation succeeds.

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:

- `rule_id`: The rule's id (`aclr_...` for a firewall rule, `pfr_...` for a port forwarding rule).

Example:

```sh
curl -X DELETE "https://api.pantechdynamics.com/public/v1/port-forwarding-rules/pfr_06gk3x9tq7m2v8r4y1c5d0zp8w" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "pfr_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 port forwarding rule with this id in your organization, or the id is not a port forwarding rule id (`pfr_…`). Check the id.
`NOT_FOUND`: port forwarding is not offered on this platform yet.
- `409`: `INVALID_RESOURCE_STATE`: the port forwarding rule cannot do this in its current state, for example while it is being created or deleted. Wait for its current operation to finish, then retry.
`CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
- `422`: `VALIDATION_FAILED` with no `errors` entry: the platform refused the request, for example because the resource has already been deleted. Read the resource to check its state.
- `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`.
