# Security groups

Reusable firewalls for standard instances.

Source: https://docs.pantechdynamics.com/api/security-groups

A security group is a set of rules shared by every standard instance that uses it. Inbound traffic no rule allows is dropped. Each organization has a default group, used when an instance names none. VPC instances use their subnet's [firewall rules](https://docs.pantechdynamics.com/api/networks) instead.

Endpoints:

- `GET /public/v1/security-groups`: List security groups
- `POST /public/v1/security-groups`: Create a security group
- `GET /public/v1/security-groups/{security_group_id}`: Get a security group
- `PUT /public/v1/security-groups/{security_group_id}/rules`: Replace a security group's rules
- `DELETE /public/v1/security-groups/{security_group_id}`: Delete a security group

## The security group object

A reusable firewall for standard instances.

- `id` (string, required): e.g. `sg_...`.
- `name` (string, required): Its name, unique among your live security groups. The organization's default group is called `default`.
- `rules` (array of SecurityGroupRule, required): Its rules. Inbound traffic no rule allows is dropped.
  - `direction` (string, required): `ingress` (into the instance) or `egress` (out of it). One of `ingress`, `egress`.
  - `protocol` (string, required): `tcp`, `udp`, `icmp` or `all`. One of `tcp`, `udp`, `icmp`, `all`.
  - `port_range` (string): For `tcp`/`udp`: one port (`22`) or an inclusive range (`8000-8100`). Empty or left out for `icmp` and `all`, meaning every port.
  - `cidr` (string, required): One IPv4 CIDR: the source for `ingress`, the destination for `egress`, e.g. `203.0.113.4/32`. Avoid `0.0.0.0/0` on ingress where you can: it blocks private database network interfaces on instances using the group.
