# Databases

Managed PostgreSQL, MySQL and MariaDB: create, power, secure, grow, snapshot and delete databases.

Source: https://docs.pantechdynamics.com/api/databases

A database is a single-node PostgreSQL, MySQL or MariaDB server that the platform runs and patches for you, on its own VM sized by a [plan](https://docs.pantechdynamics.com/api/catalogue). It never has a public address: connect to its `hostname` from your own network, through the CIDRs its access rules allow. See [Managed databases](https://docs.pantechdynamics.com/databases/overview) for the guide.

Creating one places an [order](https://docs.pantechdynamics.com/api/database-orders), paid for like an instance. Every other change returns an [operation](https://docs.pantechdynamics.com/api/operations) to follow. The admin password is never stored or returned, except once: in the response that generated it.

The data disk starts at the plan's size, or `storage_gb`, and [grows later](https://docs.pantechdynamics.com/databases/storage) without a restart. [Snapshots](https://docs.pantechdynamics.com/databases/snapshots) of it are crash-consistent copies, listed under the database and in the account's [snapshots](https://docs.pantechdynamics.com/api/snapshots). [Database error codes](https://docs.pantechdynamics.com/databases/errors) explains every refusal.

Endpoints:

- `GET /public/v1/database-engines`: List database engines
- `GET /public/v1/databases`: List databases
- `POST /public/v1/databases`: Create a database
- `GET /public/v1/databases/{database_id}`: Get a database
- `DELETE /public/v1/databases/{database_id}`: Delete a database
- `POST /public/v1/databases/{database_id}/start`: Start a database
- `POST /public/v1/databases/{database_id}/stop`: Stop a database
- `PUT /public/v1/databases/{database_id}/access-rules`: Replace access rules
- `PUT /public/v1/databases/{database_id}/password`: Set the password
- `POST /public/v1/databases/{database_id}/reset-password`: Reset the password
- `PUT /public/v1/databases/{database_id}/security-groups`: Attach security groups
- `POST /public/v1/databases/{database_id}/resize-storage`: Grow a database's storage
- `GET /public/v1/databases/{database_id}/snapshots`: List a database's snapshots
- `POST /public/v1/databases/{database_id}/snapshots`: Snapshot a database
- `GET /public/v1/databases/{database_id}/snapshots/{snapshot_id}`: Get a database's snapshot
- `DELETE /public/v1/databases/{database_id}/snapshots/{snapshot_id}`: Delete a database's snapshot

## The database object



- `admin_username` (string, required): The admin login to connect as: the one chosen at create, or `dbadmin`. `pantech_admin` for a database created before the login could be chosen.
- `security_group_ids` (array of string, required): Your security groups attached to the database, in the order attached (at most 5). `[]` when none.
- `effective_access_rules` (array of DatabaseEffectiveAccessRule, required): The allow-list as the database applies it: its own `access_rules` plus the ranges its security groups add, each range once under its first source. In effect once `observed_generation` equals `generation`.
  - `cidr` (string, required): An allowed IPv4 range, in canonical form.
  - `protocol` (string, required): Always `tcp`. One of `tcp`.
  - `port` (integer, required): The engine's port: 5432 or 3306.
  - `source` (string, required): Where the range comes from: `access_rule`, or `security_group:` followed by the group's id (e.g. `security_group:sg_…`).
- `ignored_security_group_rules` (array of DatabaseIgnoredSecurityGroupRule, required): Rules of the attached security groups that add nothing to the database, and why. `[]` when none.
  - `security_group_id` (string, required): The attached group the rule belongs to.
  - `direction` (string, required): The rule's direction. One of `ingress`, `egress`.
  - `protocol` (string, required): The rule's protocol. One of `tcp`, `udp`, `icmp`, `all`.
  - `port_range` (string or null, required): `80`, `3000-4000`, or null for all ports.
  - `cidr` (string, required): The rule's CIDR.
  - `reason` (string, required): Why it adds nothing: `egress_rule`, `protocol_not_tcp` (udp or icmp), `port_not_covered` (its ports exclude the engine's), `cidr_too_wide` (wider than /8, including `0.0.0.0/0`) or `cidr_not_ipv4`. One of `egress_rule`, `protocol_not_tcp`, `port_not_covered`, `cidr_too_wide`, `cidr_not_ipv4`.
- `id` (string, required): The database's id, `db_` followed by 26 characters.
- `name` (string, required): Its name, unique among your databases.
- `engine` (string, required): `postgresql`, `mysql` or `mariadb`. One of `postgresql`, `mysql`, `mariadb`.
- `version` (string, required): The engine's version line, e.g. `18` or `8.4`. Minor updates within the line are applied by the platform.
- `port` (integer, required): The TCP port to connect to: 5432 for PostgreSQL, 3306 for MySQL and MariaDB.
- `plan_id` (string, required): The plan setting its vCPU and memory.
- `data_volume_size_gb` (integer, required): The data disk's size in GB, as in place and billed.
- `pending_data_volume_size_gb` (integer or null, required): The size a storage resize in flight is growing the data disk to; null when none.
- `zone_id` (string, required): The zone it runs in, e.g. `af-abj-2`.
- `subnet_id` (string or null, required): Its VPC subnet in a VPC zone; null in a standard zone.
- `hostname` (string or null, required): The name to connect to, e.g. `db-7k2q9x4m1a.af-abj-2.db.pantechdynamics.com`. It resolves to `private_ip` and works only from where that address is reachable. Null until assigned.
- `private_ip` (string or null, required): The database's private address, never public. Null until assigned.
- `desired_state` (string, required): What you asked for: `running`, `stopped` or `deleted`. One of `running`, `stopped`, `deleted`.
- `observed_state` (string, required): What the platform last observed. A change is complete when it matches `desired_state`; `failed` means the last change could not be made (see the failure fields), and `unknown` means the platform could not check. One of `pending`, `provisioning`, `running`, `stopping`, `stopped`, `deleting`, `deleted`, `failed`, `unknown`.
- `generation` (integer, required): Increases by one with every change you request.
- `observed_generation` (integer, required): The `generation` last applied: equal to `generation` once the last change (access rules, security groups, password) is in effect.
- `access_rules` (array of DatabaseAccessRule, required): The database's own allow-list, as set at create or with *Replace access rules*.
  - `cidr` (string, required): An allowed IPv4 range, in canonical form.
  - `protocol` (string, required): Always `tcp`. One of `tcp`.
  - `port` (integer, required): The engine's port: 5432 or 3306.
- `failure_code` (string or null, required): A stable code for why the last change failed; null when nothing failed. The failed operation's `failure.reason` explains it.
- `created_at` (string or null, required): When it was created (RFC 3339, UTC).
- `updated_at` (string or null, required): When it last changed (RFC 3339, UTC).
## The database engine object



- `engine` (string, required): The value `engine` takes in *Create a database*: `postgresql`, `mysql` or `mariadb`.
- `display_name` (string, required): The engine's name for display.
- `port` (integer, required): The port its databases listen on: 5432 or 3306.
- `versions` (array of object, required): Version lines offered for new databases, long-term-support lines only. Empty when none is offered right now.
  - `version` (string, required)
  - `eol_date` (string or null, required)
  - `zones` (array of string, required)
- `storage` (array of object, required): The data disk sizes a new database of this engine may have, per zone: use them for `storage_gb` in *Create a database* and *Grow a database's storage*. One entry per zone where a version of this engine can be created and database storage is available. Read the allowed sizes from here rather than hard-coding them.
  - `zone_id` (string, required): The zone, e.g. `af-abj-2`.
  - `min_gb` (integer, required): The zone's minimum data disk in GB. 0 means the plan's `disk_gb` is the minimum; otherwise the minimum is the larger of the plan's `disk_gb` and this value. At least 0.
  - `min_is_plan_disk` (boolean, required): True when `min_gb` is 0, so the chosen plan's `disk_gb` is the minimum.
  - `max_gb` (integer, required): The largest data disk in GB.
  - `step_gb` (integer, required): Above the base size (the minimum), `storage_gb` must be a multiple of this.
  - `price_per_gb_month_minor` (integer or null, required): Price of one GB of data disk for a 730-hour month, in minor units of `currency`; null when it could not be priced.
  - `currency` (string or null, required): `NGN` or `USD`; null when not priced.
## List database engines

```http
GET /public/v1/database-engines
```

The engines and version lines you can create a managed database with: PostgreSQL, MySQL and MariaDB, long-term-support lines only. A version appears only while it is offered for new databases, and `zones` lists the zones it can be created in. An engine with no offered version has an empty `versions` list.

Use `engine` and a `version` from here, and a zone from that version's `zones`, in *Create a database*.

Scope: `read`.

Example:

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

Response `200`:

```json
{
  "data": [
    {
      "engine": "postgresql",
      "display_name": "PostgreSQL",
      "port": 5432,
      "versions": [
        {
          "version": "18",
          "eol_date": "2030-11-14",
          "zones": [
            "af-abj-2"
          ]
        }
      ],
      "storage": [
        {
          "zone_id": "af-abj-2",
          "min_gb": 0,
          "min_is_plan_disk": true,
          "max_gb": 2000,
          "step_gb": 10,
          "price_per_gb_month_minor": 15000,
          "currency": "NGN"
        }
      ]
    }
  ],
  "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.
- `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.
## List databases

```http
GET /public/v1/databases
```

Your managed databases, oldest first, with each one's hostname, state and access rules. Filter by name with `q`, or by state. Never includes a password.

Databases whose create failed are hidden by default; list them with `observed_state=failed` (their failure is in `failure_code`), and delete them to clean up.

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`: `running`, `stopped` or `deleted`. One of `running`, `stopped`, `deleted`.
- `observed_state` (string): Only items with this `observed_state`. One of `pending`, `provisioning`, `running`, `stopping`, `stopped`, `deleting`, `deleted`, `failed`, `unknown`.

Example:

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

Response `200`:

```json
{
  "data": [
    {
      "admin_username": "dbadmin",
      "security_group_ids": [],
      "effective_access_rules": [
        {
          "cidr": "10.0.1.0/24",
          "protocol": "tcp",
          "port": 5432,
          "source": "access_rule"
        }
      ],
      "ignored_security_group_rules": [],
      "id": "db_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "name": "orders-db",
      "engine": "postgresql",
      "version": "18",
      "port": 5432,
      "plan_id": "starter",
      "data_volume_size_gb": 80,
      "pending_data_volume_size_gb": null,
      "zone_id": "af-abj-2",
      "subnet_id": "snet_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "hostname": "db-7k2q9x4m1a.af-abj-2.db.pantechdynamics.com",
      "private_ip": "10.0.1.12",
      "desired_state": "running",
      "observed_state": "running",
      "generation": 1,
      "observed_generation": 1,
      "access_rules": [
        {
          "cidr": "10.0.1.0/24",
          "protocol": "tcp",
          "port": 5432
        }
      ],
      "failure_code": null,
      "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 database

```http
POST /public/v1/databases
```

Creates a single-node PostgreSQL, MySQL or MariaDB database on its own server, sized by a plan (vCPU and memory), with its own data disk, and never on a public address. You connect to it by its `hostname` from your own network.

**Placement.** Pick a zone where your engine version is offered (`GET /public/v1/database-engines`). In a VPC zone (`af-abj-2`) `subnet_id` is required, and unless you send `access_rules` the database starts allowing the subnet's VPC CIDR. In a standard zone (`af-abj-1`) leave out `subnet_id`; the database starts allowing nobody until you add access rules or attach security groups, and your instances reach it after *Add a private database network interface*.

**Paid first, like an instance.** The request records an order and returns `202 Accepted` with it (`order_id`, also the `Location` header). The initial payment is then taken from credit, or else from the organization's default card, and the database is provisioned. It costs exactly what an instance of the same plan with no public address costs, with `storage_gb` as its disk (`amount_minor` is the upfront payment). Follow `GET /public/v1/database-orders/{order_id}` until `status` is `provisioned`, then read the database; `payment_failed` and `failed` are final, and any payment taken is returned to your credit.

**Admin login.** Send `admin_username` to name the database's admin login, or leave it out for `dbadmin`. It is not a superuser: on PostgreSQL and MySQL it can create further users and databases; on MariaDB it is the only login.

**Admin password.** Send `password` to choose it (16 to 128 printable ASCII characters, with no spaces, quotes or backslashes); it is never returned. Leave it out and a 32-character password is generated and **returned once, in this response** (`password`). Until provisioning starts the password is held only encrypted, then erased: it cannot be read again (use *Reset the password* for a new one). Every response is `Cache-Control: no-store`. Infrastructure-as-code tools should keep the password out of state (for Terraform, a write-only attribute).

**Storage.** Leave out `storage_gb` for the plan's `disk_gb`, or ask for more: at most 2000 GB, and above the plan's size a multiple of 10 GB. Read each zone's allowed sizes and price from `storage` in `GET /public/v1/database-engines` rather than hard-coding them. It can be grown later with *Grow a database's storage*, never shrunk.

**Idempotency.** Send an `Idempotency-Key`. A retry with the same key returns the original order with `password_returned: false` and a null `password`: a generated password is shown only on the first response.

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

Body:

- `name` (string, required): 1 to 255 characters, unique among your databases. At most 255 characters.
- `engine` (string, required): `postgresql`, `mysql` or `mariadb`; see `GET /public/v1/database-engines`. One of `postgresql`, `mysql`, `mariadb`.
- `version` (string, required): A version line offered for `engine` in `zone_id`, from `GET /public/v1/database-engines`, e.g. `18` for PostgreSQL or `8.4` for MySQL.
- `plan_slug` (string, required): The size, from `GET /public/v1/plans`: its vCPU and memory, and its `disk_gb` as the smallest data disk.
- `zone_id` (string, required): Where to create it: a zone from the version's `zones`, e.g. `af-abj-1` (standard) or `af-abj-2` (VPC).
- `subnet_id` (string): Required in a VPC zone (`af-abj-2`): one of your subnets there, which the database joins. Leave out in a standard zone.
- `access_rules` (array of string): The initial allow-list: IPv4 CIDRs with a prefix of `/8` or longer, never `0.0.0.0/0`, at most 50, no range twice. Leave out for the zone's default (the subnet's VPC CIDR in a VPC zone, nobody in a standard zone); send `[]` for nobody. At most 50 items.
- `admin_username` (string): The admin login's name: 3 to 32 characters, lowercase letters, digits and underscores, starting with a letter. Reserved, and refused with `INVALID_DATABASE_ADMIN_USERNAME`: `root`, `postgres`, `public`, `none`, `all`, `user`, `sys`, `system`, `replication`, `current_user`, `current_role`, `session_user`, and any name starting with `pg_`, `mysql`, `mariadb` or `pantech`. Not secret. Defaults to `dbadmin`. From 3 to 32 characters. Defaults to `dbadmin`.
- `password` (string): Optional admin password: 16 to 128 printable ASCII characters, with no spaces, single quotes (`'`), double quotes (`"`) or backslashes (`\`). Write-only: never stored in plain text, never returned, never logged. Leave it out to have a 32-character password generated and returned once in the response. From 16 to 128 characters.
- `storage_gb` (integer): Data disk size in GB. Leave out for the plan's `disk_gb`. Otherwise at least the plan's `disk_gb` (or the zone's `min_gb` when larger), at most the zone's `max_gb` (2000 today), and above that base a multiple of the zone's `step_gb` (10 today); the base itself is always allowed. Read the zone's limits and price from `storage` in `GET /public/v1/database-engines` rather than hard-coding them. The upfront payment and the hourly storage price use this size. It can be grown later, never shrunk. At least 1.

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/databases" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "orders-db",
    "engine": "postgresql",
    "version": "18",
    "plan_slug": "starter",
    "zone_id": "af-abj-2",
    "subnet_id": "snet_06gk3x9tq7m2v8r4y1c5d0zp8w"
  }'
```

Response `202`:

```json
{
  "order_id": "ord_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "database_id": "db_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "db_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "status": "awaiting_payment",
  "operation_id": null,
  "failure_code": null,
  "amount_minor": 1504000,
  "currency": "NGN",
  "password_returned": true,
  "password": "q8Vn3TzK1mXw7RbLc4Hs9JdP2fYa6GeU",
  "admin_username": "dbadmin"
}
```

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.
- `402`: `INSUFFICIENT_CREDIT`: the initial payment is more than your available credit and the organization has no default card; `detail` gives both amounts. Buy credit (`POST /public/v1/billing/credit/purchases`) or add a card in the console, then retry.
- `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`: `ACCOUNT_SUSPENDED`: an invoice is overdue, or the organization was suspended for non-payment. Pay the outstanding invoice in the console, then retry.
`ACCOUNT_NOT_ACTIVE`: the organization's billing account is not active yet. Finish billing setup in the console (verify the organization and add a payment method), then retry.
`DATABASE_NAME_TAKEN`: you already have a database with this name. Choose a different name.
`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` `INVALID_DATABASE_NAME`: the name is empty or longer than 255 characters.
`engine` `INVALID_DATABASE_ENGINE`: use `postgresql`, `mysql` or `mariadb`.
`version` `INVALID_DATABASE_VERSION`: the version line is not offered for new databases. Pick one from `GET /public/v1/database-engines`.
`zone_id` `INVALID_DATABASE_ZONE`: the zone is missing or not one your organization can use. Pick one from `GET /public/v1/regions`.
`zone_id` `DATABASE_VERSION_NOT_AVAILABLE_IN_ZONE`: this version is not offered in this zone yet. Pick a zone from the version's `zones`.
`plan_slug` `PLAN_NOT_FOUND`: no active plan has this slug. Pick one from `GET /public/v1/plans`.
`subnet_id` `DATABASE_SUBNET_REQUIRED`: a VPC zone needs a subnet. Send the `subnet_id` of one of your subnets in that zone.
`subnet_id` `DATABASE_SUBNET_NOT_ALLOWED`: a standard zone takes no subnet. Leave out `subnet_id`.
`subnet_id` `DATABASE_SUBNET_NOT_FOUND`: no subnet with this id in your organization. Check the id.
`subnet_id` `DATABASE_SUBNET_WRONG_ZONE`: the subnet is in another zone. Use a subnet in `zone_id`.
`subnet_id` `DATABASE_SUBNET_NOT_USABLE`: the subnet is being deleted or has failed. Use another subnet.
`access_rules` `INVALID_ACCESS_RULE`: each rule must be an IPv4 CIDR with a prefix of /8 or longer, such as `10.0.1.0/24` or `10.250.3.17/32`; `0.0.0.0/0` is refused.
`access_rules` `TOO_MANY_ACCESS_RULES`: at most 50 rules. Merge neighbouring ranges, or attach a security group.
`access_rules` `DUPLICATE_ACCESS_RULE`: the same range appears twice (after masking host bits). Remove the repeat.
`admin_username` `INVALID_DATABASE_ADMIN_USERNAME`: use 3 to 32 lowercase letters, digits or underscores, starting with a letter, and not a reserved name (`root`, `postgres`, `public`, `none`, `all`, `user`, `sys`, `system`, `replication`, `current_user`, `current_role`, `session_user`, or a name starting with `pg_`, `mysql`, `mariadb` or `pantech`).
`password` `INVALID_DATABASE_PASSWORD`: use 16 to 128 printable ASCII characters with no spaces, single or double quotes, or backslashes; or leave it out to have one generated.
`storage_gb` `INVALID_DATABASE_STORAGE`: use at least the plan's `disk_gb`, at most 2000, and above the plan's size a multiple of 10. Leave it out for the plan's size.
`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.
`DATABASES_UNAVAILABLE`: managed databases are not available on this platform yet. Try again later, or contact support.
`DATABASE_STORAGE_UNAVAILABLE`: database storage is not offered in this zone yet. Choose another zone, or try again later.
`DATABASE_HOSTNAME_UNAVAILABLE`: a hostname could not be reserved for the database. Retry with a new `Idempotency-Key`.
`BILLING_UNAVAILABLE`: billing is not answering right now. 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 database

```http
GET /public/v1/databases/{database_id}
```

One database: its connection `hostname` and `port`, `admin_username`, state, data disk size, access rules, attached security groups and the allow-list it applies (`effective_access_rules`). Never includes a password.

A change you requested is in effect once `observed_generation` equals `generation`. Connect as `admin_username` to `hostname`:`port` from an address its allow-list includes.

Scope: `read`.

Path parameters:

- `database_id`: The database's id, e.g. `db_...`.

Example:

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

Response `200`:

```json
{
  "admin_username": "dbadmin",
  "security_group_ids": [],
  "effective_access_rules": [
    {
      "cidr": "10.0.1.0/24",
      "protocol": "tcp",
      "port": 5432,
      "source": "access_rule"
    }
  ],
  "ignored_security_group_rules": [],
  "id": "db_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "name": "orders-db",
  "engine": "postgresql",
  "version": "18",
  "port": 5432,
  "plan_id": "starter",
  "data_volume_size_gb": 80,
  "pending_data_volume_size_gb": null,
  "zone_id": "af-abj-2",
  "subnet_id": "snet_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "hostname": "db-7k2q9x4m1a.af-abj-2.db.pantechdynamics.com",
  "private_ip": "10.0.1.12",
  "desired_state": "running",
  "observed_state": "running",
  "generation": 1,
  "observed_generation": 1,
  "access_rules": [
    {
      "cidr": "10.0.1.0/24",
      "protocol": "tcp",
      "port": 5432
    }
  ],
  "failure_code": null,
  "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`: `DATABASE_NOT_FOUND`: no database with this id in your organization. Check the id: a database of another organization is reported the same way.
`RESOURCE_NOT_FOUND`: the id is not a database id (`db_…`). Check the path.
- `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 database

```http
DELETE /public/v1/databases/{database_id}
```

Deletes the database. Its hostname is removed first, then its server is retired. Its data disk and a safety snapshot of it are kept for a short retention period (12 and 24 hours) and then destroyed; nothing is billed for them during retention. Snapshots you took yourself are kept and stay billed until you delete them.

Once the delete succeeds, the part of the database's upfront payment that no invoice has drawn yet goes back to your organization's credit balance, never to the card. Usage up to the delete is invoiced as usual. A delete that fails returns nothing.

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:

- `database_id`: The database's id, e.g. `db_...`.

Example:

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

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "db_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`: `DATABASE_NOT_FOUND`: no database with this id in your organization. Check the id: a database of another organization is reported the same way.
`RESOURCE_NOT_FOUND`: the id is not a database id (`db_…`). Check the path.
- `409`: `INVALID_RESOURCE_STATE`: the database is being deleted or has been deleted. Nothing more can be changed on it.
`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 a value, for example a malformed CIDR or a name longer than 255 characters. Check the request against this page.
- `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`.
## Start a database

```http
POST /public/v1/databases/{database_id}/start
```

Starts a stopped database. Its hostname, data and access rules are unchanged. A running database is billed for its plan again.

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:

- `database_id`: The database's id, e.g. `db_...`.

Example:

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

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "db_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`: `DATABASE_NOT_FOUND`: no database with this id in your organization. Check the id: a database of another organization is reported the same way.
`RESOURCE_NOT_FOUND`: the id is not a database id (`db_…`). Check the path.
- `409`: `ACCOUNT_SUSPENDED`: an invoice is overdue, or the organization was suspended for non-payment. Pay the outstanding invoice in the console, then retry.
`INVALID_RESOURCE_STATE`: the database is being deleted or has been deleted. Nothing more can be changed on it.
`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`.
## Stop a database

```http
POST /public/v1/databases/{database_id}/stop
```

Shuts the database down cleanly. A stopped database keeps its data, hostname and access rules, refuses connections, and is billed for its storage only. Start it again with *Start a database*.

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:

- `database_id`: The database's id, e.g. `db_...`.

Example:

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

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "db_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`: `DATABASE_NOT_FOUND`: no database with this id in your organization. Check the id: a database of another organization is reported the same way.
`RESOURCE_NOT_FOUND`: the id is not a database id (`db_…`). Check the path.
- `409`: `INVALID_RESOURCE_STATE`: the database is being deleted or has been deleted. Nothing more can be changed on it.
`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`.
## Replace access rules

```http
PUT /public/v1/databases/{database_id}/access-rules
```

Replaces the database's whole allow-list. Each rule allows TCP to the engine's port (5432 for PostgreSQL, 3306 for MySQL and MariaDB) from one IPv4 CIDR. Send `[]` to remove every rule. To let one instance in, allow its address as a `/32`: in a standard zone, use the instance's `private_network_ip` after *Add a private database network interface*.

Limits: an IPv4 CIDR with a prefix of `/8` or longer (host bits are masked, so `10.0.1.7/24` becomes `10.0.1.0/24`); never `0.0.0.0/0`; at most 50 rules; no range twice. These rules plus the ranges added by attached security groups may total at most 1024.

Removing a rule also ends connections already open from that range. 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 new list is in effect once the database's `observed_generation` equals its `generation`.

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:

- `database_id`: The database's id, e.g. `db_...`.

Body:

- `rules` (array of object, required): The complete new allow-list, at most 50 rules with no range twice. `[]` removes every rule. At most 50 items.
  - `cidr` (string, required): One IPv4 CIDR to allow, with a prefix of `/8` to `/32`; never `0.0.0.0/0`. Host bits are masked (`10.0.1.7/24` is stored as `10.0.1.0/24`). Use `/32` for one address, such as an instance's `private_network_ip`.

Example:

```sh
curl -X PUT "https://api.pantechdynamics.com/public/v1/databases/db_06gk3x9tq7m2v8r4y1c5d0zp8w/access-rules" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "rules": [
      {
        "cidr": "10.0.1.0/24"
      },
      {
        "cidr": "10.250.3.17/32"
      }
    ]
  }'
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "db_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`: `DATABASE_NOT_FOUND`: no database with this id in your organization. Check the id: a database of another organization is reported the same way.
`RESOURCE_NOT_FOUND`: the id is not a database id (`db_…`). Check the path.
- `409`: `INVALID_RESOURCE_STATE`: the database is being deleted or has been deleted. Nothing more can be changed on it.
`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 list; `[]` removes every rule.
`rules` `INVALID_ACCESS_RULE`: each `cidr` must be an IPv4 CIDR with a prefix of /8 or longer, such as `10.0.1.0/24`; `0.0.0.0/0` is refused.
`rules` `TOO_MANY_ACCESS_RULES`: at most 50 rules. Merge neighbouring ranges, or attach a security group instead.
`rules` `DUPLICATE_ACCESS_RULE`: the same range appears twice (after masking host bits). Remove the repeat.
`UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
`DATABASE_ACCESS_RANGE_LIMIT_EXCEEDED`: the database's own access rules plus the address ranges its security groups add would exceed 1024. Remove access rules, detach a security group, or narrow the groups' rules, then retry.
- `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`.
## Set the password

```http
PUT /public/v1/databases/{database_id}/password
```

Sets the admin login's password to one you choose: 16 to 128 printable ASCII characters, with no spaces, single or double quotes, or backslashes. It is applied inside the database and never stored or returned. The database must be running. Existing connections stay open; new ones need the new password once the operation succeeds.

Use this from infrastructure-as-code to rotate a password you manage; use *Reset the password* to have one generated instead. The response is `Cache-Control: no-store`.

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:

- `database_id`: The database's id, e.g. `db_...`.

Body:

- `password` (string, required): The new admin password: 16 to 128 printable ASCII characters, with no spaces, single quotes (`'`), double quotes (`"`) or backslashes (`\`). Write-only: never stored in plain text, never returned, never logged. From 16 to 128 characters.

Example:

```sh
curl -X PUT "https://api.pantechdynamics.com/public/v1/databases/db_06gk3x9tq7m2v8r4y1c5d0zp8w/password" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "password": "c0rrect-H0rse-battery-St4ple"
  }'
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "db_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`: `DATABASE_NOT_FOUND`: no database with this id in your organization. Check the id: a database of another organization is reported the same way.
`RESOURCE_NOT_FOUND`: the id is not a database id (`db_…`). Check the path.
- `409`: `DATABASE_NOT_RUNNING`: the database must be running for this change. Start it (`POST /public/v1/databases/{database_id}/start`), wait for the operation to succeed, then retry.
`INVALID_RESOURCE_STATE`: the database is being deleted or has been deleted. Nothing more can be changed on it.
`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`:
`password` `REQUIRED`: send the new password.
`password` `INVALID_DATABASE_PASSWORD`: use 16 to 128 printable ASCII characters with no spaces, single or double quotes, or backslashes.
`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`.
## Reset the password

```http
POST /public/v1/databases/{database_id}/reset-password
```

Replaces the admin login's password with a generated one (32 letters and digits), returned once in this response (`password`) with `Cache-Control: no-store`, and never stored. The database must be running. Use this when the password is lost: support cannot recover it.

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 new password works once the operation succeeds.

**Idempotency.** Send an `Idempotency-Key`. A retry with the same key returns the original operation with `password_returned: false` and a null `password`: if the first response was lost, reset again with a new key.

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

Path parameters:

- `database_id`: The database's id, e.g. `db_...`.

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/databases/db_06gk3x9tq7m2v8r4y1c5d0zp8w/reset-password" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "db_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "status": "submitting",
  "password_returned": true,
  "password": "q8Vn3TzK1mXw7RbLc4Hs9JdP2fYa6GeU"
}
```

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`: `DATABASE_NOT_FOUND`: no database with this id in your organization. Check the id: a database of another organization is reported the same way.
`RESOURCE_NOT_FOUND`: the id is not a database id (`db_…`). Check the path.
- `409`: `DATABASE_NOT_RUNNING`: the database must be running for this change. Start it (`POST /public/v1/databases/{database_id}/start`), wait for the operation to succeed, then retry.
`INVALID_RESOURCE_STATE`: the database is being deleted or has been deleted. Nothing more can be changed on it.
`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 security groups

```http
PUT /public/v1/databases/{database_id}/security-groups
```

Replaces the set of your security groups attached to the database; `[]` detaches every group. At most 5, each one of your own security groups in the database's zone that is not being deleted or failed. Another organization's group is reported as not found.

A group is only a **source of address ranges** for the database: each of its **ingress** rules whose protocol is `tcp` or `all`, whose ports include the engine's port (5432 or 3306; a range such as `3000-4000`, or all ports, counts) and whose CIDR is IPv4 with a prefix of `/8` or longer adds its CIDR to the database's allow-list, beside its own access rules. Every other rule (other ports, egress, `udp`, `icmp`, `0.0.0.0/0` and anything wider than `/8`, IPv6) is ignored for the database and listed in `ignored_security_group_rules` with the reason. The group itself is never applied to the database server, and SSH to it is never opened. The database's access rules plus the ranges its groups add may total at most 1024.

When an attached group's rules change, the database is updated automatically. A group cannot be deleted while a database uses it (`409 SECURITY_GROUP_ATTACHED_TO_DATABASE`); detach it here 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`. Send an `Idempotency-Key` header.

Path parameters:

- `database_id`: The database's id, e.g. `db_...`.

Body:

- `security_group_ids` (array of string, required): The complete set to attach: at most 5 distinct ids of your own security groups in the database's zone, e.g. `sg_…`. `[]` detaches every group. At most 5 items.

Example:

```sh
curl -X PUT "https://api.pantechdynamics.com/public/v1/databases/db_06gk3x9tq7m2v8r4y1c5d0zp8w/security-groups" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "security_group_ids": [
      "sg_06gk3x9tq7m2v8r4y1c5d0zp8w"
    ]
  }'
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "db_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`: `DATABASE_NOT_FOUND`: no database with this id in your organization. Check the id: a database of another organization is reported the same way.
`RESOURCE_NOT_FOUND`: the id is not a database id (`db_…`). Check the path.
- `409`: `INVALID_RESOURCE_STATE`: the database is being deleted or has been deleted. Nothing more can be changed on it.
`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`:
`security_group_ids` `REQUIRED`: send the complete set; `[]` detaches every group.
`security_group_ids` `INVALID_DATABASE_SECURITY_GROUPS`: more than 5 ids, or an id repeated. Send at most 5 distinct ids.
`security_group_ids` `DATABASE_SECURITY_GROUP_NOT_FOUND`: an id is not one of your security groups. Check the ids with `GET /public/v1/security-groups`.
`security_group_ids` `DATABASE_SECURITY_GROUP_NOT_USABLE`: a group is being deleted or has failed. Remove it from the list.
`security_group_ids` `DATABASE_SECURITY_GROUP_WRONG_ZONE`: a group is in another zone than the database. Use groups in the database's zone.
`UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
`DATABASE_ACCESS_RANGE_LIMIT_EXCEEDED`: the database's own access rules plus the address ranges its security groups add would exceed 1024. Remove access rules, detach a security group, or narrow the groups' rules, then retry.
- `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`.
## Grow a database's storage

```http
POST /public/v1/databases/{database_id}/resize-storage
```

Grows the database's data disk to `storage_gb`. vCPU and memory stay the plan's. The disk only grows: `storage_gb` must be larger than the current `data_volume_size_gb`, at most 2000 GB and a multiple of 10 GB (read the zone's limits from `storage` in `GET /public/v1/database-engines` rather than hard-coding them). The database must be running; the filesystem grows online, without a restart. One resize at a time.

No upfront charge, as for an instance resize: storage is billed hourly, at the new size only once the resize has completed. While it runs, `pending_data_volume_size_gb` shows the size in flight; `data_volume_size_gb` changes when it succeeds. A failed resize leaves the size and the bill unchanged.

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:

- `database_id`: The database's id, e.g. `db_...`.

Body:

- `storage_gb` (integer, required): The new data disk size in GB: larger than the current `data_volume_size_gb`, at most the zone's `max_gb` (2000 today) and a multiple of its `step_gb` (10 today); see `storage` in `GET /public/v1/database-engines`. It only grows. At least 1.

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/databases/db_06gk3x9tq7m2v8r4y1c5d0zp8w/resize-storage" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "storage_gb": 200
  }'
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "db_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`: `DATABASE_NOT_FOUND`: no database with this id in your organization. Check the id: a database of another organization is reported the same way.
`RESOURCE_NOT_FOUND`: the id is not a database id (`db_…`). Check the path.
- `409`: `ACCOUNT_SUSPENDED`: an invoice is overdue, or the organization was suspended for non-payment. Pay the outstanding invoice in the console, then retry.
`DATABASE_NOT_RUNNING`: the database must be running for this change. Start it (`POST /public/v1/databases/{database_id}/start`), wait for the operation to succeed, then retry.
`DATABASE_STORAGE_RESIZE_IN_PROGRESS`: a storage resize of this database is still running. Wait for its operation to finish, then retry.
`INVALID_RESOURCE_STATE`: the database is being deleted or has been deleted. Nothing more can be changed on it.
`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`:
`storage_gb` `REQUIRED`: send the new size in GB.
`storage_gb` `INVALID_DATABASE_STORAGE`: the size must be larger than the current `data_volume_size_gb`, at most 2000, and a multiple of 10.
`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.
`DATABASE_STORAGE_UNAVAILABLE`: database storage is not offered in this database's zone right now. Try again later.
`PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
## List a database's snapshots

```http
GET /public/v1/databases/{database_id}/snapshots
```

Snapshots of the database's data disk, oldest first. Each is also listed in `GET /public/v1/snapshots`, with `database_id` set.

Scope: `read`.

Path parameters:

- `database_id`: The database's id, e.g. `db_...`.

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/databases/db_06gk3x9tq7m2v8r4y1c5d0zp8w/snapshots" \
  -H "Authorization: Bearer $PANTECH_API_KEY"
```

Response `200`:

```json
{
  "data": [
    {
      "id": "snap_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "name": "pre-upgrade",
      "instance_id": null,
      "source_instance_name": null,
      "volume_id": null,
      "volume_name": null,
      "database_id": "db_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "region": "af-abj",
      "trigger": "manual",
      "size_bytes": 1610612736,
      "desired_state": "present",
      "observed_state": "active",
      "completed_at": "2026-10-03T09:31:12Z",
      "created_at": "2026-10-03T09:30:00Z",
      "updated_at": "2026-10-03T09:31:12Z"
    }
  ],
  "next_cursor": null
}
```

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.
- `404`: `DATABASE_NOT_FOUND`: no database with this id in your organization. Check the id: a database of another organization is reported the same way.
`RESOURCE_NOT_FOUND`: the id is not a database id (`db_…`). Check the path.
- `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.
## Snapshot a database

```http
POST /public/v1/databases/{database_id}/snapshots
```

Takes a snapshot of the database's data disk. The engine keeps running, so the snapshot is **crash-consistent only**: what the disk held at that instant, as after a power cut, which the engine recovers from on start. It is not an engine-level backup; stop the database first for a quiesced copy. The database must be running or stopped.

Billed like every snapshot: the storage rate on its physical size, for every started hour until it is deleted. A snapshot outlives its database and stays billed until you delete it. Restoring a database from a snapshot is not available yet.

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 new snapshot'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:

- `database_id`: The database's id, e.g. `db_...`.

Body:

- `name` (string, required): 1 to 255 characters, unique among this resource's snapshots, e.g. `pre-upgrade`. At most 255 characters.

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/databases/db_06gk3x9tq7m2v8r4y1c5d0zp8w/snapshots" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "pre-upgrade"
  }'
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "snap_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`: `DATABASE_NOT_FOUND`: no database with this id in your organization. Check the id: a database of another organization is reported the same way.
`RESOURCE_NOT_FOUND`: the id is not a database id (`db_…`). Check the path.
- `409`: `ACCOUNT_SUSPENDED`: an invoice is overdue, or the organization was suspended for non-payment. Pay the outstanding invoice in the console, then retry.
`DATABASE_SNAPSHOT_NOT_READY`: the database is not running or stopped (it is starting, stopping, being created or deleted). Wait until it is running or stopped, then retry.
`DATABASE_SNAPSHOT_NAME_TAKEN`: this database already has a snapshot with that name. Choose a different name.
`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` `INVALID_DATABASE_SNAPSHOT_NAME`: the name is empty or longer than 255 characters.
`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.
`DATABASES_UNAVAILABLE`: managed databases are not available on this platform yet. Try again later, or contact support.
`PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
## Get a database's snapshot

```http
GET /public/v1/databases/{database_id}/snapshots/{snapshot_id}
```

One snapshot of the database's data disk.

Scope: `read`.

Path parameters:

- `database_id`: The database's id, e.g. `db_...`.
- `snapshot_id`: The snapshot's id, e.g. `snap_...`.

Example:

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

Response `200`:

```json
{
  "id": "snap_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "name": "before-migration",
  "instance_id": null,
  "source_instance_name": null,
  "volume_id": null,
  "volume_name": null,
  "database_id": "db_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "region": "af-abj",
  "trigger": "manual",
  "size_bytes": 1610612736,
  "desired_state": "present",
  "observed_state": "active",
  "completed_at": "2026-10-03T09:30:00Z",
  "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`: `DATABASE_NOT_FOUND`: no database with this id in your organization. Check the id: a database of another organization is reported the same way.
`RESOURCE_NOT_FOUND`: the id is not a database id (`db_…`). Check the path.
`DATABASE_SNAPSHOT_NOT_FOUND`: this database has no snapshot with this id (a snapshot of an instance or volume is reported the same way). Check the id, or use `GET /public/v1/snapshots/{snapshot_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.
## Delete a database's snapshot

```http
DELETE /public/v1/databases/{database_id}/snapshots/{snapshot_id}
```

Deletes one snapshot of the database's data disk. It is billed until the deletion completes.

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:

- `database_id`: The database's id, e.g. `db_...`.
- `snapshot_id`: The snapshot's id, e.g. `snap_...`.

Example:

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

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "snap_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`: `DATABASE_NOT_FOUND`: no database with this id in your organization. Check the id: a database of another organization is reported the same way.
`RESOURCE_NOT_FOUND`: the id is not a database id (`db_…`). Check the path.
`DATABASE_SNAPSHOT_NOT_FOUND`: this database has no snapshot with this id. Check the id.
- `409`: `INVALID_RESOURCE_STATE`: the snapshot 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.
- `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`.