- `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 security groups

```http
GET /public/v1/security-groups
```

Your security groups, newest first, with their rules. Filter by name with `q`, or by state.

Scope: `read`.

Query parameters:

- `limit` (integer): Items per page, 1 to 100. Defaults to 50. From 1 to 100. Defaults to `50`.
- `cursor` (string): The previous page's `next_cursor`, to get the next page. Leave out for the first page; keep the other parameters the same.
- `q` (string): Only items whose name contains this text (case-insensitive), at most 255 characters. At most 255 characters.
- `desired_state` (string): Only items with this `desired_state`: `present` or `deleted`. One of `present`, `deleted`.
- `observed_state` (string): Only items with this `observed_state`. One of `pending`, `provisioning`, `active`, `deleting`, `deleted`, `failed`, `unknown`.

Example:

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

Response `200`:

```json
{
  "data": [
    {
      "id": "sg_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "name": "web",
      "rules": [
        {
          "direction": "ingress",
          "protocol": "tcp",
          "port_range": "22",
          "cidr": "203.0.113.4/32"
        }
      ],
      "desired_state": "present",
      "observed_state": "active",
      "created_at": "2026-10-03T09:30:00Z",
      "updated_at": "2026-10-03T09:30:00Z"
    }
  ],
  "next_cursor": "eyJhdCI6IjIwMjYtMTAtMDNUMDk6MzA6MDBaIiwiaWQiOiJ2bV8wNmdrIn0"
}
```

Errors:

- `400`: `INVALID_PAGE`: `limit` is not a whole number from 1 to 100. Send a value in that range, or leave it out for 50.
`INVALID_CURSOR`: `cursor` does not belong to this list with these filters. Start again without a cursor.
`INVALID_FILTER`: `q` is longer than 255 characters, or `desired_state` or `observed_state` is not one of its listed values. Fix the parameter named in `detail`.
- `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.
- `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 security group

```http
POST /public/v1/security-groups
```

Creates a reusable firewall for standard instances in a security-group zone (`af-abj-1`). Each rule has a `direction`, a `protocol` (`tcp`, `udp`, `icmp` or `all`), a `port_range` for `tcp`/`udp` and one IPv4 `cidr`. Inbound traffic no rule allows is dropped. A group needs at least one rule.

The name is unique among your live security groups; a deleted group frees its name. Attach the group to an instance at create (`security_group_id`) or with *Change an instance's security group*, or to a database as a source of allowed address ranges.

Prefer narrow sources: a rule from `0.0.0.0/0` stops an instance from attaching a private database network interface.

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 group'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.

Body:

- `name` (string, required): 1 to 255 characters, unique among your live security groups. At most 255 characters.
- `rules` (array of SecurityGroupRule): The group's rules; at least one. Inbound traffic no rule allows is dropped.
  - `direction` (string, required): `ingress` (into the instance) or `egress` (out of it). One of `ingress`, `egress`.
  - `protocol` (string, required): `tcp`, `udp`, `icmp` or `all`. One of `tcp`, `udp`, `icmp`, `all`.
  - `port_range` (string): For `tcp`/`udp`: one port (`22`) or an inclusive range (`8000-8100`). Empty or left out for `icmp` and `all`, meaning every port.
  - `cidr` (string, required): One IPv4 CIDR: the source for `ingress`, the destination for `egress`, e.g. `203.0.113.4/32`. Avoid `0.0.0.0/0` on ingress where you can: it blocks private database network interfaces on instances using the group.

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/security-groups" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "web",
    "rules": [
      {
        "direction": "ingress",
        "protocol": "tcp",
        "port_range": "443",
        "cidr": "0.0.0.0/0"
      },
      {
        "direction": "ingress",
        "protocol": "tcp",
        "port_range": "22",
        "cidr": "203.0.113.4/32"
      }
    ]
  }'
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "sg_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.
- `409`: `SECURITY_GROUP_NAME_TAKEN`: you already have a security group with this name. Choose a different name.
`SECURITY_GROUPS_NOT_SUPPORTED_IN_ZONE`: this zone uses VPC firewall rules, not security groups.
`PROVIDER_ACCOUNT_NOT_READY`: your organization's cloud account is still being set up, which takes a few minutes after sign-up. Retry shortly.
`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` `REQUIRED` or `NAME_TOO_LONG`: send a name of 1 to 255 characters.
`UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
`VALIDATION_FAILED` with no `errors` entry: the rule list is empty, or a rule has an unknown `direction` or `protocol` or an invalid IPv4 `cidr`. Send at least one valid rule.
- `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 security group

```http
GET /public/v1/security-groups/{security_group_id}
```

One security group and its rules.

Scope: `read`.

Path parameters:

- `security_group_id`: The resource's id.

Example:

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

Response `200`:

```json
{
  "id": "sg_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "name": "web",
  "rules": [
    {
      "direction": "ingress",
      "protocol": "tcp",
      "port_range": "22",
      "cidr": "203.0.113.4/32"
    }
  ],
  "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 security group with this id in your organization, or the id is not a security group id (`sg_…`). Check the id.
- `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.
## Replace a security group's rules

```http
PUT /public/v1/security-groups/{security_group_id}/rules
```

Replaces the group's complete rule list, for every instance using it. Each rule has a `direction`, a `protocol` (`tcp`, `udp`, `icmp` or `all`), a `port_range` for `tcp`/`udp` and one IPv4 `cidr`. Inbound traffic no rule allows is dropped. A group needs at least one rule.

Databases the group is attached to are updated automatically; a list that would give one of them more than 1024 address ranges is refused. While an instance using the group has a private database network interface, an ingress rule whose source overlaps the private network's range (`10.250.0.0/20`), `0.0.0.0/0` included, is refused.

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:

- `security_group_id`: The resource's id.

Body:

- `rules` (array of SecurityGroupRule, required): The complete new rule list; at least one rule. It replaces every existing rule.
  - `direction` (string, required): `ingress` (into the instance) or `egress` (out of it). One of `ingress`, `egress`.
  - `protocol` (string, required): `tcp`, `udp`, `icmp` or `all`. One of `tcp`, `udp`, `icmp`, `all`.
  - `port_range` (string): For `tcp`/`udp`: one port (`22`) or an inclusive range (`8000-8100`). Empty or left out for `icmp` and `all`, meaning every port.
  - `cidr` (string, required): One IPv4 CIDR: the source for `ingress`, the destination for `egress`, e.g. `203.0.113.4/32`. Avoid `0.0.0.0/0` on ingress where you can: it blocks private database network interfaces on instances using the group.

Example:

```sh
curl -X PUT "https://api.pantechdynamics.com/public/v1/security-groups/sg_06gk3x9tq7m2v8r4y1c5d0zp8w/rules" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "rules": [
      {
        "direction": "ingress",
        "protocol": "tcp",
        "port_range": "22",
        "cidr": "203.0.113.4/32"
      },
      {
        "direction": "ingress",
        "protocol": "tcp",
        "port_range": "5432",
        "cidr": "10.0.1.0/24"
      }
    ]
  }'
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "sg_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 security group with this id in your organization, or the id is not a security group id (`sg_…`). Check the id.
- `409`: `INVALID_RESOURCE_STATE`: the security group 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`:
`rules` `REQUIRED`: send the complete rule list.
`rules` `RULE_ALLOWS_PRIVATE_NETWORK`: an instance using this group has a private database network interface, and a rule's source overlaps the private network's range; the message names the range and the rules. Replace `0.0.0.0/0` (and any other overlapping source) with narrower ranges that leave out `10.250.0.0/20`.
`rules` `DATABASE_ACCESS_RANGE_LIMIT_EXCEEDED`: a database this group is attached to would get more than 1024 address ranges. Use fewer or wider (but at most /8) ranges, or detach the group from that database.
`UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
`VALIDATION_FAILED` with no `errors` entry: the list is empty, or a rule has an unknown `direction` or `protocol` or an invalid IPv4 `cidr`. Send at least one valid rule.
- `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 security group

```http
DELETE /public/v1/security-groups/{security_group_id}
```

Deletes a security group. It must not be used by any instance. The organization's default group cannot be deleted. A group attached to a database cannot be deleted either: detach it with `PUT /public/v1/databases/{database_id}/security-groups` 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:

- `security_group_id`: The resource's id.

Example:

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

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "sg_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 security group with this id in your organization, or the id is not a security group id (`sg_…`). Check the id.
- `409`: `DEFAULT_SECURITY_GROUP_UNDELETABLE`: the organization's default group cannot be deleted. You can change its rules instead.
`SECURITY_GROUP_ATTACHED_TO_DATABASE`: a database uses this group; `detail` names it. Detach the group from that database, then retry.
`INVALID_RESOURCE_STATE`: instances still use this group, or it is already being deleted. Move those instances to another group first.
`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`.
