# Pantech Dynamics public API: /public/v1. Additive changes only within v1.
openapi: "3.1.0"
info:
  title: Pantech Dynamics public API
  version: "1.0.0"
  description: |-
    The Pantech Dynamics public API lets your scripts, CI pipelines and tools manage everything you run on Pantech: instances, VPC networks, firewalls, public IPs, load balancers, volumes, snapshots, managed databases, managed Kubernetes clusters, SSH keys and webhooks.

    **Base URL:** `https://api.pantechdynamics.com/public/v1`

    **Authentication.** Every request needs an API key: `Authorization: Bearer PAN_…`. Create one in the console under Organization › API keys. A `read` key can call every GET; a `write` key can also create, change and delete. A key acts as the member who created it, with no more access than they have. `DELETE /me` revokes the key that sends it.

    **AI apps.** When a member connects an AI app (an AI assistant or coding tool) to Pantech, the app is given an API key of kind `ai_app`, shown under Organization › AI apps; `GET /me` reports `kind` and `client_name`. Its access is the level the member chose: `read` (Read only), `write` (Manage) or `write` plus `delete` (Manage and delete). An `ai_app` key with `write` but not `delete` can create and change resources but cannot make a destructive call — every DELETE except `DELETE /me`, rebuilding or restoring an instance, and lowering a snapshot schedule's `retention_count` (these operations are marked `x-destructive`); it gets `403 INSUFFICIENT_SCOPE` instead. A standard key's `write` covers destructive calls as it always has. Owners can turn AI app access off for the whole organization: every request made with an `ai_app` key, and every request sent by Pantech's MCP server (it sends the header `Pantech-Client: mcp/<version>`, whatever key it uses), then gets `403 AI_APPS_DISABLED` until it is turned back on. `DELETE /me` keeps working so an app can always be disconnected.

    **Asynchronous changes.** Writes return `202 Accepted` with an `operation_id`. Poll `GET /operations/{operation_id}` (or stream `/events`) until `status` is `succeeded` or `failed`. A new instance or database returns an order instead: follow `GET /instance-orders/{order_id}` or `GET /database-orders/{order_id}`.

    **Idempotency.** Every POST, PUT and DELETE needs an `Idempotency-Key` header. Retrying with the same key returns the original result instead of repeating the change. Some endpoints do not replay, and say so: `POST /ssh-keys`, `DELETE /ssh-keys/{ssh_key_id}`, `POST /instances/{instance_id}/console`, and the webhook test, delete, secret rotation and redelivery endpoints.

    **Errors.** Every error is `application/problem+json` with a stable `code`, a plain-language `detail` that says what went wrong and what to do, and a `request_id` to quote to support. Branch on `code`, never on `detail`. A `422 VALIDATION_FAILED` lists each invalid field in `errors` (`field`, `code`, `message`). Each endpoint below lists the codes it can return. Two more apply everywhere: `404 ROUTE_NOT_FOUND` (no endpoint matches the method and path) and `500 INTERNAL` (something failed on our side; retry with the same `Idempotency-Key`, and contact support with the `request_id` if it persists).

    **Failed operations.** An operation that fails has `status: failed` and a `failure` with a stable `code` and a readable `reason`, safe to show to your users. Common codes: `INSTANCE_BUSY` (the resource kept changing; retry when it has settled), `SECURITY_GROUP_ALLOWS_PRIVATE_NETWORK` (see *Add a private database network interface*), `DATABASE_STORAGE_RESIZE_FAILED` and `DATABASE_STORAGE_GROW_FAILED` (nothing changed and nothing extra is billed; retry later).

    **Pagination.** Lists return `{ "data": [...], "next_cursor": ... }`. Pass `next_cursor` as `cursor` until it is null.

    **Rate limits.** 300 requests/minute per key, plus shared verified-user and organization budgets (defaults: 1,200/minute per user and 6,000/minute per organization, including console traffic). Changing IPs or keys does not reset those budgets. Authenticated responses carry `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`; successful calls report the key budget. A quota `429` reports the denying budget and `RateLimit-Scope` (`api_key`, `user` or `organization`). Every `429` carries `Retry-After`. Separate edge/IP and failed-auth limits can refuse requests before these quotas.

    **Money.** Amounts are integers in minor units (`amount_minor`): kobo for NGN, cents for USD.
  contact:
    name: Pantech Dynamics support
    url: "https://pantechdynamics.com/contact"
servers:
  - url: "https://api.pantechdynamics.com"
    description: Production
security:
  - apiKey: []
tags:
  - name: Account
    description: The API key making the call.
  - name: Catalogue
    description: "What you can buy, and what it costs."
  - name: Instances
    description: "Virtual machines (VPS): create, power, change, delete."
  - name: Instance orders
    description: How a new instance is paid for and provisioned.
  - name: Networks
    description: "VPCs, subnets and their firewall rules."
  - name: Public IPs
    description: "Public addresses for VPC instances, and port forwarding."
  - name: Load balancers
    description: "Spread traffic on one public port across several instances in a private network."
  - name: Kubernetes
    description: "Managed Kubernetes clusters whose nodes are VMs in your account."
  - name: Security groups
    description: Reusable firewalls for standard instances.
  - name: Volumes
    description: Data disks.
  - name: Databases
    description: "Managed PostgreSQL, MySQL and MariaDB databases, reachable only from your own network."
  - name: Snapshots
    description: "Point-in-time copies of root disks and volumes, and automatic schedules."
  - name: SSH keys
    description: Keys installed on new instances.
  - name: Webhooks
    description: "Events sent to your own URL when something changes: resources finishing or failing a change, invoices, payments and credit."
  - name: Operations
    description: Follow asynchronous changes.
  - name: Usage
    description: "What has been used, and what it cost."
  - name: Billing
    description: "Your credit, and whether it covers your next invoice."
paths:
  /public/v1/billing/credit:
    get:
      tags:
        - Billing
      summary: Get credit
      description: |-
        Your organization's credit, and how it compares with your next invoice.

        `estimated_next_invoice_minor` is usage so far this billing period plus the recent daily rate for the days left, plus 7.5% VAT on that (VAT applies only to the monthly invoice, never to credit purchases). It is an estimate: the invoice issued at `next_invoice_at` is the real figure. `standing` is `covered` when credit and prepaid instance payments cover the estimate, `low` when they don't, and `empty` when no credit is left. Anything credit doesn't cover is charged to your default payment method.

        Check this before creating resources from automation, or poll it to top up in time. Owners and admins are also emailed once when credit becomes `low` and once when it becomes `empty`. To add credit, see *Buy credit*.

        **Scope:** `read`.
      operationId: getCredit
      security:
        - apiKey: []
      x-scope: read
      x-order: 86
      responses:
        200:
          description: Credit.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreditOutlook"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `BILLING_UNAVAILABLE`: billing is not answering right now. Wait a few seconds, then retry.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/billing/credit/purchases:
    post:
      tags:
        - Billing
      summary: Buy credit
      description: |-
        Creates a payment link to add credit to your organization. Send `checkout_url` to whoever pays: it opens the payment provider's own secure page, so no card details ever pass through your systems or ours. Organizations pay in NGN or USD. The currency follows your organization's country and can't be chosen.

        Nothing is charged until someone pays the link. The credit is added once the provider confirms the payment, usually within a minute: follow the purchase with `GET /public/v1/billing/credit/purchases/{purchase_id}` until `status` is `settled`, and the owners and admins get a receipt by email. A link that isn't paid expires at `checkout_expires_at`; create a new purchase after that.

        One purchase is between NGN 500 and NGN 500,000 (`50000` to `50000000` kobo), or between USD 5 and USD 2,000 (`500` to `200000` cents).

        Send an `Idempotency-Key`: a retry with the same key returns the same purchase and the same link. The member who created the key must be an owner or admin with a verified email.

        **Scope:** `write`.
      operationId: createCreditPurchase
      security:
        - apiKey: []
      x-scope: write
      x-order: 87
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCreditPurchaseRequest"
      responses:
        201:
          description: "The purchase, with its `checkout_url`."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreditPurchase"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          description: |-
            `INSUFFICIENT_SCOPE`: this API key has only the `read` scope. Use a key with `write` access.
            `FORBIDDEN`: only an owner or admin may buy credit. Use a key created by an owner or admin.
            `AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server. An owner can turn it on in the console under Organization › AI apps.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `EMAIL_VERIFICATION_REQUIRED`: the member who created this key has not verified their email, where the receipt is sent. Verify it in the console, then retry.
            `CREDIT_PURCHASE_UNAVAILABLE`: credit can't be bought for this organization yet. Finish billing setup in the console, or contact support.
            `IDEMPOTENCY_CONFLICT`: this `Idempotency-Key` was already used for a different amount. Use a new key for a different purchase.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `amount_minor` `REQUIRED`: send a positive amount in minor units.
            `amount_minor` `AMOUNT_OUT_OF_RANGE`: the amount is outside the per-purchase limits above for your currency. Choose an amount within them.
            `UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `BILLING_UNAVAILABLE`: billing is not answering right now. Wait a few seconds, then retry.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/billing/credit/purchases/{purchase_id}:
    get:
      tags:
        - Billing
      summary: Get a credit purchase
      description: |-
        One credit purchase: whether it has been paid. `checkout_url` is shown only while the purchase can still be paid. Poll this after *Buy credit* until `status` is `settled` (credit added), or `failed` or `expired` (nothing charged; create a new purchase).

        **Scope:** `read`.
      operationId: getCreditPurchase
      security:
        - apiKey: []
      x-scope: read
      x-order: 88
      parameters:
        - name: purchase_id
          in: path
          required: true
          description: "The purchase's id, e.g. `cpr_...`."
          schema:
            type: string
      responses:
        200:
          description: The purchase.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreditPurchase"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no credit purchase with this id in your organization. Check the id (`cpr_…`).
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `BILLING_UNAVAILABLE`: billing is not answering right now. Wait a few seconds, then retry.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/billing/receipts:
    get:
      tags:
        - Billing
      summary: List receipts
      description: |-
        Every receipt issued to your organization, newest first. A receipt is issued, numbered `PTD-RCT-<year>-<number>`, whenever money moves: a card payment, a credit top-up, an invoice paid by card or from credit, a debit from your credit (for example a new instance's first charges), and a refund. A payment's receipt usually appears within a minute of it settling.

        **Scope:** `read`.
      operationId: listReceipts
      security:
        - apiKey: []
      x-scope: read
      x-order: 107
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
      responses:
        200:
          description: A page of receipts.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReceiptList"
        400:
          description: |-
            `INVALID_CURSOR`: the cursor did not come from this list. Start again without one.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `BILLING_UNAVAILABLE`: billing is not answering right now. Wait a few seconds, then retry.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/billing/receipts/{receipt_id}:
    get:
      tags:
        - Billing
      summary: Get a receipt
      description: |-
        One receipt: what it was for, the amount, who issued it and the details printed on it. Download it as a PDF with `GET /public/v1/billing/receipts/{receipt_id}/pdf`.

        **Scope:** `read`.
      operationId: getReceipt
      security:
        - apiKey: []
      x-scope: read
      x-order: 108
      parameters:
        - name: receipt_id
          in: path
          required: true
          description: "The receipt's id, e.g. `rcp_...`."
          schema:
            type: string
      responses:
        200:
          description: The receipt.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Receipt"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RECEIPT_NOT_FOUND`: no receipt with this id in your organization. List your receipts to find it.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `BILLING_UNAVAILABLE`: billing is not answering right now. Wait a few seconds, then retry.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/billing/receipts/{receipt_id}/pdf:
    get:
      tags:
        - Billing
      summary: Download a receipt
      description: |-
        The receipt as a PDF, the same document that was emailed. The file name is the receipt number, e.g. `PTD-RCT-2026-000123.pdf`.

        **Scope:** `read`.
      operationId: getReceiptPdf
      security:
        - apiKey: []
      x-scope: read
      x-order: 109
      parameters:
        - name: receipt_id
          in: path
          required: true
          description: "The receipt's id, e.g. `rcp_...`."
          schema:
            type: string
      responses:
        200:
          description: The PDF.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            Content-Disposition:
              description: '`attachment; filename="PTD-RCT-2026-000123.pdf"`.'
              schema:
                type: string
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RECEIPT_NOT_FOUND`: no receipt with this id in your organization. List your receipts to find it.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `BILLING_UNAVAILABLE`: billing is not answering right now. Wait a few seconds, then retry.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/database-engines:
    get:
      tags:
        - Databases
      summary: List database engines
      description: |-
        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`.
      operationId: listDatabaseEngines
      security:
        - apiKey: []
      x-scope: read
      x-order: 89
      responses:
        200:
          description: The engines.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabaseEngineList"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/database-orders:
    get:
      tags:
        - Databases
      summary: List database orders
      description: |-
        Your database orders, newest first, one per *Create a database* call. An order shows whether the initial payment was taken and whether provisioning started. Never includes a password.

        **Scope:** `read`.
      operationId: listDatabaseOrders
      security:
        - apiKey: []
      x-scope: read
      x-order: 99
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
      responses:
        200:
          description: A page of orders.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabaseOrderList"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/database-orders/{order_id}:
    get:
      tags:
        - Databases
      summary: Get a database order
      description: |-
        Payment and provisioning progress for one new database. Poll it after *Create a database* until `status` is `provisioned` (then read the database at `GET /public/v1/databases/{database_id}`), or `payment_failed` or `failed` (final: any payment taken is returned to your credit, and `failure_code` says why). Once `operation_id` is set you can also follow that operation. Never includes a password.

        **Scope:** `read`.
      operationId: getDatabaseOrder
      security:
        - apiKey: []
      x-scope: read
      x-order: 100
      parameters:
        - name: order_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
      responses:
        200:
          description: The order.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabaseOrder"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `DATABASE_ORDER_NOT_FOUND`: no database order with this id in your organization. Check the id; an order of another organization is reported the same way.
            `RESOURCE_NOT_FOUND`: the id is not an order id (`ord_…`). Check the path.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/databases:
    get:
      tags:
        - Databases
      summary: List databases
      description: |-
        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`.
      operationId: listDatabases
      security:
        - apiKey: []
      x-scope: read
      x-order: 90
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Q"
        - $ref: "#/components/parameters/InstanceDesiredState"
        - $ref: "#/components/parameters/InstanceObservedState"
      responses:
        200:
          description: A page of databases.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabaseList"
        400:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    post:
      tags:
        - Databases
      summary: Create a database
      description: |-
        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`.
      operationId: createDatabase
      security:
        - apiKey: []
      x-scope: write
      x-order: 91
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateDatabaseRequest"
            examples:
              generated_password:
                summary: "PostgreSQL in a VPC subnet, password generated"
                value: {"name": "orders-db", "engine": "postgresql", "version": "18", "plan_slug": "starter", "zone_id": "af-abj-2", "subnet_id": "snet_06gk3x9tq7m2v8r4y1c5d0zp8w"}
              chosen_password:
                summary: "MySQL in a standard zone, with your own password, admin login and a bigger disk"
                value: {"name": "orders-db", "engine": "mysql", "version": "8.4", "plan_slug": "starter", "zone_id": "af-abj-1", "access_rules": ["10.250.3.17/32"], "admin_username": "app_admin", "password": "c0rrect-H0rse-battery-St4ple", "storage_gb": 100}
      responses:
        202:
          description: "Ordered. Follow `order_id` (also the `Location` header) until provisioning starts."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
            Cache-Control:
              description: "Always `no-store`: this response may carry a password."
              schema:
                type: string
                enum:
                  - no-store
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabaseOrderAccepted"
              examples:
                generated_password:
                  summary: "Password generated: returned once, here"
                  value: {"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"}
                chosen_password:
                  summary: "You chose the password, or this is a retry: no password returned"
                  value: {"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": false, "password": null, "admin_username": "app_admin"}
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        402:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        403:
          $ref: "#/components/responses/Forbidden"
        409:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/databases/{database_id}:
    get:
      tags:
        - Databases
      summary: Get a database
      description: |-
        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`.
      operationId: getDatabase
      security:
        - apiKey: []
      x-scope: read
      x-order: 92
      parameters:
        - name: database_id
          in: path
          required: true
          description: "The database's id, e.g. `db_...`."
          schema:
            type: string
      responses:
        200:
          description: The database.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Database"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    delete:
      tags:
        - Databases
      summary: Delete a database
      description: |-
        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`.
      operationId: deleteDatabase
      security:
        - apiKey: []
      x-scope: write
      x-destructive: true
      x-order: 93
      parameters:
        - name: database_id
          in: path
          required: true
          description: "The database's id, e.g. `db_...`."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/databases/{database_id}/access-rules:
    put:
      tags:
        - Databases
      summary: Replace access rules
      description: |-
        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`.
      operationId: replaceDatabaseAccessRules
      security:
        - apiKey: []
      x-scope: write
      x-order: 96
      parameters:
        - name: database_id
          in: path
          required: true
          description: "The database's id, e.g. `db_...`."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReplaceDatabaseAccessRulesRequest"
            examples:
              two_ranges:
                summary: "Allow a VPC subnet and one instance's private interface"
                value: {"rules": [{"cidr": "10.0.1.0/24"}, {"cidr": "10.250.3.17/32"}]}
              none:
                summary: "Allow nobody"
                value: {"rules": []}
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
              examples:
                accepted:
                  summary: "Accepted"
                  value: {"operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w", "resource_id": "db_06gk3x9tq7m2v8r4y1c5d0zp8w", "status": "submitting"}
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/databases/{database_id}/security-groups:
    put:
      tags:
        - Databases
      summary: Attach security groups
      description: |-
        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`.
      operationId: setDatabaseSecurityGroups
      security:
        - apiKey: []
      x-scope: write
      x-order: 101
      parameters:
        - name: database_id
          in: path
          required: true
          description: "The database's id, e.g. `db_...`."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SetDatabaseSecurityGroupsRequest"
            examples:
              attach:
                summary: "Attach one security group"
                value: {"security_group_ids": ["sg_06gk3x9tq7m2v8r4y1c5d0zp8w"]}
              detach_all:
                summary: "Detach every group"
                value: {"security_group_ids": []}
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
              examples:
                accepted:
                  summary: "Accepted"
                  value: {"operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w", "resource_id": "db_06gk3x9tq7m2v8r4y1c5d0zp8w", "status": "submitting"}
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/databases/{database_id}/password:
    put:
      tags:
        - Databases
      summary: Set the password
      description: |-
        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`.
      operationId: changeDatabasePassword
      security:
        - apiKey: []
      x-scope: write
      x-order: 97
      parameters:
        - name: database_id
          in: path
          required: true
          description: "The database's id, e.g. `db_...`."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChangeDatabasePasswordRequest"
            examples:
              rotate:
                summary: "Set a password you manage"
                value: {"password": "c0rrect-H0rse-battery-St4ple"}
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
            Cache-Control:
              description: "Always `no-store`: this response may carry a password."
              schema:
                type: string
                enum:
                  - no-store
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/databases/{database_id}/reset-password:
    post:
      tags:
        - Databases
      summary: Reset the password
      description: |-
        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`.
      operationId: resetDatabasePassword
      security:
        - apiKey: []
      x-scope: write
      x-order: 98
      parameters:
        - name: database_id
          in: path
          required: true
          description: "The database's id, e.g. `db_...`."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
            Cache-Control:
              description: "Always `no-store`: this response may carry a password."
              schema:
                type: string
                enum:
                  - no-store
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatabasePasswordAccepted"
              examples:
                first:
                  summary: "First response: the new password, once"
                  value: {"operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w", "resource_id": "db_06gk3x9tq7m2v8r4y1c5d0zp8w", "status": "submitting", "password_returned": true, "password": "q8Vn3TzK1mXw7RbLc4Hs9JdP2fYa6GeU"}
                retry:
                  summary: "Retry with the same key: no password"
                  value: {"operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w", "resource_id": "db_06gk3x9tq7m2v8r4y1c5d0zp8w", "status": "submitting", "password_returned": false, "password": null}
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/databases/{database_id}/start:
    post:
      tags:
        - Databases
      summary: Start a database
      description: |-
        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`.
      operationId: startDatabase
      security:
        - apiKey: []
      x-scope: write
      x-order: 94
      parameters:
        - name: database_id
          in: path
          required: true
          description: "The database's id, e.g. `db_...`."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/databases/{database_id}/stop:
    post:
      tags:
        - Databases
      summary: Stop a database
      description: |-
        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`.
      operationId: stopDatabase
      security:
        - apiKey: []
      x-scope: write
      x-order: 95
      parameters:
        - name: database_id
          in: path
          required: true
          description: "The database's id, e.g. `db_...`."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/databases/{database_id}/resize-storage:
    post:
      tags:
        - Databases
      summary: Grow a database's storage
      description: |-
        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`.
      operationId: resizeDatabaseStorage
      security:
        - apiKey: []
      x-scope: write
      x-order: 102
      parameters:
        - name: database_id
          in: path
          required: true
          description: "The database's id, e.g. `db_...`."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ResizeDatabaseStorageRequest"
            examples:
              grow:
                summary: "Grow the data disk to 200 GB"
                value: {"storage_gb": 200}
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
              examples:
                accepted:
                  summary: "Accepted"
                  value: {"operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w", "resource_id": "db_06gk3x9tq7m2v8r4y1c5d0zp8w", "status": "submitting"}
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/databases/{database_id}/snapshots:
    get:
      tags:
        - Databases
      summary: List a database's snapshots
      description: |-
        Snapshots of the database's data disk, oldest first. Each is also listed in `GET /public/v1/snapshots`, with `database_id` set.

        **Scope:** `read`.
      operationId: listDatabaseSnapshots
      security:
        - apiKey: []
      x-scope: read
      x-order: 103
      parameters:
        - name: database_id
          in: path
          required: true
          description: "The database's id, e.g. `db_...`."
          schema:
            type: string
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Q"
        - $ref: "#/components/parameters/DesiredState"
        - $ref: "#/components/parameters/ObservedState"
      responses:
        200:
          description: A page of snapshots.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SnapshotList"
              examples:
                one:
                  summary: "One crash-consistent snapshot"
                  value: {"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}
        400:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    post:
      tags:
        - Databases
      summary: Snapshot a database
      description: |-
        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`.
      operationId: createDatabaseSnapshot
      security:
        - apiKey: []
      x-scope: write
      x-order: 104
      parameters:
        - name: database_id
          in: path
          required: true
          description: "The database's id, e.g. `db_...`."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSnapshotRequest"
            examples:
              named:
                summary: "Name the snapshot"
                value: {"name": "pre-upgrade"}
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
              examples:
                accepted:
                  summary: "Accepted; the new snapshot's id is resource_id"
                  value: {"operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w", "resource_id": "snap_06gk3x9tq7m2v8r4y1c5d0zp8w", "status": "submitting"}
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/databases/{database_id}/snapshots/{snapshot_id}:
    get:
      tags:
        - Databases
      summary: Get a database's snapshot
      description: |-
        One snapshot of the database's data disk.

        **Scope:** `read`.
      operationId: getDatabaseSnapshot
      security:
        - apiKey: []
      x-scope: read
      x-order: 105
      parameters:
        - name: database_id
          in: path
          required: true
          description: "The database's id, e.g. `db_...`."
          schema:
            type: string
        - name: snapshot_id
          in: path
          required: true
          description: "The snapshot's id, e.g. `snap_...`."
          schema:
            type: string
      responses:
        200:
          description: The snapshot.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Snapshot"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `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}`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    delete:
      tags:
        - Databases
      summary: Delete a database's snapshot
      description: |-
        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`.
      operationId: deleteDatabaseSnapshot
      security:
        - apiKey: []
      x-scope: write
      x-destructive: true
      x-order: 106
      parameters:
        - name: database_id
          in: path
          required: true
          description: "The database's id, e.g. `db_...`."
          schema:
            type: string
        - name: snapshot_id
          in: path
          required: true
          description: "The snapshot's id, e.g. `snap_...`."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/disk-offerings:
    get:
      tags:
        - Catalogue
      summary: List disk offerings
      description: |-
        Volume types and their hourly prices. Use a `slug` as `disk_offering_slug` in *Create a volume*; for an offering with `custom_size: true`, also send `size_gb`.

        **Scope:** `read`.
      operationId: listDiskOfferings
      security:
        - apiKey: []
      x-scope: read
      x-order: 5
      responses:
        200:
          description: Disk offerings.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DiskOfferingList"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/disk-offerings/{slug}/quote:
    get:
      tags:
        - Catalogue
      summary: Price a volume
      description: |-
        The monthly price of one volume of this offering. `size_gb` is required for a custom-size offering and ignored otherwise. Use it to show a price before *Create a volume*.

        **Scope:** `read`.
      operationId: quoteDiskOffering
      security:
        - apiKey: []
      x-scope: read
      x-order: 6
      parameters:
        - name: slug
          in: path
          required: true
          description: "The disk offering's slug."
          schema:
            type: string
        - name: size_gb
          in: query
          schema:
            type: integer
            minimum: 1
      responses:
        200:
          description: The price.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                type: object
                required:
                  - disk_offering_slug
                  - size_gb
                  - currency
                  - hourly_price_minor
                  - monthly_price_minor
                properties:
                  disk_offering_slug:
                    type: string
                  size_gb:
                    type: integer
                  currency:
                    type: string
                  hourly_price_minor:
                    type: integer
                    format: int64
                  monthly_price_minor:
                    type: integer
                    format: int64
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no active disk offering has this slug. Pick one from `GET /public/v1/disk-offerings`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `size_gb` `INVALID_SIZE`: send a whole number of GB, at least 1.
            `size_gb` `SIZE_REQUIRED`: this offering is custom-size, so `size_gb` is required.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/firewall-rules/{rule_id}:
    delete:
      tags:
        - Networks
      summary: Delete a firewall rule
      description: |-
        Deletes one subnet firewall rule. Traffic it allowed is dropped once the operation succeeds, unless another rule allows 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`.

        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`.
      operationId: deleteFirewallRule
      security:
        - apiKey: []
      x-scope: write
      x-destructive: true
      x-order: 45
      parameters:
        - name: rule_id
          in: path
          required: true
          description: "The rule's id (`aclr_...` for a firewall rule, `pfr_...` for a port forwarding rule)."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no firewall rule with this id in your organization, or the id is not a firewall rule id (`aclr_…`). Check the id.
            `NOT_FOUND`: firewall rules are not offered on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `INVALID_RESOURCE_STATE`: the firewall rule cannot do this in its current state, for example while it is being created or deleted. Wait for its current operation to finish, then retry.
            `CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/images:
    get:
      tags:
        - Catalogue
      summary: List images
      description: |-
        Operating systems you can install on an instance, and the zones each runs in. Use a `slug` as `image_slug` in *Create an instance* or *Rebuild an instance*.

        Encrypted images (`encrypted: true`) appear only for organizations they are enabled for; elsewhere their slug is refused as `IMAGE_NOT_FOUND`.

        **Scope:** `read`.
      operationId: listImages
      security:
        - apiKey: []
      x-scope: read
      x-order: 4
      responses:
        200:
          description: Images.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ImageList"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/instance-orders:
    get:
      tags:
        - Instance orders
      summary: List instance orders
      description: |-
        Your instance orders, newest first, one per *Create an instance* call. An order shows whether the initial payment was taken and whether provisioning started.

        **Scope:** `read`.
      operationId: listInstanceOrders
      security:
        - apiKey: []
      x-scope: read
      x-order: 31
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
      responses:
        200:
          description: A page of orders.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InstanceOrderList"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/instance-orders/{order_id}:
    get:
      tags:
        - Instance orders
      summary: Get an instance order
      description: |-
        Payment and provisioning progress for one new instance. Poll it after *Create an instance* until `status` is `provisioned` (then read the instance), or `payment_failed` or `failed` (final: any payment taken is returned to your credit, and `failure_code` says why).

        **Scope:** `read`.
      operationId: getInstanceOrder
      security:
        - apiKey: []
      x-scope: read
      x-order: 32
      parameters:
        - name: order_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
      responses:
        200:
          description: The order.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InstanceOrder"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance order with this id in your organization, or the id is not an order id (`ord_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/instances:
    get:
      tags:
        - Instances
      summary: List instances
      description: |-
        Your instances, newest first, with addresses and state. Filter by name with `q`, or by state.

        **Scope:** `read`.
      operationId: listInstances
      security:
        - apiKey: []
      x-scope: read
      x-order: 8
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Q"
        - $ref: "#/components/parameters/InstanceDesiredState"
        - $ref: "#/components/parameters/InstanceObservedState"
      responses:
        200:
          description: A page of instances.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InstanceList"
        400:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    post:
      tags:
        - Instances
      summary: Create an instance
      description: |-
        Orders a new instance (VPS). Choose a plan (`GET /public/v1/plans`), an image (`GET /public/v1/images`) and an SSH key (`GET /public/v1/ssh-keys`), which is required: images have no password login.

        **Placement.** Leave out `subnet_id` for a standard instance with its own public IPv4, protected by a security group (your organization's default group unless you send `security_group_id`). Send `subnet_id` (and optionally its `network_id`) to place it in a VPC subnet, with a private address only; reach it through a public IP.

        **Paid first.** Returns `202 Accepted` with an order (`order_id`, also the `Location` header). The initial payment is taken from credit, or else from the organization's default card, and then the instance is provisioned. Follow `GET /public/v1/instance-orders/{order_id}` until `status` is `provisioned`, then read the instance; `payment_failed` and `failed` are final, and any payment taken is returned to your credit.

        **Idempotency.** Send an `Idempotency-Key`. A retry with the same key returns the original order instead of ordering a second instance.

        **Scope:** `write`.
      operationId: createInstance
      security:
        - apiKey: []
      x-scope: write
      x-order: 9
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateInstanceRequest"
      responses:
        202:
          description: Ordered.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrderAccepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        402:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: `ssh_key_id` is not one of your SSH keys. Check it with `GET /public/v1/ssh-keys`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `INSTANCE_NAME_TAKEN`: you already have an instance with this name. Choose a different name.
            `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.
            `PROVIDER_ACCOUNT_NOT_READY`: your organization's cloud account is still being set up, which takes a few minutes after sign-up. Retry shortly.
            `SECURITY_GROUPS_NOT_SUPPORTED_IN_ZONE`: a VPC instance uses its subnet's firewall rules, not a security group. Leave out `security_group_id`.
            `CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
            `ssh_key_id` `REQUIRED`: no SSH key was sent. Send the id of one of your SSH keys (`GET /public/v1/ssh-keys`): images have no password login.
            `plan_slug` `PLAN_NOT_FOUND`: no active plan has this slug. Pick one from `GET /public/v1/plans`.
            `image_slug` `IMAGE_NOT_FOUND`: no active image has this slug. Pick one from `GET /public/v1/images`.
            `image_slug` `IMAGE_NOT_AVAILABLE_IN_ZONE`: the image does not run in the zone this instance goes to. Pick an image whose `zones` include it.
            `encrypted` `ENCRYPTION_NOT_SUPPORTED`: `encrypted` is `true` but this image has no encrypted version available to your organization. Choose an image that offers one, or send `false`.
            `encrypted` `ENCRYPTION_CONFLICT`: `encrypted` is `false` but `image_slug` is an encrypted image. Send `true`, or choose the plain image.
            `security_group_id` `SECURITY_GROUP_NOT_FOUND`: not one of your security groups. Check it with `GET /public/v1/security-groups`.
            `subnet_id` `SUBNET_REQUIRED`: `network_id` was sent without a subnet. Send the subnet too.
            `subnet_id` `SUBNET_NOT_IN_NETWORK`: the subnet is not in `network_id`. Send a subnet of that VPC, or leave out `network_id`.
            `subnet_id` `SUBNET_NOT_FOUND`: no subnet with this id in your organization.
            `subnet_id` `SUBNET_NOT_USABLE`: the subnet is being deleted or has failed. Use another subnet.
            `region` `REGION_NOT_AVAILABLE`: this region cannot take this instance. Pick one from `GET /public/v1/regions`.
            `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 platform refused a value, for example a malformed CIDR or a name longer than 255 characters. Check the request against this page.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/instances/{instance_id}:
    get:
      tags:
        - Instances
      summary: Get an instance
      description: |-
        One instance: its plan, image, addresses (including `private_network_ip` once a private database network interface is attached), and current state. A change is complete when `observed_state` matches `desired_state`.

        **Scope:** `read`.
      operationId: getInstance
      security:
        - apiKey: []
      x-scope: read
      x-order: 10
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
      responses:
        200:
          description: The instance.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Instance"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    delete:
      tags:
        - Instances
      summary: Delete an instance
      description: |-
        Deletes the instance. It is stopped, a safety snapshot of its root disk is kept for 24 hours, and then it is removed. Attached volumes are detached, not deleted. Release its public IP and delete port forwarding rules that point at it 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`.
      operationId: deleteInstance
      security:
        - apiKey: []
      x-scope: write
      x-destructive: true
      x-order: 23
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `INSTANCE_HAS_PUBLIC_IP`: a VPC public IP still maps to this instance. Detach it (`POST /public/v1/public-ips/{public_ip_id}/detach`) to keep the address, or release it (`DELETE /public/v1/public-ips/{public_ip_id}`), then retry.
            `INSTANCE_HAS_PORT_FORWARDS`: port forwarding rules still point at this instance. Delete them, then retry.
            `INSTANCE_BEHIND_LOAD_BALANCER`: the instance is still a load balancer target. Remove it from its load balancers (`PATCH /public/v1/load-balancers/{load_balancer_id}`), then retry.
            `INVALID_RESOURCE_STATE`: the instance 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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED` with no `errors` entry: the instance has already been deleted.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/instances/{instance_id}/console:
    post:
      tags:
        - Instances
      summary: Open the browser console
      description: |-
        Returns a short-lived URL to the instance's browser console. **The console gives a logged-in shell; treat the URL as a password** and never log or share it. The instance must be running.

        **Idempotency:** each request opens a new console URL; a retry is not replayed. Opening one changes nothing else, so retrying is safe.

        **Scope:** `write`.
      operationId: openInstanceConsole
      security:
        - apiKey: []
      x-scope: write
      x-order: 19
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        201:
          description: The console URL.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                type: object
                required:
                  - url
                properties:
                  url:
                    type: string
                    format: uri
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `CONSOLE_INSTANCE_NOT_RUNNING`: the instance must be running to open its console. Start it, wait for the operation to succeed, then retry.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `CONSOLE_UNAVAILABLE`: the console is not available right now. Retry in a minute.
            `PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/instances/{instance_id}/volume-key-credential:
    post:
      tags:
        - Instances
      summary: Issue a volume key credential
      description: |-
        Gives an instance the **credential** it uses to ask Pantech for the key of an encrypted data volume. Put the credential on the instance, in the volume agent's configuration, and attach the volume to any instance of your organization: that instance can unlock the volume, whether it runs an encrypted image or a plain one. A volume's key is released only to an instance of the same organization that the volume is attached to.

        **The credential is a secret: it is shown once.** Pantech keeps only a hash of it, so it cannot be shown again. Treat it like a password and never log or share it. Each request issues a new credential and **ends the previous one** for that instance, so use it to rotate a credential too, and update the agent on that instance. The credential also ends when the instance is deleted.

        **Idempotency:** each request issues a new credential; a retry is not replayed.

        **Scope:** `write`.
      operationId: issueVolumeKeyCredential
      security:
        - apiKey: []
      x-scope: write
      x-order: 20
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        201:
          description: The new credential. It is shown once.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                type: object
                required:
                  - instance_id
                  - credential
                properties:
                  instance_id:
                    type: string
                    examples: ["vm_06gk3x9tq7m2v8r4y1c5d0zp8w"]
                  credential:
                    type: string
                    description: "The secret, `pvk_` followed by 43 characters. Shown once."
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `INSTANCE_NOT_LIVE`: the instance is being deleted or has failed. Use another instance.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `ENCRYPTED_VOLUMES_UNAVAILABLE`: encrypted volumes are not available right now. Retry in a minute.
            `PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/instances/{instance_id}/metrics:
    get:
      tags:
        - Instances
      summary: Get instance metrics
      description: |-
        CPU, memory, network and disk activity over `interval` (`1h`, `24h`, `7d` or `30d`), one point per bucket. `available` is false when no samples exist yet, for example just after creation.

        **Scope:** `read`.
      operationId: getInstanceMetrics
      security:
        - apiKey: []
      x-scope: read
      x-order: 24
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - name: interval
          in: query
          schema:
            type: string
            enum:
              - "1h"
              - "24h"
              - "7d"
              - "30d"
            default: "24h"
      responses:
        200:
          description: The series.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MetricsSeries"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `interval` `INVALID_METRIC_INTERVAL`: use `1h`, `24h`, `7d` or `30d`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/instances/{instance_id}/reboot:
    post:
      tags:
        - Instances
      summary: Reboot an instance
      description: |-
        Restarts the instance (a clean reboot of the operating system). It must be running.

        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.

        For the first 5 minutes after a new instance is created, this returns `409` with code `INSTANCE_POWER_LOCKED` while first boot finishes. Wait the `Retry-After` header's seconds, then try again. Pending, provisioning, or uncertain instances also return this 409 before the first run is recorded, without `Retry-After`; wait for the instance to reach running first.

        **Scope:** `write`.
      operationId: rebootInstance
      security:
        - apiKey: []
      x-scope: write
      x-order: 13
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `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 instance 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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED` with no `errors` entry: the instance has been deleted.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/instances/{instance_id}/rebuild:
    post:
      tags:
        - Instances
      summary: Rebuild an instance
      description: |-
        Reinstalls the operating system from `image_slug`, or from the instance's current image when it is left out. **Everything on the root disk is erased**; attached volumes, addresses and the plan are kept. Take a snapshot first if you may need the data.

        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`.
      operationId: rebuildInstance
      security:
        - apiKey: []
      x-scope: write
      x-destructive: true
      x-order: 16
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RebuildRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `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.
            `INVALID_RESOURCE_STATE`: the instance 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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `image_slug` `IMAGE_NOT_FOUND`: no active image has this slug. Pick one from `GET /public/v1/images`.
            `image_slug` `IMAGE_NOT_AVAILABLE_IN_ZONE`: the image does not run in this instance's zone. Pick an image whose `zones` include it.
            `UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/instances/{instance_id}/rename:
    post:
      tags:
        - Instances
      summary: Rename an instance
      description: |-
        Changes the instance's name (1 to 255 characters, unique among your instances).

        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`.
      operationId: renameInstance
      security:
        - apiKey: []
      x-scope: write
      x-order: 14
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RenameRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `INSTANCE_NAME_TAKEN`: you already have an instance with this name. Choose a different name.
            `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.
            `INVALID_RESOURCE_STATE`: the instance 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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/instances/{instance_id}/resize:
    post:
      tags:
        - Instances
      summary: Resize an instance
      description: |-
        Moves the instance to a bigger plan. The instance is stopped, resized and started again. Instances only grow: the new plan must be bigger than the current one. No upfront charge: the bigger plan is billed hourly from the change.

        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`.
      operationId: resizeInstance
      security:
        - apiKey: []
      x-scope: write
      x-order: 15
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ResizeRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `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 instance 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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `plan_slug` `PLAN_NOT_FOUND`: no active plan has this slug. Pick one from `GET /public/v1/plans`.
            `plan_slug` `PLAN_NOT_BIGGER`: the plan is not bigger than the instance's current one. Choose a bigger plan.
            `UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/instances/{instance_id}/change-ssh-key:
    post:
      tags:
        - Instances
      summary: Change an instance's SSH key
      description: |-
        Replaces the SSH key you log in to the instance with by another key on your account, for example after losing the private key. The instance must be running: it is stopped, given the new key and started again, which takes about two minutes. The previous key stops working once the instance is back. Keys you added to the server yourself are left as they are.

        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`.
      operationId: changeInstanceSSHKey
      security:
        - apiKey: []
      x-scope: write
      x-order: 18
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChangeSSHKeyRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `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 instance cannot do this in its current state: it is not running, or another change such as a resize is in progress. Start it, or 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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `ssh_key_id` `REQUIRED`: the field is missing. Send the id of one of your SSH keys.
            `ssh_key_id` `SSH_KEY_NOT_FOUND`: no SSH key with this id on your account. Pick one from `GET /public/v1/ssh-keys`.
            `UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/instances/{instance_id}/restore:
    post:
      tags:
        - Instances
      summary: Restore an instance from a snapshot
      description: |-
        Rolls the instance's root disk back to one of its own snapshots and reboots it. **Everything written to the root disk since the snapshot is lost**, so `confirm_name` must repeat the instance's current name. Attached volumes are not changed.

        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`.
      operationId: restoreInstance
      security:
        - apiKey: []
      x-scope: write
      x-destructive: true
      x-order: 17
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RestoreRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
            `RESOURCE_NOT_FOUND` can also mean `snapshot_id` is not a snapshot of this instance.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `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.
            `INVALID_RESOURCE_STATE`: the instance 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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `snapshot_id` `REQUIRED`: send a snapshot of this instance.
            `confirm_name` `REQUIRED` or `CONFIRM_NAME_MISMATCH`: repeat the instance's current name exactly.
            `UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/instances/{instance_id}/security-group:
    put:
      tags:
        - Instances
      summary: "Change an instance's security group"
      description: |-
        Moves a standard instance to another of your security groups. The instance must be stopped; the new group applies when it starts again.

        If the instance has a private database network interface, the new group must allow nothing from the private network's range (`10.250.0.0/20` in `af-abj-1`): a group with any ingress rule whose source overlaps it, `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`.
      operationId: changeInstanceSecurityGroup
      security:
        - apiKey: []
      x-scope: write
      x-order: 18
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChangeSecurityGroupRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `INSTANCE_MUST_BE_STOPPED`: stop the instance first; the new group is applied when it starts again.
            `SECURITY_GROUPS_NOT_SUPPORTED_IN_ZONE`: a VPC instance uses its subnet's firewall rules, not a security group.
            `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.
            `INVALID_RESOURCE_STATE`: the instance 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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `security_group_id` `REQUIRED` or `SECURITY_GROUP_NOT_FOUND`: send the id of one of your security groups.
            `security_group_id` `SECURITY_GROUP_ALLOWS_PRIVATE_NETWORK`: the group allows inbound traffic from the private database network, which this instance is attached to; the message names the range and the rules. Replace `0.0.0.0/0` (including the default group's ICMP rule) with narrower ranges that leave out the private range, then retry.
            `UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/instances/{instance_id}/private-network:
    post:
      tags:
        - Instances
      summary: "Add a private database network interface to an instance"
      description: |-
        Adds a second network interface on the zone's private database network to a standard instance, so it can reach your managed databases there by their private address. Only in a zone with a private database network (`af-abj-1`); a VPC instance already reaches databases in its own VPC. The interface is free.

        When the operation succeeds, the instance's `private_network_state` is `attached` and `private_network_ip` is the interface's address: allow it on each database as a `/32` access rule (*Replace access rules*).

        **The instance's security group must allow nothing from the private network's range** (`10.250.0.0/20`; the zone's `private_network_cidr` in `GET /public/v1/regions`). A security group applies to every interface of an instance, so any ingress rule whose source overlaps that range, whatever its protocol or ports, would let every database and every other customer's private interface on the network reach this instance. That includes `0.0.0.0/0` and `10.0.0.0/8`, and the ICMP-from-anywhere rule of your organization's default security group. Such a group is refused, naming the rules to change: replace `0.0.0.0/0` with the addresses you actually need, or with ranges that leave out `10.250.0.0/20`, or move the instance to a group without such rules. While the instance has the interface, rule changes and group moves that would allow the range are refused too.

        The instance must be running or stopped with no other change in progress. A running instance gets the interface without a restart.

        **In the guest** the interface stays down until you configure it. On Ubuntu add it to netplan with DHCP only, never the default route or DNS: `dhcp4: true` with `dhcp4-overrides: {use-routes: false, use-dns: false}` for the new interface (often `ens4` or `eth1`), then run `netplan apply`.

        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`. If the operation fails, its `failure.code` says why: `SECURITY_GROUP_ALLOWS_PRIVATE_NETWORK` (a rule allowing the range was added meanwhile; nothing was attached), `INSTANCE_BUSY` (the instance kept changing; retry when it has settled), or `INSTANCE_PRIVATE_NETWORK_ATTACH_FAILED` (retry later, or contact support). The instance itself is never marked failed for it.

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

        **Scope:** `write`.
      operationId: attachInstancePrivateNetwork
      security:
        - apiKey: []
      x-scope: write
      x-order: 102
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
              examples:
                accepted:
                  summary: "Accepted; read the instance for private_network_ip once it succeeds"
                  value: {"operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w", "resource_id": "vm_06gk3x9tq7m2v8r4y1c5d0zp8w", "status": "submitting"}
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
            `NOT_FOUND`: private database network interfaces are not offered on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `PRIVATE_NETWORK_NOT_AVAILABLE`: this instance's zone has no private database network for instances (VPC instances reach databases in their own VPC). Nothing to attach.
            `SECURITY_GROUP_ALLOWS_PRIVATE_NETWORK`: the instance's security group allows inbound traffic from the private network's range; `detail` names the range and each offending rule. Replace `0.0.0.0/0` in those rules (including the default group's ICMP rule) with narrower ranges that leave out `10.250.0.0/20`, or move the instance to another group, then retry.
            `PRIVATE_NETWORK_ALREADY_ATTACHED`: the instance already has the interface, or it is being added. Read the instance for `private_network_ip`.
            `PRIVATE_NETWORK_CHANGE_IN_PROGRESS`: the interface is being added or removed. Wait for that operation to finish.
            `INSTANCE_NOT_SETTLED`: the instance is starting, stopping, resizing, restoring or being deleted. Wait until it is running or stopped, then retry.
            `ACCOUNT_SUSPENDED`: an invoice is overdue, or the organization was suspended for non-payment. Pay the outstanding invoice in the console, then retry.
            `CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
    delete:
      tags:
        - Instances
      summary: "Remove an instance's private database network interface"
      description: |-
        Removes the instance's interface on the private database network. The instance must be running or stopped with no other change in progress; a running instance keeps running. `private_network_ip` stays readable until the operation succeeds; then remove any database access rule for it, and the interface from the guest's network configuration.

        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`. A detach that cannot complete fails with `INSTANCE_PRIVATE_NETWORK_DETACH_FAILED`; retry later.

        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`.
      operationId: detachInstancePrivateNetwork
      security:
        - apiKey: []
      x-scope: write
      x-destructive: true
      x-order: 103
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
              examples:
                accepted:
                  summary: "Accepted"
                  value: {"operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w", "resource_id": "vm_06gk3x9tq7m2v8r4y1c5d0zp8w", "status": "submitting"}
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
            `NOT_FOUND`: private database network interfaces are not offered on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `PRIVATE_NETWORK_NOT_ATTACHED`: the instance has no private network interface to remove.
            `PRIVATE_NETWORK_CHANGE_IN_PROGRESS`: the interface is being added or removed. Wait for that operation to finish.
            `INSTANCE_NOT_SETTLED`: the instance is starting, stopping, resizing, restoring or being deleted. Wait until it is running or stopped, then retry.
            `CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/instances/{instance_id}/snapshot-schedule:
    get:
      tags:
        - Snapshots
      summary: "Get an instance's snapshot schedule"
      description: |-
        The instance's automatic snapshot schedule: how often a snapshot of its root disk is taken, how many are kept, and when the next one runs.

        **Scope:** `read`.
      operationId: getInstanceSnapshotSchedule
      security:
        - apiKey: []
      x-scope: read
      x-order: 29
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
      responses:
        200:
          description: The schedule.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SnapshotSchedule"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
            `SNAPSHOT_SCHEDULE_NOT_FOUND`: no schedule has been set for this resource. Set one with the `PUT` on this path.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    put:
      tags:
        - Snapshots
      summary: "Set an instance's snapshot schedule"
      description: |-
        Turns automatic snapshots of the root disk on, changes them, or pauses them with `enabled: false`. `frequency` is `daily`, `weekly` or `monthly`; `retention_count` (1 to 168, default 7) successful snapshots are kept and older automatic ones are deleted. Automatic snapshots are billed like manual ones.

        Returns the schedule at once (`200`); no operation to follow. Send an `Idempotency-Key`: a retry with the same key and settings returns the same schedule.

        **Scope:** `write`. Lowering `retention_count` deletes the oldest copies, so an AI app's key also needs `delete` for that (`403 INSUFFICIENT_SCOPE` otherwise).
      operationId: putInstanceSnapshotSchedule
      security:
        - apiKey: []
      x-scope: write
      x-order: 30
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PutSnapshotScheduleRequest"
      responses:
        200:
          description: The schedule.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SnapshotSchedule"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `IDEMPOTENCY_CONFLICT`: this `Idempotency-Key` was already used with different settings. Use a new key for a different request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `frequency` `REQUIRED`: send `daily`, `weekly` or `monthly`.
            `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: `frequency` is not `daily`, `weekly` or `monthly`, or `retention_count` is outside 1 to 168.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/instances/{instance_id}/snapshots:
    get:
      tags:
        - Snapshots
      summary: "List an instance's snapshots"
      description: |-
        Snapshots of this instance's root disk, oldest first.

        **Scope:** `read`.
      operationId: listInstanceSnapshots
      security:
        - apiKey: []
      x-scope: read
      x-order: 27
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Q"
        - $ref: "#/components/parameters/DesiredState"
        - $ref: "#/components/parameters/ObservedState"
      responses:
        200:
          description: A page of snapshots.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SnapshotList"
        400:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    post:
      tags:
        - Snapshots
      summary: Snapshot an instance
      description: |-
        Takes a snapshot of the instance's root disk, named `name` (unique among this instance's snapshots). A snapshot of a running instance is crash-consistent: what the disk held at that instant. Billed on its physical size, hourly, until deleted.

        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`.
      operationId: createInstanceSnapshot
      security:
        - apiKey: []
      x-scope: write
      x-order: 28
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSnapshotRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `SNAPSHOT_NAME_TAKEN`: this instance already has a snapshot with that name. Choose a different name.
            `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 instance 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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/instances/{instance_id}/ssh-access:
    post:
      tags:
        - Instances
      summary: Grant temporary SSH access
      description: |-
        Opens port 22 of a standard instance to the address this request comes from, for 15 minutes, so you can SSH in without leaving the port open. The instance must be running and be the only one using its security group.

        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`. Then read the grant (`GET /public/v1/instances/{instance_id}/ssh-access/{grant_id}`, the operation's `resource_id` is the grant id) for the `host` and `port` to connect to once `status` is `active`.

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

        **Scope:** `write`.
      operationId: openSSHAccess
      security:
        - apiKey: []
      x-scope: write
      x-order: 20
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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_CALLER_ADDRESS`: the address this request comes from could not be used to scope access. Retry from a normal internet connection.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `SSH_ACCESS_INSTANCE_NOT_RUNNING`: the instance must be running. Start it, then retry.
            `SSH_ACCESS_SECURITY_GROUP_SHARED`: the instance's security group is used by other instances too, so opening it would open them all. Move the instance to its own security group, then retry.
            `SSH_ACCESS_NOT_SUPPORTED_IN_ZONE`: temporary SSH access is not available for VPC instances yet. Use a public IP with a port forwarding rule instead.
            `INVALID_RESOURCE_STATE`: the instance 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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/instances/{instance_id}/ssh-access/{grant_id}:
    get:
      tags:
        - Instances
      summary: Get an SSH access grant
      description: |-
        A temporary SSH access grant: its `status`, and the `host` and `port` to connect to once it is `active`. It expires by itself at `expires_at`.

        **Scope:** `read`.
      operationId: getSSHAccess
      security:
        - apiKey: []
      x-scope: read
      x-order: 21
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - name: grant_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
      responses:
        200:
          description: The grant.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SSHAccessGrant"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no grant with this id on this instance, or the instance is not in your organization. Check both ids.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    delete:
      tags:
        - Instances
      summary: Revoke SSH access
      description: |-
        Closes the port opened by a grant before it expires.

        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`.
      operationId: revokeSSHAccess
      security:
        - apiKey: []
      x-scope: write
      x-destructive: true
      x-order: 22
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - name: grant_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no grant with this id on this instance, or the instance is not in your organization. Check both ids.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `INVALID_RESOURCE_STATE`: the grant is already revoked or expired. Nothing to do.
            `CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/instances/{instance_id}/start:
    post:
      tags:
        - Instances
      summary: Start an instance
      description: |-
        Starts a stopped instance. A running instance 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`.
      operationId: startInstance
      security:
        - apiKey: []
      x-scope: write
      x-order: 11
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `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 instance 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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED` with no `errors` entry: the instance has been deleted.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/instances/{instance_id}/stop:
    post:
      tags:
        - Instances
      summary: Stop an instance
      description: |-
        Shuts the instance down. A stopped instance keeps its disks and addresses and is billed for storage only.

        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.

        For the first 5 minutes after a new instance is created, this returns `409` with code `INSTANCE_POWER_LOCKED` while first boot finishes. Wait the `Retry-After` header's seconds, then try again. Pending, provisioning, or uncertain instances also return this 409 before the first run is recorded, without `Retry-After`; wait for the instance to reach running first.

        **Scope:** `write`.
      operationId: stopInstance
      security:
        - apiKey: []
      x-scope: write
      x-order: 12
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `INVALID_RESOURCE_STATE`: the instance 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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED` with no `errors` entry: the instance has been deleted.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/instances/{instance_id}/usage:
    get:
      tags:
        - Usage
      summary: "Get an instance's usage"
      description: |-
        Measured usage for this instance in the current billing period: running hours, storage, memory and network.

        **Scope:** `read`.
      operationId: getInstanceUsage
      security:
        - apiKey: []
      x-scope: read
      x-order: 25
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
      responses:
        200:
          description: Usage.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UsageSummary"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/instances/{instance_id}/volumes:
    get:
      tags:
        - Volumes
      summary: "List an instance's volumes"
      description: |-
        The volumes attached to this instance.

        **Scope:** `read`.
      operationId: listInstanceVolumes
      security:
        - apiKey: []
      x-scope: read
      x-order: 26
      parameters:
        - name: instance_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Q"
        - $ref: "#/components/parameters/DesiredState"
        - $ref: "#/components/parameters/ObservedState"
      responses:
        200:
          description: A page of volumes.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VolumeList"
        400:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no instance with this id in your organization, or the id is not a instance id (`vm_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/me:
    get:
      tags:
        - Account
      summary: Describe this API key
      description: |-
        The organization and project the key acts in, its scopes and when it expires. Call it to check that a key works before using it.

        **Scope:** `read`.
      operationId: getMe
      security:
        - apiKey: []
      x-scope: read
      x-order: 1
      responses:
        200:
          description: The key.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Me"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    delete:
      tags:
        - Account
      summary: Revoke this API key
      description: |-
        Revokes the API key that sends this request, at once and for good. This is how an AI app disconnects itself and how `pantech auth logout` ends its key. Other keys are untouched.

        It needs no `Idempotency-Key`: once the key is revoked, repeating the call gets `401 UNAUTHENTICATED`, because the key no longer works. It works even while the organization has AI app access turned off.

        **Scope:** `read` (any key may revoke itself).
      operationId: revokeMe
      security:
        - apiKey: []
      x-scope: read
      x-order: 2
      responses:
        204:
          description: The key is revoked.
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/networks:
    get:
      tags:
        - Networks
      summary: List VPC networks
      description: |-
        Your VPC networks, newest first. Filter by name with `q`, or by state.

        **Scope:** `read`.
      operationId: listNetworks
      security:
        - apiKey: []
      x-scope: read
      x-order: 33
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Q"
        - $ref: "#/components/parameters/DesiredState"
        - $ref: "#/components/parameters/ObservedState"
      responses:
        200:
          description: A page of networks.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NetworkList"
        400:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    post:
      tags:
        - Networks
      summary: Create a VPC network
      description: |-
        Creates a VPC: a private network of your own, with a CIDR such as `10.0.0.0/16`. Then add subnets to it, and instances or databases to the subnets. A VPC is billed monthly: see *Price a VPC network*.

        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 network'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`.
      operationId: createNetwork
      security:
        - apiKey: []
      x-scope: write
      x-order: 34
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateNetworkRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        409:
          description: |-
            `NETWORK_NAME_TAKEN`: you already have a VPC with this name. Choose a different name.
            `ACCOUNT_SUSPENDED`: an invoice is overdue, or the organization was suspended for non-payment. Pay the outstanding invoice in the console, then retry.
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
            `cidr` `REQUIRED`: send an IPv4 CIDR such as `10.0.0.0/16`.
            `region` `REGION_NOT_AVAILABLE`: this region cannot take VPC networks now. Pick one from `GET /public/v1/regions`.
            `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: `cidr` is not a valid IPv4 CIDR.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/networks/quote:
    get:
      tags:
        - Networks
      summary: Price a VPC network
      description: |-
        What one more VPC network costs per month, in your organization's currency. `priced` is false, with an `unpriced_reason`, when no price applies.

        **Scope:** `read`.
      operationId: quoteNetwork
      security:
        - apiKey: []
      x-scope: read
      x-order: 35
      responses:
        200:
          description: The price.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MonthlyQuote"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `NOT_FOUND`: cost quotes are not available on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/networks/{network_id}:
    get:
      tags:
        - Networks
      summary: Get a VPC network
      description: |-
        One VPC network, with the number of instances in it.

        **Scope:** `read`.
      operationId: getNetwork
      security:
        - apiKey: []
      x-scope: read
      x-order: 36
      parameters:
        - name: network_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
      responses:
        200:
          description: The network.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Network"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no VPC network with this id in your organization, or the id is not a VPC network id (`net_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    delete:
      tags:
        - Networks
      summary: Delete a VPC network
      description: |-
        Deletes a VPC network. It must hold no instances (in any of its subnets) and no public IPs.

        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`.
      operationId: deleteNetwork
      security:
        - apiKey: []
      x-scope: write
      x-destructive: true
      x-order: 37
      parameters:
        - name: network_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no VPC network with this id in your organization, or the id is not a VPC network id (`net_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `NETWORK_HAS_INSTANCES`: a subnet of this network still has instances. Delete or move them first.
            `NETWORK_HAS_PUBLIC_IPS`: the network still holds public IPs. Release them first.
            `INVALID_RESOURCE_STATE`: the network 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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/networks/{network_id}/subnets:
    get:
      tags:
        - Networks
      summary: List subnets
      description: |-
        The VPC's subnets.

        **Scope:** `read`.
      operationId: listSubnets
      security:
        - apiKey: []
      x-scope: read
      x-order: 38
      parameters:
        - name: network_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Q"
        - $ref: "#/components/parameters/DesiredState"
        - $ref: "#/components/parameters/ObservedState"
      responses:
        200:
          description: A page of subnets.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SubnetList"
        400:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no VPC network with this id in your organization, or the id is not a VPC network id (`net_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    post:
      tags:
        - Networks
      summary: Create a subnet
      description: |-
        Creates a subnet inside the VPC's CIDR, such as `10.0.1.0/24`. Instances and databases are placed in subnets; each subnet has its own firewall rules.

        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 subnet'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`.
      operationId: createSubnet
      security:
        - apiKey: []
      x-scope: write
      x-order: 39
      parameters:
        - name: network_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSubnetRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no VPC network with this id in your organization, or the id is not a VPC network id (`net_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `INVALID_RESOURCE_STATE`: the network 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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
            `cidr` `REQUIRED`: send an IPv4 CIDR inside the VPC's CIDR.
            `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: `cidr` is not a valid IPv4 CIDR.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/operations/{operation_id}:
    get:
      tags:
        - Operations
      summary: Get an operation
      description: |-
        One asynchronous operation. Every write that returns `202` gives you its `operation_id`: poll this until `status` is `succeeded` or `failed` (wait a second or two between polls), or use *Stream an operation* instead.

        A failed operation has `failure.code`, a stable code to branch on, and `failure.reason`, a readable sentence safe to show to your users.

        **Scope:** `read`.
      operationId: getOperation
      security:
        - apiKey: []
      x-scope: read
      x-order: 82
      parameters:
        - name: operation_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
      responses:
        200:
          description: The operation.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Operation"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no operation with this id in your organization, or the id is not a operation id (`op_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/operations/{operation_id}/events:
    get:
      tags:
        - Operations
      summary: Stream an operation
      description: |-
        Server-Sent Events for one operation: an `operation` event (data: the Operation) each time it changes, ending when it succeeds or fails. A `: keepalive` comment is sent while it is idle. Use it instead of polling.

        If the stream stops before the operation finishes, its last event is `error`, with data `{"code", "detail", "request_id"}`: `OPERATION_STREAM_UNAVAILABLE` after 30 seconds of failing to read the operation, `UNAUTHENTICATED` within 30 seconds of the key being revoked or expiring, or `OPERATION_STREAM_TIMEOUT` after 15 minutes open. Reconnect, or poll `GET /public/v1/operations/{operation_id}`.

        **Scope:** `read`.
      operationId: streamOperation
      security:
        - apiKey: []
      x-scope: read
      x-order: 83
      parameters:
        - name: operation_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
      responses:
        200:
          description: The event stream.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            text/event-stream:
              schema:
                type: string
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no operation with this id in your organization, or the id is not a operation id (`op_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/plans:
    get:
      tags:
        - Catalogue
      summary: List plans
      description: |-
        Instance sizes (vCPU, memory and disk), priced in your organization's currency. Use a `slug` as `plan_slug` for an instance or a database. Send `placement=vpc` to price a VPC instance (no public address of its own), or leave it out for a standard instance.

        **Scope:** `read`.
      operationId: listPlans
      security:
        - apiKey: []
      x-scope: read
      x-order: 3
      parameters:
        - name: placement
          in: query
          description: Price for standard instances or VPC instances.
          schema:
            type: string
            enum:
              - standard
              - vpc
            default: standard
      responses:
        200:
          description: Plans.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlanList"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `placement` `INVALID_PLACEMENT`: use `standard` or `vpc`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/load-balancers:
    get:
      tags:
        - Load balancers
      summary: List load balancers
      description: |-
        Your load balancers, newest first, each with its targets. Deleted and failed ones are left out. Narrow it with `network_id` or `public_ip_id`.

        **Scope:** `read`.
      operationId: listLoadBalancers
      security:
        - apiKey: []
      x-scope: read
      x-order: 110
      parameters:
        - name: network_id
          in: query
          required: false
          description: "Only load balancers in this private network (`net_…`)."
          schema:
            type: string
        - name: public_ip_id
          in: query
          required: false
          description: "Only load balancers on this public IP (`pip_…`)."
          schema:
            type: string
      responses:
        200:
          description: The load balancers.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LoadBalancerList"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `NOT_FOUND`: load balancers are not offered on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    post:
      tags:
        - Load balancers
      summary: Create a load balancer
      description: |-
        Spreads one TCP port of a `load_balancer` public IP across instances in one subnet of that IP's private network (VPC). The public IP must be active and have `purpose` `load_balancer`; one public IP can carry several load balancers, one per public port. `private_port` defaults to `public_port`, and `algorithm` to `roundrobin`. `cidr_list` limits which source addresses may connect (default: anywhere). The subnet's firewall rules must allow the port.

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

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

        **Scope:** `write`.
      operationId: createLoadBalancer
      security:
        - apiKey: []
      x-scope: write
      x-order: 111
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateLoadBalancerRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: the public IP, subnet or an instance does not exist in your organization. Check the ids.
            `NOT_FOUND`: load balancers are not offered on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `PUBLIC_IP_NOT_FOR_LOAD_BALANCER`: use an active public IP with `purpose` `load_balancer`.
            `PORT_IN_USE`: another load balancer on this public IP already uses this port. Choose another port, or delete that load balancer.
            `CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `name`, `public_ip_id`, `subnet_id` or `public_port` `REQUIRED`: send them.
            `public_port` `INVALID_LOAD_BALANCER`: `name` 1 to 63 characters, `algorithm` `roundrobin`, `leastconn` or `source`, ports 1 to 65535, and at most 20 IPv4 CIDRs in `cidr_list`.
            `subnet_id` `SUBNET_NOT_IN_NETWORK`: the subnet is not in the public IP's private network.
            `instance_ids` `INSTANCE_NOT_IN_SUBNET`: every instance must be in the load balancer's subnet.
            `instance_ids` `TOO_MANY`: at most 50 instances.
            `UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/load-balancers/{load_balancer_id}:
    get:
      tags:
        - Load balancers
      summary: Get a load balancer
      description: |-
        One load balancer, with its targets. `in_sync` is false while a change you made has not reached it yet.

        **Scope:** `read`.
      operationId: getLoadBalancer
      security:
        - apiKey: []
      x-scope: read
      x-order: 112
      parameters:
        - name: load_balancer_id
          in: path
          required: true
          description: "The load balancer's id (`lb_…`)."
          schema:
            type: string
      responses:
        200:
          description: The load balancer.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LoadBalancer"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no load balancer with this id in your organization, or the id is not a load balancer id (`lb_…`). Check the id.
            `NOT_FOUND`: load balancers are not offered on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    patch:
      tags:
        - Load balancers
      summary: Change a load balancer
      description: |-
        Renames it, changes its `algorithm`, or replaces its targets: `instance_ids`, when sent (even empty), is the whole new set. Fields you leave out stay as they are. The ports, the public IP, the subnet and `cidr_list` cannot change: create a new load balancer for those. A change that changes nothing returns an empty `operation_id`.

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

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

        **Scope:** `write`.
      operationId: updateLoadBalancer
      security:
        - apiKey: []
      x-scope: write
      x-order: 113
      parameters:
        - name: load_balancer_id
          in: path
          required: true
          description: "The load balancer's id (`lb_…`)."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateLoadBalancerRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no load balancer with this id in your organization, or the id is not a load balancer id (`lb_…`). Check the id.
            `NOT_FOUND`: load balancers are not offered on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `LOAD_BALANCER_NOT_CHANGEABLE`: the load balancer is being deleted or has failed.
            `CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `public_port` `INVALID_LOAD_BALANCER`: `name` 1 to 63 characters and `algorithm` `roundrobin`, `leastconn` or `source`.
            `instance_ids` `INSTANCE_NOT_IN_SUBNET`: every instance must be in the load balancer's subnet.
            `instance_ids` `TOO_MANY`: at most 50 instances.
            `UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
    delete:
      tags:
        - Load balancers
      summary: Delete a load balancer
      description: |-
        Deletes the load balancer. Traffic to its port stops once the operation succeeds; the instances and the public IP are kept.

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

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

        **Scope:** `write`. Destructive: an AI app's key also needs `delete`.
      operationId: deleteLoadBalancer
      security:
        - apiKey: []
      x-scope: write
      x-destructive: true
      x-order: 114
      parameters:
        - name: load_balancer_id
          in: path
          required: true
          description: "The load balancer's id (`lb_…`)."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no load balancer with this id in your organization, or the id is not a load balancer id (`lb_…`). Check the id.
            `NOT_FOUND`: load balancers are not offered on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/port-forwarding-rules/{rule_id}:
    delete:
      tags:
        - Public IPs
      summary: Delete a port forwarding rule
      description: |-
        Deletes one port forwarding rule. Traffic to those ports stops once the operation succeeds.

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

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

        **Scope:** `write`. Destructive: an AI app's key also needs `delete`.
      operationId: deletePortForwardingRule
      security:
        - apiKey: []
      x-scope: write
      x-destructive: true
      x-order: 54
      parameters:
        - name: rule_id
          in: path
          required: true
          description: "The rule's id (`aclr_...` for a firewall rule, `pfr_...` for a port forwarding rule)."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no port forwarding rule with this id in your organization, or the id is not a port forwarding rule id (`pfr_…`). Check the id.
            `NOT_FOUND`: port forwarding is not offered on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `INVALID_RESOURCE_STATE`: the port forwarding rule cannot do this in its current state, for example while it is being created or deleted. Wait for its current operation to finish, then retry.
            `CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/pricing/estimate:
    post:
      tags:
        - Catalogue
      summary: Estimate a monthly bill
      description: |-
        Prices a whole configuration before you build it: machines by plan and running hours, extra storage, VPC networks and snapshot storage. Nothing is created and nothing is charged, so no `Idempotency-Key` is needed.

        `pricing_complete` is false when any line could not be priced; that line has `priced: false` and an `unpriced_reason`.

        **Scope:** `read`.
      operationId: estimatePricing
      security:
        - apiKey: []
      x-scope: read
      x-order: 7
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PricingEstimateRequest"
      responses:
        200:
          description: The estimate.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PricingEstimate"
        400:
          description: |-
            `INVALID_REQUEST_BODY`: the body is not a single valid JSON object. Send one JSON object with the fields documented for this endpoint.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `currency` `UNSUPPORTED_CURRENCY`: use `NGN` or `USD`.
            `machines` `TOO_MANY_MACHINES`: at most 50 machine lines.
            `machines` `INVALID_MACHINE`: each machine needs a `plan_slug`, a `quantity` above 0 and 0 to 730 `running_hours`.
            `placement` `INVALID_PLACEMENT`: use `standard` or `vpc`.
            `UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/public-ips:
    get:
      tags:
        - Public IPs
      summary: List public IPs
      description: |-
        Your VPC public IPs, or one VPC's with `network_id`.

        **Scope:** `read`.
      operationId: listPublicIPs
      security:
        - apiKey: []
      x-scope: read
      x-order: 46
      parameters:
        - name: network_id
          in: query
          schema:
            type: string
      responses:
        200:
          description: Public IPs.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicIPList"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `NOT_FOUND`: public IPs are not offered on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    post:
      tags:
        - Public IPs
      summary: Allocate a public IP
      description: |-
        Allocates a public IPv4 address for a VPC. `static_nat` (the default) maps every port of the address to one instance in `network_id`, sent as `instance_id`; `port_forwarding`, where offered, carries per-port rules instead (add them with *Add a port forwarding rule*). The network must be active. A public IP is billed monthly: see *Price a public IP*.

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

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

        **Scope:** `write`.
      operationId: createPublicIP
      security:
        - apiKey: []
      x-scope: write
      x-order: 47
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePublicIPRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          description: |-
            `INSUFFICIENT_SCOPE`: this API key has only the `read` scope. Use a key with `write` access.
            `FORBIDDEN`: the member who created this key may not do this in the organization, or has left it. Use a key created by an owner or admin.
            `AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server. An owner can turn it on in the console under Organization › AI apps.
            `STATIC_NAT_LIMIT_EXCEEDED`: your organization has reached its limit of static NAT public IPs. Release one you no longer need, or contact support for a higher limit.
            `PUBLIC_IP_LIMIT_EXCEEDED`: your organization has reached its limit of public IPs of any purpose (20 by default). Release one you no longer need, or contact support for a higher limit.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        404:
          description: |-
            `NOT_FOUND`: public IPs are not offered on this platform yet.
            `RESOURCE_NOT_FOUND`: `network_id` is not one of your VPC networks.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `NETWORK_NOT_READY`: public IPs can only be added to an active VPC network. Wait for the network to be active.
            `INSTANCE_ALREADY_HAS_PUBLIC_IP`: the instance already has a public IP. Release it before adding another.
            `ACCOUNT_SUSPENDED`: an invoice is overdue, or the organization was suspended for non-payment. Pay the outstanding invoice in the console, then retry.
            `CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `network_id` `REQUIRED`: send the VPC network.
            `purpose` `PURPOSE_NOT_AVAILABLE`: use `static_nat`, `port_forwarding` or `load_balancer`.
            `instance_id` `INSTANCE_NOT_IN_NETWORK`: the instance is not in `network_id`.
            `instance_id` `NOT_ALLOWED`: a `port_forwarding` address takes no instance; each rule names its own.
            `UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/public-ips/quote:
    get:
      tags:
        - Public IPs
      summary: Price a public IP
      description: |-
        What one more public IP costs per month, in your organization's currency.

        **Scope:** `read`.
      operationId: quotePublicIP
      security:
        - apiKey: []
      x-scope: read
      x-order: 48
      responses:
        200:
          description: The price.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MonthlyQuote"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `NOT_FOUND`: cost quotes are not available on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/public-ips/{public_ip_id}:
    get:
      tags:
        - Public IPs
      summary: Get a public IP
      description: |-
        One public IP, with the instance it maps to.

        **Scope:** `read`.
      operationId: getPublicIP
      security:
        - apiKey: []
      x-scope: read
      x-order: 49
      parameters:
        - name: public_ip_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
      responses:
        200:
          description: The public IP.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicIP"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no public IP with this id in your organization, or the id is not a public IP id (`pip_…`). Check the id.
            `NOT_FOUND`: public IPs are not offered on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    delete:
      tags:
        - Public IPs
      summary: Release a public IP
      description: |-
        Releases a public IP. The address is returned to the pool and may be given to someone else; billing stops when the operation succeeds. Delete its port forwarding rules first.

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

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

        **Scope:** `write`. Destructive: an AI app's key also needs `delete`.
      operationId: deletePublicIP
      security:
        - apiKey: []
      x-scope: write
      x-destructive: true
      x-order: 50
      parameters:
        - name: public_ip_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no public IP with this id in your organization, or the id is not a public IP id (`pip_…`). Check the id.
            `NOT_FOUND`: public IPs are not offered on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `PUBLIC_IP_HAS_PORT_FORWARDS`: delete this address's port forwarding rules first.
            `PUBLIC_IP_HAS_LOAD_BALANCERS`: delete this address's load balancers first.
            `INVALID_RESOURCE_STATE`: the public IP cannot do this in its current state, for example while it is being created or deleted. Wait for its current operation to finish, then retry.
            `CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/public-ips/{public_ip_id}/attach:
    post:
      tags:
        - Public IPs
      summary: Attach a public IP to an instance
      description: |-
        Points a static NAT public IP at an instance in the same private network (VPC), keeping the address. Use it to move an address from one instance to another without releasing it: you keep the same IP and pay for it once. If the address is attached elsewhere it moves; the instance must not already have a static NAT address of its own.

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

        **Scope:** `write`.
      operationId: attachPublicIP
      security:
        - apiKey: []
      x-scope: write
      x-order: 51
      parameters:
        - name: public_ip_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AttachPublicIPRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no public IP with this id in your organization, or the id is not a public IP id (`pip_…`). Check the id.
            `NOT_FOUND`: public IPs are not offered on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `PUBLIC_IP_NOT_STATIC_NAT`: only a static NAT address can be attached or detached; port forwarding and load balancer addresses use their rules.
            `INSTANCE_ALREADY_HAS_PUBLIC_IP`: the instance already has a static NAT address. Detach or release that one first.
            `INVALID_RESOURCE_STATE`: the public IP cannot do this in its current state, for example while it is being created or deleted. Wait for its current operation to finish, then retry.
            `CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: `instance_id` `REQUIRED`, or `INSTANCE_NOT_IN_NETWORK` (the instance must be in the address's private network). With no `errors` entry: the platform refused the request, for example because the instance is deleted.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/public-ips/{public_ip_id}/detach:
    post:
      tags:
        - Public IPs
      summary: Detach a public IP from its instance
      description: |-
        Unmaps a static NAT public IP from its instance. The address stays yours (and is billed once, as before) until you attach it to another instance or release it. The instance keeps running and can then be deleted.

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

        **Scope:** `write`.
      operationId: detachPublicIP
      security:
        - apiKey: []
      x-scope: write
      x-order: 52
      parameters:
        - name: public_ip_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no public IP with this id in your organization, or the id is not a public IP id (`pip_…`). Check the id.
            `NOT_FOUND`: public IPs are not offered on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `PUBLIC_IP_NOT_STATIC_NAT`: only a static NAT address can be attached or detached; port forwarding and load balancer addresses use their rules.
            `INVALID_RESOURCE_STATE`: the public IP cannot do this in its current state, for example while it is being created or deleted. Wait for its current operation to finish, then retry.
            `CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/public-ips/{public_ip_id}/port-forwarding-rules:
    get:
      tags:
        - Public IPs
      summary: List port forwarding rules
      description: |-
        The address's port forwarding rules.

        **Scope:** `read`.
      operationId: listPortForwardingRules
      security:
        - apiKey: []
      x-scope: read
      x-order: 51
      parameters:
        - name: public_ip_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
      responses:
        200:
          description: The rules.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PortForwardingRuleList"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no public IP with this id in your organization, or the id is not a public IP id (`pip_…`). Check the id.
            `NOT_FOUND`: port forwarding is not offered on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    post:
      tags:
        - Public IPs
      summary: Add a port forwarding rule
      description: |-
        Forwards a port or range of a `port_forwarding` public IP to an instance in its VPC. Defaults: `public_port_end` equals `public_port_start`, and the private range equals the public one (or starts at `private_port_start` with the same width).

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

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

        **Scope:** `write`.
      operationId: createPortForwardingRule
      security:
        - apiKey: []
      x-scope: write
      x-order: 52
      parameters:
        - name: public_ip_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePortForwardingRuleRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no public IP with this id in your organization, or the id is not a public IP id (`pip_…`). Check the id.
            `NOT_FOUND`: port forwarding is not offered on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `PUBLIC_IP_NOT_FOR_PORT_FORWARDING`: use an active public IP with `purpose` `port_forwarding`.
            `PORTS_IN_USE`: another rule on this address already forwards some of these ports for this protocol. Choose other ports, or delete that rule.
            `INVALID_RESOURCE_STATE`: the public IP cannot do this in its current state, for example while it is being created or deleted. Wait for its current operation to finish, then retry.
            `CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `instance_id` or `protocol` `REQUIRED`: send both.
            `instance_id` `INSTANCE_NOT_IN_NETWORK`: the instance is not in the address's VPC.
            `public_port_start` `INVALID_PORTS`: use `tcp` or `udp`, ports 1 to 65535, and public and private ranges of the same width.
            `UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/public-ips/{public_ip_id}/port-forwarding-rules/{rule_id}:
    get:
      tags:
        - Public IPs
      summary: Get a port forwarding rule
      description: |-
        One port forwarding rule on the address.

        **Scope:** `read`.
      operationId: getPortForwardingRule
      security:
        - apiKey: []
      x-scope: read
      x-order: 53
      parameters:
        - name: public_ip_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - name: rule_id
          in: path
          required: true
          description: "The rule's id (`aclr_...` for a firewall rule, `pfr_...` for a port forwarding rule)."
          schema:
            type: string
      responses:
        200:
          description: The rule.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PortForwardingRule"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no rule with this id on this address, or no such address in your organization. Check both ids.
            `NOT_FOUND`: port forwarding is not offered on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/regions:
    get:
      tags:
        - Catalogue
      summary: List regions
      description: |-
        Regions and their zones, and whether each zone can take standard instances or VPC networks right now (`available`, with an `unavailable_reason` when not). Use a zone as `zone_id` for a database, or a region as `region` elsewhere.

        **Scope:** `read`.
      operationId: listRegions
      security:
        - apiKey: []
      x-scope: read
      x-order: 2
      responses:
        200:
          description: Regions.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RegionList"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/kubernetes-versions:
    get:
      tags:
        - Kubernetes
      summary: List Kubernetes versions
      description: |-
        The Kubernetes versions you can create clusters with, newest first, each in one zone. `ha_zone_ids` are the zones where a highly available control plane (3 control nodes) can be created now. A version is withdrawn from new clusters before its upstream end of life; clusters already on it keep running it.

        **Scope:** `read`.
      operationId: listKubernetesVersions
      security:
        - apiKey: []
      x-scope: read
      x-order: 115
      parameters:
        - name: zone_id
          in: query
          required: false
          description: "Only versions in this zone."
          schema:
            type: string
      responses:
        200:
          description: The versions.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KubernetesVersionList"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/kubernetes-clusters:
    get:
      tags:
        - Kubernetes
      summary: List Kubernetes clusters
      description: |-
        Your Kubernetes clusters, newest first. Deleted and failed clusters are left out.

        **Scope:** `read`.
      operationId: listKubernetesClusters
      security:
        - apiKey: []
      x-scope: read
      x-order: 116
      responses:
        200:
          description: The clusters.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KubernetesClusterList"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
    post:
      tags:
        - Kubernetes
      summary: Create a Kubernetes cluster
      description: |-
        Creates a managed Kubernetes cluster. Every node, control and worker, is a VM of `node_plan` in your account and is billed as one; there is no separate control-plane fee. In a VPC zone the nodes go in `subnet_id`; in a standard zone leave `subnet_id` out. `control_nodes` is 1, or 3 where the zone offers a highly available control plane (see `ha_zone_ids` on the versions list). Persistent volumes work out of the box. Download the kubeconfig once the cluster is `running`.

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

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

        **Scope:** `write`.
      operationId: createKubernetesCluster
      security:
        - apiKey: []
      x-scope: write
      x-order: 117
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateKubernetesClusterRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        402:
          description: |-
            `INSUFFICIENT_CREDIT`: your credit does not cover the first payment for every node. Top up or add a card, then try again.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `KUBERNETES_CLUSTER_NAME_TAKEN`: you already have a cluster with this name.
            `KUBERNETES_CLUSTER_LIMIT`: your organization has reached its cluster limit. Delete a cluster, or contact support to raise it.
            `CONCURRENT_UPDATE`: the cluster changed while this request was being processed. Retry the request; if it repeats, read the cluster first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `name`, `zone_id`, `kubernetes_version_id`, `node_plan` or `workers` `REQUIRED`: send them.
            `name` `INVALID_KUBERNETES_CLUSTER_NAME`: 1 to 40 lowercase letters, digits and hyphens, starting with a letter.
            `zone_id` `INVALID_KUBERNETES_ZONE`: a zone your organization can use.
            `kubernetes_version_id` `INVALID_KUBERNETES_VERSION`: a version offered in this zone.
            `node_plan` `INVALID_KUBERNETES_NODE_PLAN`: an active plan with at least the version's minimum vCPU and memory.
            `workers` `INVALID_KUBERNETES_WORKER_COUNT`: 1 to 10.
            `control_nodes` `INVALID_KUBERNETES_CONTROL_NODES` or `KUBERNETES_HA_NOT_AVAILABLE`: 1, or 3 where offered.
            `autoscaling` `INVALID_KUBERNETES_AUTOSCALING`: min_workers and max_workers 1 to 10, min no more than max, and workers within them.
            `api_allowed_cidrs` `INVALID_API_ALLOWED_CIDRS` (at most 20 IPv4 CIDRs) or `API_ALLOWED_CIDRS_VPC_ONLY`.
            `subnet_id` `KUBERNETES_SUBNET_REQUIRED`, `KUBERNETES_SUBNET_NOT_ALLOWED` or `KUBERNETES_SUBNET_NOT_FOUND`: an active subnet in a VPC zone, and none in a standard zone.
            `UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`).
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
            `PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/kubernetes-clusters/{cluster_id}:
    get:
      tags:
        - Kubernetes
      summary: Get a Kubernetes cluster
      description: |-
        One cluster. `in_sync` is false while a change you made has not reached it yet; `available_upgrades` lists the versions it can move to now.

        **Scope:** `read`.
      operationId: getKubernetesCluster
      security:
        - apiKey: []
      x-scope: read
      x-order: 118
      parameters:
        - name: cluster_id
          in: path
          required: true
          description: "The cluster's id (`k8s_…`)."
          schema:
            type: string
      responses:
        200:
          description: The cluster.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KubernetesCluster"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `KUBERNETES_CLUSTER_NOT_FOUND`: no cluster with this id in your organization. Check the id.
            `RESOURCE_NOT_FOUND`: the id is not a cluster id (`k8s_…`).
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
    patch:
      tags:
        - Kubernetes
      summary: Change a Kubernetes cluster
      description: |-
        Sets a fixed number of worker nodes (1 to 10), turns the cluster autoscaler on or off and sets its range, or replaces the list of addresses allowed to reach the API server (VPC zone). Send any of them; the rest stay as they are. The cluster must be running. Added workers, and an autoscaler maximum above today's count, are checked against your credit and account limits; workers are billed as VMs of the node plan while they run. A change that changes nothing returns an empty `operation_id`.

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

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

        **Scope:** `write`.
      operationId: scaleKubernetesCluster
      security:
        - apiKey: []
      x-scope: write
      x-order: 119
      parameters:
        - name: cluster_id
          in: path
          required: true
          description: "The cluster's id (`k8s_…`)."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateKubernetesClusterRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        402:
          description: |-
            `INSUFFICIENT_CREDIT`: your credit does not cover the added nodes.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        404:
          description: |-
            `KUBERNETES_CLUSTER_NOT_FOUND`: no cluster with this id in your organization. Check the id.
            `RESOURCE_NOT_FOUND`: the id is not a cluster id (`k8s_…`).
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `KUBERNETES_CLUSTER_NOT_RUNNING`: start the cluster first.
            `KUBERNETES_CLUSTER_BUSY`: the cluster is already changing. Wait until it is running or stopped.
            `KUBERNETES_CLUSTER_NOT_CHANGEABLE`: the cluster cannot make this change now (stopped, being deleted, or failed).
            `CONCURRENT_UPDATE`: the cluster changed while this request was being processed. Retry the request; if it repeats, read the cluster first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: `workers` `REQUIRED` (send at least one field) or `INVALID_KUBERNETES_WORKER_COUNT` (1 to 10); `autoscaling` `INVALID_KUBERNETES_AUTOSCALING`; `api_allowed_cidrs` `INVALID_API_ALLOWED_CIDRS` or `API_ALLOWED_CIDRS_VPC_ONLY`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
            `PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
    delete:
      tags:
        - Kubernetes
      summary: Delete a Kubernetes cluster
      description: |-
        Deletes the cluster. It stops at once, disappears from your lists and stops billing; its nodes and disks are destroyed 12 hours later and cannot be recovered. Volumes your workloads created through persistent volume claims are deleted with it. Deleting a cluster already being deleted returns an empty `operation_id`.

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

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

        **Scope:** `write`. Destructive: an AI app's key also needs `delete`.
      operationId: deleteKubernetesCluster
      security:
        - apiKey: []
      x-scope: write
      x-destructive: true
      x-order: 120
      parameters:
        - name: cluster_id
          in: path
          required: true
          description: "The cluster's id (`k8s_…`)."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `KUBERNETES_CLUSTER_NOT_FOUND`: no cluster with this id in your organization. Check the id.
            `RESOURCE_NOT_FOUND`: the id is not a cluster id (`k8s_…`).
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `CONCURRENT_UPDATE`: the cluster changed while this request was being processed. Retry the request; if it repeats, read the cluster first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
            `PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/kubernetes-clusters/{cluster_id}/upgrade:
    post:
      tags:
        - Kubernetes
      summary: Upgrade a Kubernetes cluster
      description: |-
        Moves the cluster to a newer Kubernetes version: one of its `available_upgrades` (the next patch, or the next minor version). Upgrades never happen on their own and never go down. The cluster must be running; nodes are replaced one at a time.

        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`.
      operationId: upgradeKubernetesCluster
      security:
        - apiKey: []
      x-scope: write
      x-order: 121
      parameters:
        - name: cluster_id
          in: path
          required: true
          description: "The cluster's id (`k8s_…`)."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpgradeKubernetesClusterRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `KUBERNETES_CLUSTER_NOT_FOUND`: no cluster with this id in your organization. Check the id.
            `RESOURCE_NOT_FOUND`: the id is not a cluster id (`k8s_…`).
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `KUBERNETES_CLUSTER_NOT_RUNNING`: start the cluster first.
            `KUBERNETES_CLUSTER_BUSY`: the cluster is already changing. Wait until it is running or stopped.
            `KUBERNETES_CLUSTER_NOT_CHANGEABLE`: the cluster cannot make this change now (stopped, being deleted, or failed).
            `CONCURRENT_UPDATE`: the cluster changed while this request was being processed. Retry the request; if it repeats, read the cluster first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: `kubernetes_version_id` `REQUIRED`, `INVALID_KUBERNETES_VERSION` or `KUBERNETES_UPGRADE_NOT_ALLOWED` (pick one of `available_upgrades`).
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
            `PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/kubernetes-clusters/{cluster_id}/resize-plan:
    post:
      tags:
        - Kubernetes
      summary: Resize worker node compute plan
      description: |-
        Resizes the compute plan of worker nodes in the cluster via zero-downtime rolling replacement.
        A new worker node with the updated plan is provisioned, joins the cluster, workloads migrate, and the old node is retired.

        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`.

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

        **Scope:** `write`.
      operationId: resizeKubernetesClusterPlan
      security:
        - apiKey: []
      x-scope: write
      x-order: 122
      parameters:
        - name: cluster_id
          in: path
          required: true
          description: "The cluster's id (`k8s_…`)."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ResizeKubernetesClusterPlanRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `KUBERNETES_CLUSTER_NOT_FOUND`: no cluster with this id in your organization. Check the id.
            `RESOURCE_NOT_FOUND`: the id is not a cluster id (`k8s_…`).
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `KUBERNETES_CLUSTER_NOT_RUNNING`: start the cluster first.
            `KUBERNETES_CLUSTER_BUSY`: the cluster is already changing. Wait until it is running or stopped.
            `KUBERNETES_CLUSTER_NOT_CHANGEABLE`: the cluster cannot make this change now (stopped, being deleted, or failed).
            `CONCURRENT_UPDATE`: the cluster changed while this request was being processed. Retry the request; if it repeats, read the cluster first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: `node_plan` `REQUIRED` or `INVALID_KUBERNETES_NODE_PLAN`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
            `PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/kubernetes-clusters/{cluster_id}/upgrade-network:
    post:
      tags:
        - Kubernetes
      summary: Upgrade Kubernetes cluster networking
      description: |-
        Reconfigures the CNI plugin (`flannel`, `wireguard`, `calico`), Pod CIDR, Service CIDR, or API allowed CIDR ranges for the cluster.

        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`.

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

        **Scope:** `write`.
      operationId: upgradeKubernetesClusterNetwork
      security:
        - apiKey: []
      x-scope: write
      x-order: 123
      parameters:
        - name: cluster_id
          in: path
          required: true
          description: "The cluster's id (`k8s_…`)."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpgradeKubernetesNetworkRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `KUBERNETES_CLUSTER_NOT_FOUND`: no cluster with this id in your organization. Check the id.
            `RESOURCE_NOT_FOUND`: the id is not a cluster id (`k8s_…`).
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `KUBERNETES_CLUSTER_NOT_RUNNING`: start the cluster first.
            `KUBERNETES_CLUSTER_BUSY`: the cluster is already changing. Wait until it is running or stopped.
            `KUBERNETES_CLUSTER_NOT_CHANGEABLE`: the cluster cannot make this change now (stopped, being deleted, or failed).
            `CONCURRENT_UPDATE`: the cluster changed while this request was being processed. Retry the request; if it repeats, read the cluster first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: `INVALID_KUBERNETES_CNI_PLUGIN` or `INVALID_KUBERNETES_CIDR`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
            `PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/kubernetes-clusters/{cluster_id}/start:
    post:
      tags:
        - Kubernetes
      summary: Start a Kubernetes cluster
      description: |-
        Starts a stopped cluster. Its nodes are billed for compute again once it runs.

        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`.
      operationId: startKubernetesCluster
      security:
        - apiKey: []
      x-scope: write
      x-order: 122
      parameters:
        - name: cluster_id
          in: path
          required: true
          description: "The cluster's id (`k8s_…`)."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `KUBERNETES_CLUSTER_NOT_FOUND`: no cluster with this id in your organization. Check the id.
            `RESOURCE_NOT_FOUND`: the id is not a cluster id (`k8s_…`).
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `KUBERNETES_CLUSTER_BUSY`: the cluster is already changing. Wait until it is running or stopped.
            `KUBERNETES_CLUSTER_NOT_CHANGEABLE`: the cluster cannot make this change now (stopped, being deleted, or failed).
            `CONCURRENT_UPDATE`: the cluster changed while this request was being processed. Retry the request; if it repeats, read the cluster first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
            `PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/kubernetes-clusters/{cluster_id}/stop:
    post:
      tags:
        - Kubernetes
      summary: Stop a Kubernetes cluster
      description: |-
        Stops a running cluster. While stopped only its disks are billed.

        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`.
      operationId: stopKubernetesCluster
      security:
        - apiKey: []
      x-scope: write
      x-order: 123
      parameters:
        - name: cluster_id
          in: path
          required: true
          description: "The cluster's id (`k8s_…`)."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `KUBERNETES_CLUSTER_NOT_FOUND`: no cluster with this id in your organization. Check the id.
            `RESOURCE_NOT_FOUND`: the id is not a cluster id (`k8s_…`).
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `KUBERNETES_CLUSTER_BUSY`: the cluster is already changing. Wait until it is running or stopped.
            `KUBERNETES_CLUSTER_NOT_CHANGEABLE`: the cluster cannot make this change now (stopped, being deleted, or failed).
            `CONCURRENT_UPDATE`: the cluster changed while this request was being processed. Retry the request; if it repeats, read the cluster first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
            `PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/kubernetes-clusters/{cluster_id}/kubeconfig:
    post:
      tags:
        - Kubernetes
      summary: Download a kubeconfig
      description: |-
        The cluster's admin kubeconfig, for `kubectl`. It holds a long-lived cluster-admin credential: store it like a password. It is fetched fresh on each call and is not kept by the platform. Available while the cluster is running. It is a `POST` that needs a `write` key so that a read-only key can never fetch a cluster-admin credential; the response is `Cache-Control: no-store`.

        **Scope:** `write`.
      operationId: getKubernetesKubeconfig
      security:
        - apiKey: []
      x-scope: write
      x-order: 124
      parameters:
        - name: cluster_id
          in: path
          required: true
          description: "The cluster's id (`k8s_…`)."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        200:
          description: The kubeconfig.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Kubeconfig"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `KUBERNETES_CLUSTER_NOT_FOUND`: no cluster with this id in your organization. Check the id.
            `RESOURCE_NOT_FOUND`: the id is not a cluster id (`k8s_…`).
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `KUBECONFIG_UNAVAILABLE`: the cluster is not running yet. Wait until it is, then try again.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/kubernetes-clusters/{cluster_id}/nodes:
    get:
      tags:
        - Kubernetes
      summary: List a cluster's nodes
      description: |-
        The cluster's nodes as Kubernetes reports them right now, read from the cluster's own API server. `vcpu` and `memory_mb` are each node's capacity; `disk_gb` is the cluster's node plan.

        Errors: `404 KUBERNETES_CLUSTER_NOT_FOUND`; `409 KUBERNETES_CLUSTER_NOT_RUNNING` (start the cluster first); `503 KUBERNETES_API_UNREACHABLE` (the API server did not answer: it may still be starting, or its endpoint may be private; retry).
      operationId: listKubernetesClusterNodes
      security:
        - apiKey: []
      x-scope: read
      parameters:
        - name: cluster_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: The cluster's nodes.
          content:
            application/json:
              schema:
                type: object
                required: [nodes]
                properties:
                  nodes:
                    type: array
                    items:
                      type: object
                      required: [id, name, role, status, ip_address, vcpu, memory_mb, disk_gb, kubelet_version, created_at]
                      properties:
                        id: {type: string, description: "The node's name, which is its id inside Kubernetes."}
                        name: {type: string}
                        role: {type: string, enum: [control, worker]}
                        status: {type: string, enum: [Ready, NotReady]}
                        ip_address: {type: string, description: "The node's internal address."}
                        vcpu: {type: integer}
                        memory_mb: {type: integer}
                        disk_gb: {type: integer}
                        kubelet_version: {type: string, examples: ["v1.31.2+k3s1"]}
                        created_at:
                          type:
                            - string
                            - "null"
                          format: date-time
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "`KUBERNETES_CLUSTER_NOT_FOUND`."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        "409":
          description: "`KUBERNETES_CLUSTER_NOT_RUNNING`."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        "503":
          description: "`KUBERNETES_API_UNREACHABLE`: retry."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/kubernetes-clusters/{cluster_id}/workloads:
    get:
      tags:
        - Kubernetes
      summary: Get a cluster's services and pods
      description: |-
        Every Service and pod in the cluster, in every namespace, read live from its API server. A cluster you have not deployed anything to shows only what Kubernetes itself runs. A service you exposed (see *Expose a Kubernetes service*) has `public_port` and `external_url`, the address it is reached on from the internet.

        Errors: `404 KUBERNETES_CLUSTER_NOT_FOUND`; `409 KUBERNETES_CLUSTER_NOT_RUNNING`; `503 KUBERNETES_API_UNREACHABLE` (retry).
      operationId: getKubernetesWorkloads
      security:
        - apiKey: []
      x-scope: read
      parameters:
        - name: cluster_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: The cluster's services and pods.
          content:
            application/json:
              schema:
                type: object
                required: [cluster_id, public_ip, api_port, services, pods]
                properties:
                  cluster_id: {type: string}
                  public_ip:
                    type: ["string", "null"]
                    description: The public address exposed services and the API server are reached on; null until routing has been synced.
                  api_port:
                    type: ["integer", "null"]
                    description: The cluster's API server port on `public_ip`; null for a private endpoint or before routing is synced.
                  services:
                    type: array
                    items:
                      type: object
                      required: [name, namespace, type, cluster_ip, external_ip, ports, public_port, external_url, created_at]
                      properties:
                        name: {type: string}
                        namespace: {type: string}
                        type: {type: string, examples: ["ClusterIP", "NodePort", "LoadBalancer"]}
                        cluster_ip: {type: string}
                        external_ip: {type: ["string", "null"]}
                        ports:
                          type: array
                          items:
                            type: object
                            required: [name, protocol, port]
                            properties:
                              name: {type: string}
                              protocol: {type: string}
                              port: {type: integer}
                              target_port: {type: integer, description: "Left out when the target is a named container port."}
                              node_port: {type: integer}
                        public_port: {type: ["integer", "null"], description: "The public port routed to this service; null when it is not exposed."}
                        external_url: {type: ["string", "null"], examples: ["http://203.0.113.10:8088"]}
                        created_at: {type: ["string", "null"], format: date-time}
                  pods:
                    type: array
                    items:
                      type: object
                      required: [name, namespace, status, ready, restarts, node_name, pod_ip, age, created_at]
                      properties:
                        name: {type: string}
                        namespace: {type: string}
                        status: {type: string, examples: ["Running", "Pending"]}
                        ready: {type: string, description: "Ready containers over all containers.", examples: ["1/1"]}
                        restarts: {type: integer}
                        node_name: {type: string}
                        pod_ip: {type: string}
                        age: {type: string, description: "As kubectl's AGE column.", examples: ["3h"]}
                        created_at: {type: ["string", "null"], format: date-time}
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "`KUBERNETES_CLUSTER_NOT_FOUND`."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        "409":
          description: "`KUBERNETES_CLUSTER_NOT_RUNNING`."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        "503":
          description: "`KUBERNETES_API_UNREACHABLE`: retry."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/kubernetes-clusters/{cluster_id}/apply-manifest:
    post:
      tags:
        - Kubernetes
      summary: Apply a manifest to a cluster
      description: |-
        Applies a YAML manifest (one or more documents, up to 512 KB and 100 objects) to the cluster with server-side apply, as `kubectl apply --server-side --force-conflicts` would, in order. Namespaced objects without a namespace go in `namespace` (default `default`). Applying the same manifest again changes nothing, so a retry is safe. If the cluster refuses an object, the objects before it stay applied and the error names the one it refused.

        Errors: `422 VALIDATION_FAILED` with `errors[].field = manifest` and code `INVALID_KUBERNETES_MANIFEST`, whose `message` is the cluster's own reason (for example a missing field); `404 KUBERNETES_CLUSTER_NOT_FOUND`; `409 KUBERNETES_CLUSTER_NOT_RUNNING`; `503 KUBERNETES_API_UNREACHABLE` (retry).
      operationId: applyKubernetesManifest
      security:
        - apiKey: []
      x-scope: write
      parameters:
        - name: cluster_id
          in: path
          required: true
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [manifest]
              properties:
                manifest: {type: string, maxLength: 524288}
                namespace: {type: string, description: "Where namespaced objects without one go; default `default`."}
      responses:
        "200":
          description: What was applied.
          content:
            application/json:
              schema:
                type: object
                required: [applied, cluster_id, output, resources]
                properties:
                  applied: {type: boolean}
                  cluster_id: {type: string}
                  output: {type: string, description: "One line per object, as kubectl prints it.", examples: ["deployment.apps/web created"]}
                  resources:
                    type: array
                    items:
                      type: object
                      required: [kind, name, namespace, status]
                      properties:
                        kind: {type: string}
                        name: {type: string}
                        namespace: {type: string, description: "Empty for a cluster-wide object."}
                        status: {type: string, enum: [created, configured, unchanged]}
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "`KUBERNETES_CLUSTER_NOT_FOUND`."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        "409":
          description: "`KUBERNETES_CLUSTER_NOT_RUNNING`."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        "422":
          description: "`VALIDATION_FAILED`: `INVALID_KUBERNETES_MANIFEST` on `manifest`, or `REQUIRED`."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        "503":
          description: "`KUBERNETES_API_UNREACHABLE`: retry."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/kubernetes-clusters/{cluster_id}/live-manifests:
    get:
      tags:
        - Kubernetes
      summary: Export a cluster's workloads as YAML
      description: |-
        Your objects as they run in the cluster now, as one multi-document YAML stream you can edit and apply back: Namespaces, ConfigMaps, Services, Deployments, StatefulSets, DaemonSets, CronJobs and Ingresses outside the `kube-*` namespaces. Fields the cluster fills in (status, resource versions, cluster IPs) are left out. Objects a controller made (a Deployment's ReplicaSets and pods) are left out, since their owner is exported. Secrets are never exported.

        Errors: `404 KUBERNETES_CLUSTER_NOT_FOUND`; `409 KUBERNETES_CLUSTER_NOT_RUNNING`; `503 KUBERNETES_API_UNREACHABLE` (retry).
      operationId: getKubernetesLiveManifests
      security:
        - apiKey: []
      x-scope: read
      parameters:
        - name: cluster_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: The exported objects.
          content:
            application/json:
              schema:
                type: object
                required: [cluster_id, manifest, resource_count, retrieved_at]
                properties:
                  cluster_id: {type: string}
                  manifest: {type: string, description: "Empty when you have deployed nothing."}
                  resource_count: {type: integer}
                  retrieved_at: {type: string, format: date-time}
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "`KUBERNETES_CLUSTER_NOT_FOUND`."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        "409":
          description: "`KUBERNETES_CLUSTER_NOT_RUNNING`."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        "503":
          description: "`KUBERNETES_API_UNREACHABLE`: retry."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/kubernetes-clusters/{cluster_id}/expose-service:
    post:
      tags:
        - Kubernetes
      summary: Expose a Kubernetes service
      description: |-
        Routes `public_port` on the cluster's public address to a Service. The Service's port becomes a NodePort (`node_port`, or the one it has, or one the cluster picks), the Service records the public port in its `pantechdynamics.com/public-port` annotation, and routing is synced. The cluster is the record of what is exposed: removing the annotation with kubectl and syncing routing takes the route away. Your subnet's firewall rules must allow the public port.

        Public routing needs a cluster in a VPC subnet: it uses the VPC's public address. Port 22 and the cluster's API port are reserved.

        Errors: `404 KUBERNETES_CLUSTER_NOT_FOUND`; `404 KUBERNETES_SERVICE_NOT_FOUND` (apply the Service first); `409 PORT_IN_USE` (another service, cluster or resource uses that public port; choose another); `409 KUBERNETES_ROUTING_UNAVAILABLE` (a cluster with no VPC address); `409 KUBERNETES_CLUSTER_NOT_RUNNING`; `422 VALIDATION_FAILED` (`service_name`, `public_port` or `node_port`; or `INVALID_KUBERNETES_MANIFEST` when the cluster refused the change); `503 KUBERNETES_API_UNREACHABLE` (retry).
      operationId: exposeKubernetesService
      security:
        - apiKey: []
      x-scope: write
      parameters:
        - name: cluster_id
          in: path
          required: true
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [service_name, public_port]
              properties:
                service_name: {type: string}
                namespace: {type: string, description: "Default `default`."}
                public_port: {type: integer, minimum: 1, maximum: 65535}
                node_port: {type: integer, minimum: 30000, maximum: 32767, description: "Leave out to keep the Service's own NodePort, or let the cluster choose one."}
                protocol: {type: string, enum: [TCP, UDP], default: "TCP"}
      responses:
        "200":
          description: The service is routed.
          content:
            application/json:
              schema:
                type: object
                required: [success, cluster_id, service_name, namespace, public_ip, public_port, node_port, protocol, external_url, status]
                properties:
                  success: {type: boolean}
                  cluster_id: {type: string}
                  service_name: {type: string}
                  namespace: {type: string}
                  public_ip: {type: string}
                  public_port: {type: integer}
                  node_port: {type: integer}
                  protocol: {type: string}
                  external_url: {type: string, examples: ["http://203.0.113.10:8088"]}
                  status: {type: string, enum: [active]}
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "`KUBERNETES_CLUSTER_NOT_FOUND` or `KUBERNETES_SERVICE_NOT_FOUND`."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        "409":
          description: "`PORT_IN_USE`, `KUBERNETES_ROUTING_UNAVAILABLE` or `KUBERNETES_CLUSTER_NOT_RUNNING`."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        "422":
          description: "`VALIDATION_FAILED`."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        "503":
          description: "`KUBERNETES_API_UNREACHABLE`: retry."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/kubernetes-clusters/{cluster_id}/sync-routing:
    post:
      tags:
        - Kubernetes
      summary: Sync a cluster's public routing
      description: |-
        Reconciles the port forwards on the cluster's public address with what the cluster exposes now: its API server (unless its endpoint is private), on the cluster's own API port, and every Service carrying the `pantechdynamics.com/public-port` annotation. Routes to services no longer exposed are removed. Only the cluster's own routes are ever changed: other clusters and your own port forwards on the same address are left alone.

        Every cluster on a shared address gets its own API port: the first 6443, the next 6444, and so on. A new cluster's API port is routed automatically once it is running; call this after exposing or unexposing services with kubectl. If the API server does not answer, only its own route is ensured and no service route is removed.

        Errors: `404 KUBERNETES_CLUSTER_NOT_FOUND`; `409 PORT_IN_USE`; `409 KUBERNETES_ROUTING_UNAVAILABLE` (a cluster with no VPC address); `409 KUBERNETES_CLUSTER_NOT_RUNNING`.
      operationId: syncKubernetesRouting
      security:
        - apiKey: []
      x-scope: write
      parameters:
        - name: cluster_id
          in: path
          required: true
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          description: The routes now in place.
          content:
            application/json:
              schema:
                type: object
                required: [success, cluster_id, public_ip, api_port, synced_ports, message]
                properties:
                  success: {type: boolean}
                  cluster_id: {type: string}
                  public_ip: {type: string}
                  api_port: {type: ["integer", "null"], description: "Null for a private endpoint."}
                  synced_ports:
                    type: array
                    items:
                      type: object
                      required: [purpose, public_port, private_port, protocol, target, status]
                      properties:
                        purpose: {type: string, enum: [kubernetes-api, service]}
                        public_port: {type: integer}
                        private_port: {type: integer}
                        protocol: {type: string}
                        target: {type: string, description: "`control node`, or the service as namespace/name."}
                        status: {type: string, enum: [active]}
                  message: {type: string}
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "`KUBERNETES_CLUSTER_NOT_FOUND`."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        "409":
          description: "`PORT_IN_USE`, `KUBERNETES_ROUTING_UNAVAILABLE` or `KUBERNETES_CLUSTER_NOT_RUNNING`."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/kubernetes-clusters/{cluster_id}/node-pools:
    get:
      tags:
        - Kubernetes
      summary: List node pools
      description: "Lists the cluster's node pools."
      operationId: listKubernetesNodePools
      security:
        - apiKey: []
      x-scope: read
      parameters:
        - name: cluster_id
          in: path
          required: true
          description: "The cluster's id (`k8s_…`)."
          schema:
            type: string
      responses:
        200:
          description: The node pools.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KubernetesNodePoolList"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: "Cluster not found."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: "Service unavailable."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
    post:
      tags:
        - Kubernetes
      summary: Create a node pool
      description: "Adds a node pool: workers of their own plan, joined with the pool's labels, the label `pantech.io/node-pool=<name>` and the pool's taints, so workloads choose it with a nodeSelector and tolerations. Each worker is billed as a VM of the pool's plan once it has joined (`observed_workers`). With autoscaling on, the platform adds a worker when a pod the pool could run is waiting for room, and removes the newest one after 10 idle minutes. A pool's name is unique in its cluster: sending the same pool again is answered as a replay, another pool under a taken name is `409 NODE_POOL_NAME_TAKEN`. In a zone with one fixed node size (af-abj-2), the plan must be that size (`422 KUBERNETES_PLAN_NOT_IN_ZONE`)."
      operationId: createKubernetesNodePool
      security:
        - apiKey: []
      x-scope: write
      parameters:
        - name: cluster_id
          in: path
          required: true
          description: "The cluster's id (`k8s_…`)."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateKubernetesNodePoolRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: "Bad request."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: "Cluster not found."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: "Cluster busy or duplicate node pool name."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: "Validation error."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: "Service unavailable."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/kubernetes-clusters/{cluster_id}/node-pools/{pool_id}:
    patch:
      tags:
        - Kubernetes
      summary: Scale a node pool
      description: "Changes a node pool's worker count and autoscaling. The body is the pool's whole size."
      operationId: scaleKubernetesNodePool
      security:
        - apiKey: []
      x-scope: write
      parameters:
        - name: cluster_id
          in: path
          required: true
          description: "The cluster's id (`k8s_…`)."
          schema:
            type: string
        - name: pool_id
          in: path
          required: true
          description: "The node pool's id (`k8spool_…`)."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ScaleKubernetesNodePoolRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: "Bad request."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: "Cluster or node pool not found."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: "Cluster busy."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: "Validation error."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: "Service unavailable."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
    delete:
      tags:
        - Kubernetes
      summary: Delete a node pool
      description: "Removes a node pool: its workers leave the cluster (their pods move to other nodes that can run them) and are destroyed, then the pool goes. **Scope:** `write`. Destructive: an AI app's key also needs `delete`."
      operationId: deleteKubernetesNodePool
      security:
        - apiKey: []
      x-scope: write
      x-destructive: true
      parameters:
        - name: cluster_id
          in: path
          required: true
          description: "The cluster's id (`k8s_…`)."
          schema:
            type: string
        - name: pool_id
          in: path
          required: true
          description: "The node pool's id (`k8spool_…`)."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: "Bad request."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: "Cluster or node pool not found."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: "Cluster busy."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: "Service unavailable."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/kubernetes-clusters/{cluster_id}/addons:
    get:
      tags:
        - Kubernetes
      summary: List cluster add-ons
      description: "Lists the cluster's managed add-ons and what the cluster runs of each: `status` is `active`, `installing`, `removing`, `disabled` or `unavailable`. `metrics-server` gives `kubectl top` and the Horizontal Pod Autoscaler; `csi-driver` is persistent volumes on the node's own disk (StorageClass `local-path`; the data stays on that node); `ingress-controller` is ingress-nginx, the default IngressClass, served on 80 and 443 of the cluster's public address in a VPC zone. A cluster starts with metrics-server and csi-driver on."
      operationId: listKubernetesAddons
      security:
        - apiKey: []
      x-scope: read
      parameters:
        - name: cluster_id
          in: path
          required: true
          description: "The cluster's id (`k8s_…`)."
          schema:
            type: string
      responses:
        200:
          description: The cluster add-ons.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KubernetesClusterAddonList"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: "Cluster not found."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: "Service unavailable."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/kubernetes-clusters/{cluster_id}/addons/{addon_name}/toggle:
    post:
      tags:
        - Kubernetes
      summary: Toggle a cluster add-on
      description: "Turns a managed add-on on or off: `metrics-server`, `csi-driver` or `ingress-controller`. The platform applies it within moments and keeps it that way (a change made with kubectl is put back); the add-on's `status` shows when it has taken effect. Asking for the state it is already in changes nothing (empty `operation_id`)."
      operationId: toggleKubernetesAddon
      security:
        - apiKey: []
      x-scope: write
      parameters:
        - name: cluster_id
          in: path
          required: true
          description: "The cluster's id (`k8s_…`)."
          schema:
            type: string
        - name: addon_name
          in: path
          required: true
          description: "The add-on: `metrics-server`, `csi-driver` or `ingress-controller`. Any other name is `422 INVALID_CLUSTER_ADDON`."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ToggleKubernetesAddonRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: "Bad request."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: "Cluster or add-on not found."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: "Cluster busy."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: "Service unavailable."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/kubernetes-clusters/{cluster_id}/endpoint-access:
    post:
      tags:
        - Kubernetes
      summary: Configure API server endpoint access
      description: Updates the cluster API endpoint access mode (`public`, `private`, or `dual`) and authorized CIDRs.
      operationId: configureKubernetesEndpointAccess
      security:
        - apiKey: []
      x-scope: write
      parameters:
        - name: cluster_id
          in: path
          required: true
          description: "The cluster's id (`k8s_…`)."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ConfigureKubernetesEndpointAccessRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: "Bad request."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: "Cluster not found."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: "Cluster busy."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: "Validation error."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: "Service unavailable."
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/security-groups:
    get:
      tags:
        - Security groups
      summary: List security groups
      description: |-
        Your security groups, newest first, with their rules. Filter by name with `q`, or by state.

        **Scope:** `read`.
      operationId: listSecurityGroups
      security:
        - apiKey: []
      x-scope: read
      x-order: 55
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Q"
        - $ref: "#/components/parameters/DesiredState"
        - $ref: "#/components/parameters/ObservedState"
      responses:
        200:
          description: A page of security groups.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SecurityGroupList"
        400:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    post:
      tags:
        - Security groups
      summary: Create a security group
      description: |-
        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`.
      operationId: createSecurityGroup
      security:
        - apiKey: []
      x-scope: write
      x-order: 56
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSecurityGroupRequest"
            examples:
              web:
                summary: "HTTPS from anywhere, SSH from one office address"
                value: {"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"}]}
              private_network_safe:
                summary: "Compatible with a private database network interface: no rule overlaps 10.250.0.0/20"
                value: {"name": "app", "rules": [{"direction": "ingress", "protocol": "tcp", "port_range": "443", "cidr": "203.0.113.0/24"}, {"direction": "ingress", "protocol": "icmp", "cidr": "198.51.100.0/24"}]}
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
              examples:
                accepted:
                  summary: "Accepted; the new group's id is resource_id"
                  value: {"operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w", "resource_id": "sg_06gk3x9tq7m2v8r4y1c5d0zp8w", "status": "submitting"}
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        409:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/security-groups/{security_group_id}:
    get:
      tags:
        - Security groups
      summary: Get a security group
      description: |-
        One security group and its rules.

        **Scope:** `read`.
      operationId: getSecurityGroup
      security:
        - apiKey: []
      x-scope: read
      x-order: 57
      parameters:
        - name: security_group_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
      responses:
        200:
          description: The security group.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SecurityGroup"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    delete:
      tags:
        - Security groups
      summary: Delete a security group
      description: |-
        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`.
      operationId: deleteSecurityGroup
      security:
        - apiKey: []
      x-scope: write
      x-destructive: true
      x-order: 59
      parameters:
        - name: security_group_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/security-groups/{security_group_id}/rules:
    put:
      tags:
        - Security groups
      summary: "Replace a security group's rules"
      description: |-
        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`.
      operationId: replaceSecurityGroupRules
      security:
        - apiKey: []
      x-scope: write
      x-order: 58
      parameters:
        - name: security_group_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReplaceSecurityGroupRulesRequest"
            examples:
              narrowed:
                summary: "Replace 0.0.0.0/0 with the ranges you need"
                value: {"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"}]}
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
              examples:
                accepted:
                  summary: "Accepted"
                  value: {"operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w", "resource_id": "sg_06gk3x9tq7m2v8r4y1c5d0zp8w", "status": "submitting"}
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/snapshots:
    get:
      tags:
        - Snapshots
      summary: List snapshots
      description: |-
        Every snapshot in your organization: of instances' root disks, of volumes, and of databases' data disks. `instance_id`, `volume_id` or `database_id` says which.

        **Scope:** `read`.
      operationId: listSnapshots
      security:
        - apiKey: []
      x-scope: read
      x-order: 72
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Q"
        - $ref: "#/components/parameters/DesiredState"
        - $ref: "#/components/parameters/ObservedState"
      responses:
        200:
          description: A page of snapshots.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SnapshotList"
        400:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/snapshots/quote:
    get:
      tags:
        - Snapshots
      summary: Price a snapshot
      description: |-
        What a snapshot of `size_gb` GB costs per month, in your organization's currency. A snapshot is billed on its physical size.

        **Scope:** `read`.
      operationId: quoteSnapshot
      security:
        - apiKey: []
      x-scope: read
      x-order: 73
      parameters:
        - name: size_gb
          in: query
          required: true
          schema:
            type: integer
            minimum: 1
      responses:
        200:
          description: The price.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OptionalQuote"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `NOT_FOUND`: cost quotes are not available on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `size_gb` `INVALID_SIZE`: send a whole number of GB, at least 1.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/snapshots/{snapshot_id}:
    get:
      tags:
        - Snapshots
      summary: Get a snapshot
      description: |-
        One snapshot.

        **Scope:** `read`.
      operationId: getSnapshot
      security:
        - apiKey: []
      x-scope: read
      x-order: 74
      parameters:
        - name: snapshot_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
      responses:
        200:
          description: The snapshot.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Snapshot"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no snapshot with this id in your organization, or the id is not a snapshot id (`snap_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    delete:
      tags:
        - Snapshots
      summary: Delete a snapshot
      description: |-
        Deletes a snapshot of an instance, volume or database. 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`.
      operationId: deleteSnapshot
      security:
        - apiKey: []
      x-scope: write
      x-destructive: true
      x-order: 75
      parameters:
        - name: snapshot_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no snapshot with this id in your organization, or the id is not a snapshot id (`snap_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/snapshots/{snapshot_id}/restore:
    post:
      tags:
        - Snapshots
      summary: Restore a snapshot to a new volume
      description: |-
        Creates a new volume from an instance or volume snapshot, of `disk_offering_slug` and at least the snapshot's size. A database snapshot cannot be restored yet. To roll an instance's root disk back to its snapshot instead, use *Restore an instance from a snapshot*.

        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 volume'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`.
      operationId: restoreSnapshot
      security:
        - apiKey: []
      x-scope: write
      x-order: 76
      parameters:
        - name: snapshot_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RestoreSnapshotRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no snapshot with this id in your organization, or the id is not a snapshot id (`snap_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `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 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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `name` or `disk_offering_slug` `REQUIRED`: send both.
            `disk_offering_slug` `DISK_OFFERING_NOT_FOUND`: no active disk offering has this slug. Pick one from `GET /public/v1/disk-offerings`.
            `size_gb` `SIZE_REQUIRED`: a custom-size offering needs `size_gb`, at least the snapshot's size.
            `UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/snapshots/{snapshot_id}/usage:
    get:
      tags:
        - Usage
      summary: "Get a snapshot's usage"
      description: |-
        Measured storage usage for this snapshot in the current billing period.

        **Scope:** `read`.
      operationId: getSnapshotUsage
      security:
        - apiKey: []
      x-scope: read
      x-order: 77
      parameters:
        - name: snapshot_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
      responses:
        200:
          description: Usage.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UsageSummary"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no snapshot with this id in your organization, or the id is not a snapshot id (`snap_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/ssh-keys:
    get:
      tags:
        - SSH keys
      summary: List SSH keys
      description: |-
        The SSH keys you can install on new instances.

        **Scope:** `read`.
      operationId: listSSHKeys
      security:
        - apiKey: []
      x-scope: read
      x-order: 78
      responses:
        200:
          description: SSH keys.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SSHKeyList"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    post:
      tags:
        - SSH keys
      summary: Add or generate an SSH key
      description: |-
        Send `public_key` to register your own OpenSSH public key. Leave it out to have a key pair generated: the private key is returned in this response only and never stored. Returns `201` at once; there is no operation to follow.

        **Idempotency:** a retry with the same `Idempotency-Key` is not replayed yet. If a request may have gone through, list your SSH keys before retrying: a second attempt with the same name returns `409 SSH_KEY_NAME_TAKEN`, and a generated private key is never shown again.

        **Scope:** `write`.
      operationId: createSSHKey
      security:
        - apiKey: []
      x-scope: write
      x-order: 79
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSSHKeyRequest"
      responses:
        201:
          description: "The key. `private_key` is present only for a generated key."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GeneratedSSHKey"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        409:
          description: |-
            `SSH_KEY_NAME_TAKEN`: you already have an SSH key with this name. Choose a different name.
            `SSH_KEY_ALREADY_EXISTS`: this public key is already registered. Use the existing key.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `name` `REQUIRED` or `INVALID_NAME`: send a name of 1 to 255 characters.
            `public_key` `INVALID_SSH_PUBLIC_KEY`: send one OpenSSH public key line, such as `ssh-ed25519 AAAA… comment`.
            `UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/ssh-keys/{ssh_key_id}:
    get:
      tags:
        - SSH keys
      summary: Get an SSH key
      description: |-
        One SSH key.

        **Scope:** `read`.
      operationId: getSSHKey
      security:
        - apiKey: []
      x-scope: read
      x-order: 80
      parameters:
        - name: ssh_key_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
      responses:
        200:
          description: The key.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SSHKey"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no SSH key with this id in your organization, or the id is not a SSH key id (`sshk_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    delete:
      tags:
        - SSH keys
      summary: Delete an SSH key
      description: |-
        Deletes an SSH key. Instances that already have it keep it in their `authorized_keys`; remove it there yourself.

        **Idempotency:** a retry is not replayed. Once the key is deleted, a repeat returns `404`; treat that as done.

        **Scope:** `write`. Destructive: an AI app's key also needs `delete`.
      operationId: deleteSSHKey
      security:
        - apiKey: []
      x-scope: write
      x-destructive: true
      x-order: 81
      parameters:
        - name: ssh_key_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        204:
          description: Deleted.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no SSH key with this id in your organization, or the id is not a SSH key id (`sshk_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/subnets/{subnet_id}:
    get:
      tags:
        - Networks
      summary: Get a subnet
      description: |-
        One subnet, with the number of instances in it.

        **Scope:** `read`.
      operationId: getSubnet
      security:
        - apiKey: []
      x-scope: read
      x-order: 40
      parameters:
        - name: subnet_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
      responses:
        200:
          description: The subnet.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Subnet"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no subnet with this id in your organization, or the id is not a subnet id (`snet_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    delete:
      tags:
        - Networks
      summary: Delete a subnet
      description: |-
        Deletes a subnet. It must hold no instances or databases.

        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`.
      operationId: deleteSubnet
      security:
        - apiKey: []
      x-scope: write
      x-destructive: true
      x-order: 41
      parameters:
        - name: subnet_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no subnet with this id in your organization, or the id is not a subnet id (`snet_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `INVALID_RESOURCE_STATE`: the subnet still has instances or databases, or is already being deleted. Delete them first.
            `SUBNET_HAS_KUBERNETES_CLUSTERS`: Kubernetes clusters still run in this subnet. Delete them first.
            `CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/subnets/{subnet_id}/firewall-rules:
    get:
      tags:
        - Networks
      summary: "List a subnet's firewall rules"
      description: |-
        The subnet's firewall rules, in evaluation order, and the platform's fixed rules around them (`system_rules`), which always apply.

        **Scope:** `read`.
      operationId: listFirewallRules
      security:
        - apiKey: []
      x-scope: read
      x-order: 42
      parameters:
        - name: subnet_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
      responses:
        200:
          description: The rules.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FirewallRuleList"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no subnet with this id in your organization, or the id is not a subnet id (`snet_…`). Check the id.
            `NOT_FOUND`: firewall rules are not offered on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `FIREWALL_RULES_NOT_SUPPORTED`: firewall rules apply to VPC subnets only. Standard instances use security groups.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    post:
      tags:
        - Networks
      summary: Add a firewall rule
      description: |-
        Adds a rule to the subnet's firewall. Rules are evaluated by `number`, lowest first, and the first match decides; traffic no rule allows is dropped. A rule applies to the whole subnet. Defaults: `direction` `ingress`, `action` `allow`, `port_end` equal to `port_start`.

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

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

        **Scope:** `write`.
      operationId: createFirewallRule
      security:
        - apiKey: []
      x-scope: write
      x-order: 43
      parameters:
        - name: subnet_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateFirewallRuleRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no subnet with this id in your organization, or the id is not a subnet id (`snet_…`). Check the id.
            `NOT_FOUND`: firewall rules are not offered on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `FIREWALL_RULES_NOT_SUPPORTED`: firewall rules apply to VPC subnets only. Standard instances use security groups.
            `INVALID_RESOURCE_STATE`: the subnet 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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `protocol` or `cidr` `REQUIRED`: send both.
            `number` `INVALID_RULE_NUMBER`: use 100 to 9999.
            `number` `RULE_NUMBER_TAKEN`: another rule on this subnet has this number. Choose another.
            `port_start` `INVALID_PORTS`: send ports 1 to 65535 for `tcp` or `udp`, and leave them out for `icmp` or `all`.
            `protocol` `INVALID_FIREWALL_RULE`: `direction`, `protocol`, `action` or the ICMP fields are not valid values.
            `UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/subnets/{subnet_id}/firewall-rules/{rule_id}:
    get:
      tags:
        - Networks
      summary: Get a firewall rule
      description: |-
        One firewall rule on the subnet.

        **Scope:** `read`.
      operationId: getFirewallRule
      security:
        - apiKey: []
      x-scope: read
      x-order: 44
      parameters:
        - name: subnet_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - name: rule_id
          in: path
          required: true
          description: "The rule's id (`aclr_...` for a firewall rule, `pfr_...` for a port forwarding rule)."
          schema:
            type: string
      responses:
        200:
          description: The rule.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FirewallRule"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no rule with this id on this subnet, or no such subnet in your organization. Check both ids.
            `NOT_FOUND`: firewall rules are not offered on this platform yet.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `FIREWALL_RULES_NOT_SUPPORTED`: firewall rules apply to VPC subnets only. Standard instances use security groups.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/usage:
    get:
      tags:
        - Usage
      summary: Get usage
      description: |-
        Measured usage for the whole organization in the current billing period.

        **Scope:** `read`.
      operationId: getUsage
      security:
        - apiKey: []
      x-scope: read
      x-order: 84
      responses:
        200:
          description: Usage.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UsageSummary"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/usage/by-resource:
    get:
      tags:
        - Usage
      summary: Get cost by resource
      description: |-
        What each resource cost over `period`: the last `30d` or `7d`, this `month` or `last_month`.

        **Scope:** `read`.
      operationId: getUsageByResource
      security:
        - apiKey: []
      x-scope: read
      x-order: 85
      parameters:
        - name: period
          in: query
          schema:
            type: string
            enum:
              - "30d"
              - "7d"
              - month
              - last_month
            default: "30d"
      responses:
        200:
          description: Cost per resource.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UsageByResource"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `period` `INVALID_PERIOD`: use `30d`, `7d`, `month` or `last_month`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `BILLING_UNAVAILABLE`: billing is not answering right now. Wait a few seconds, then retry.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/volumes:
    get:
      tags:
        - Volumes
      summary: List volumes
      description: |-
        Your volumes, newest first, with what each costs per month. Filter by name with `q`, or by state.

        **Scope:** `read`.
      operationId: listVolumes
      security:
        - apiKey: []
      x-scope: read
      x-order: 60
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Q"
        - $ref: "#/components/parameters/DesiredState"
        - $ref: "#/components/parameters/ObservedState"
      responses:
        200:
          description: A page of volumes.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VolumeList"
        400:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    post:
      tags:
        - Volumes
      summary: Create a volume
      description: |-
        Creates a data disk of `disk_offering_slug` (see `GET /public/v1/disk-offerings`; send `size_gb` for a custom-size offering). Send `instance_id` to attach it at once to a running instance in the same zone, and `mount_point` to have it mounted there.

        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 volume'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`.
      operationId: createVolume
      security:
        - apiKey: []
      x-scope: write
      x-order: 61
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateVolumeRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        409:
          description: |-
            `ACCOUNT_SUSPENDED`: an invoice is overdue, or the organization was suspended for non-payment. Pay the outstanding invoice in the console, then retry.
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `name` or `disk_offering_slug` `REQUIRED`: send both.
            `name` `NAME_TOO_LONG`: at most 255 characters.
            `disk_offering_slug` `DISK_OFFERING_NOT_FOUND`: no active disk offering has this slug. Pick one from `GET /public/v1/disk-offerings`.
            `size_gb` `SIZE_REQUIRED`: a custom-size offering needs `size_gb`.
            `instance_id` `INSTANCE_NOT_FOUND`: not one of your instances.
            `instance_id` `INSTANCE_NOT_RUNNING`: the instance must be running to attach a volume at create. Start it, or attach the volume later.
            `instance_id` `INSTANCE_WRONG_ZONE`: the instance is in another zone than the volume.
            `mount_point` `INVALID_MOUNT_POINT`: send an absolute path such as `/data`.
            `region` `REGION_NOT_AVAILABLE`: pick a region from `GET /public/v1/regions`.
            `encrypted` `ENCRYPTION_NOT_SUPPORTED`: encrypted volumes are not enabled for your organization yet. Create a plain volume, or send `encrypted` as `false`.
            `UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
            `ENCRYPTED_VOLUMES_UNAVAILABLE`: you asked for an encrypted volume but the encrypted volume service is not available right now. Retry later, or create a plain volume.
            `PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/volumes/{volume_id}:
    get:
      tags:
        - Volumes
      summary: Get a volume
      description: |-
        One volume, with where it is attached and what it costs.

        **Scope:** `read`.
      operationId: getVolume
      security:
        - apiKey: []
      x-scope: read
      x-order: 62
      parameters:
        - name: volume_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
      responses:
        200:
          description: The volume.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Volume"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no volume with this id in your organization, or the id is not a volume id (`vol_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    delete:
      tags:
        - Volumes
      summary: Delete a volume
      description: |-
        Deletes a volume and its data. Detach it first. Its snapshots are kept and stay billed until you delete them.

        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`.
      operationId: deleteVolume
      security:
        - apiKey: []
      x-scope: write
      x-destructive: true
      x-order: 63
      parameters:
        - name: volume_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no volume with this id in your organization, or the id is not a volume id (`vol_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `INVALID_RESOURCE_STATE`: the volume is still attached, or is already being deleted. Detach it first.
            `CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/volumes/{volume_id}/attach:
    post:
      tags:
        - Volumes
      summary: Attach a volume
      description: |-
        Attaches a detached volume to a running instance in the same zone. Mount it inside the instance afterwards.

        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`.
      operationId: attachVolume
      security:
        - apiKey: []
      x-scope: write
      x-order: 64
      parameters:
        - name: volume_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AttachVolumeRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no volume with this id in your organization, or the id is not a volume id (`vol_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `INVALID_RESOURCE_STATE`: the volume is already attached or changing, or the instance is not running. Detach it first, or start the instance.
            `CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `instance_id` `REQUIRED` or `INSTANCE_NOT_FOUND`: send one of your instances.
            `instance_id` `INSTANCE_NOT_RUNNING`: the instance must be running.
            `instance_id` `INSTANCE_WRONG_ZONE`: the instance is in another zone than the volume. Volumes attach only within their zone.
            `UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/volumes/{volume_id}/detach:
    post:
      tags:
        - Volumes
      summary: Detach a volume
      description: |-
        Detaches a volume from its instance. Unmount it inside the instance first, or data being written may be lost.

        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`.
      operationId: detachVolume
      security:
        - apiKey: []
      x-scope: write
      x-order: 65
      parameters:
        - name: volume_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no volume with this id in your organization, or the id is not a volume id (`vol_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `INVALID_RESOURCE_STATE`: the volume is not attached, or is changing. Wait for its current operation to finish.
            `CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/volumes/{volume_id}/resize:
    post:
      tags:
        - Volumes
      summary: Grow a volume
      description: |-
        Grows a volume to a bigger offering, or to a bigger `size_gb` of a custom-size offering. Volumes only grow. Extend the file system from inside the instance afterwards. No upfront charge: the larger size is billed hourly.

        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`.
      operationId: resizeVolume
      security:
        - apiKey: []
      x-scope: write
      x-order: 66
      parameters:
        - name: volume_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ResizeVolumeRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no volume with this id in your organization, or the id is not a volume id (`vol_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `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 new size is not larger than the current one, or the volume is changing. Send a larger size, or wait.
            `CONCURRENT_UPDATE`: the resource changed while this request was being processed. Retry the request; if it repeats, read the resource first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `disk_offering_slug` `REQUIRED` or `DISK_OFFERING_NOT_FOUND`: send an active offering's slug.
            `size_gb` `SIZE_REQUIRED`: a custom-size offering needs `size_gb`.
            `UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/volumes/{volume_id}/snapshot-schedule:
    get:
      tags:
        - Snapshots
      summary: "Get a volume's snapshot schedule"
      description: |-
        The volume's automatic snapshot schedule: how often a snapshot of its volume is taken, how many are kept, and when the next one runs.

        **Scope:** `read`.
      operationId: getVolumeSnapshotSchedule
      security:
        - apiKey: []
      x-scope: read
      x-order: 70
      parameters:
        - name: volume_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
      responses:
        200:
          description: The schedule.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SnapshotSchedule"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no volume with this id in your organization, or the id is not a volume id (`vol_…`). Check the id.
            `SNAPSHOT_SCHEDULE_NOT_FOUND`: no schedule has been set for this resource. Set one with the `PUT` on this path.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    put:
      tags:
        - Snapshots
      summary: "Set a volume's snapshot schedule"
      description: |-
        Turns automatic snapshots of the volume on, changes them, or pauses them with `enabled: false`. `frequency` is `daily`, `weekly` or `monthly`; `retention_count` (1 to 168, default 7) successful snapshots are kept and older automatic ones are deleted. Automatic snapshots are billed like manual ones.

        Returns the schedule at once (`200`); no operation to follow. Send an `Idempotency-Key`: a retry with the same key and settings returns the same schedule.

        **Scope:** `write`. Lowering `retention_count` deletes the oldest copies, so an AI app's key also needs `delete` for that (`403 INSUFFICIENT_SCOPE` otherwise).
      operationId: putVolumeSnapshotSchedule
      security:
        - apiKey: []
      x-scope: write
      x-order: 71
      parameters:
        - name: volume_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PutSnapshotScheduleRequest"
      responses:
        200:
          description: The schedule.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SnapshotSchedule"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no volume with this id in your organization, or the id is not a volume id (`vol_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `IDEMPOTENCY_CONFLICT`: this `Idempotency-Key` was already used with different settings. Use a new key for a different request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `frequency` `REQUIRED`: send `daily`, `weekly` or `monthly`.
            `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: `frequency` is not `daily`, `weekly` or `monthly`, or `retention_count` is outside 1 to 168.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/volumes/{volume_id}/snapshots:
    get:
      tags:
        - Snapshots
      summary: "List a volume's snapshots"
      description: |-
        Snapshots of this volume, oldest first.

        **Scope:** `read`.
      operationId: listVolumeSnapshots
      security:
        - apiKey: []
      x-scope: read
      x-order: 68
      parameters:
        - name: volume_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Q"
        - $ref: "#/components/parameters/DesiredState"
        - $ref: "#/components/parameters/ObservedState"
      responses:
        200:
          description: A page of snapshots.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SnapshotList"
        400:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no volume with this id in your organization, or the id is not a volume id (`vol_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
    post:
      tags:
        - Snapshots
      summary: Snapshot a volume
      description: |-
        Takes a snapshot of the volume, named `name`. A snapshot of an attached volume is crash-consistent. Billed on its physical size, hourly, until deleted.

        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`.
      operationId: createVolumeSnapshot
      security:
        - apiKey: []
      x-scope: write
      x-order: 69
      parameters:
        - name: volume_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSnapshotRequest"
      responses:
        202:
          description: "Accepted. Follow `operation_id` until it finishes."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Accepted"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no volume with this id in your organization, or the id is not a volume id (`vol_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `SNAPSHOT_NAME_TAKEN`: this volume already has a snapshot with that name. Choose a different name.
            `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 volume 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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `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`.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/volumes/{volume_id}/usage:
    get:
      tags:
        - Usage
      summary: "Get a volume's usage"
      description: |-
        Measured storage usage for this volume in the current billing period.

        **Scope:** `read`.
      operationId: getVolumeUsage
      security:
        - apiKey: []
      x-scope: read
      x-order: 67
      parameters:
        - name: volume_id
          in: path
          required: true
          description: "The resource's id."
          schema:
            type: string
      responses:
        200:
          description: Usage.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UsageSummary"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `RESOURCE_NOT_FOUND`: no volume with this id in your organization, or the id is not a volume id (`vol_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          $ref: "#/components/responses/QuotaUnavailable"
  /public/v1/webhook-event-types:
    get:
      tags:
        - Webhooks
      summary: List webhook event types
      description: |-
        Every event type a webhook can subscribe to, with what it means. Subscribe to an exact type, to every type of one resource with `<resource>.*` (for example `instance.*`), or to everything with `*`.

        Infrastructure events are named `<resource>.<past tense>` when a change finished (`instance.created`, `volume.attached`) and `<resource>.<verb>_failed` when it failed (`instance.create_failed`).

        **Scope:** `read`.
      operationId: listWebhookEventTypes
      security:
        - apiKey: []
      x-scope: read
      x-order: 125
      responses:
        200:
          description: Event types.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookEventTypeList"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/webhooks:
    get:
      tags:
        - Webhooks
      summary: List webhooks
      description: |-
        Your organization's webhooks: the URLs Pantech sends events to. The signing secret is never part of a webhook; it is shown once, when the webhook is created and when its secret is rotated.

        **Scope:** `read`.
      operationId: listWebhooks
      security:
        - apiKey: []
      x-scope: read
      x-order: 126
      responses:
        200:
          description: Webhooks.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookList"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
    post:
      tags:
        - Webhooks
      summary: Create a webhook
      description: |-
        Adds a URL Pantech sends events to. Every delivery is a JSON `POST` signed with the webhook's secret, which is in this response only: store it now.

        **Verifying a delivery.** Each request carries `Pantech-Signature: t=<unix seconds>,v1=<hex>`. Compute HMAC-SHA256 of `<t>.<raw body>` with the secret and compare it, in constant time, with any `v1` value; reject a `t` more than five minutes old. During the 24 hours after a rotation there are two `v1` values, one per secret. `Pantech-Event-Id`, `Pantech-Event-Type` and `Pantech-Delivery-Id` name what was sent.

        **Delivery.** Any `2xx` within 10 seconds is success. Anything else, including a redirect, is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours and 10 hours, then marked failed. A webhook that has failed for three days without a single success is disabled. Events can arrive more than once and out of order: deduplicate on the event `id`.

        At most 10 webhooks per organization. Owners and admins only.

        **Idempotency:** a retry with the same `Idempotency-Key` returns the webhook the first request made, with `secret: null`: the secret is never shown again. The same key with a different request returns `409 IDEMPOTENCY_CONFLICT`.

        **Scope:** `write`.
      operationId: createWebhook
      security:
        - apiKey: []
      x-scope: write
      x-order: 127
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateWebhookRequest"
      responses:
        201:
          description: "The webhook, with its `secret`, or `secret: null` on a replay."
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookWithSecret"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          description: |-
            `INSUFFICIENT_SCOPE`: the API key lacks the `write` scope. Use a key with that scope.
            `FORBIDDEN`: the member who created the key is not an owner or admin. Use a key created by one.
            `AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server. An owner can turn it on in the console under Organization › AI apps.
            `WEBHOOK_LIMIT_REACHED`: your organization already has 10 webhooks. Delete one first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `IDEMPOTENCY_CONFLICT`: this `Idempotency-Key` was already used for a different webhook. Use a new key.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `url` `REQUIRED` or `INVALID_WEBHOOK_URL`: send an `https` URL on the public internet, with no user name or password, of at most 2,048 characters.
            `events` `INVALID_WEBHOOK_EVENTS`: send 1 to 100 entries, each an event type from `GET /webhook-event-types`, `<resource>.*`, or `*`.
            `description` `INVALID_DESCRIPTION`: send at most 255 characters.
            `UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/webhooks/{webhook_id}:
    get:
      tags:
        - Webhooks
      summary: Get a webhook
      description: |-
        One webhook. `failing_since` is when its deliveries started failing without a success since; null while they succeed.

        **Scope:** `read`.
      operationId: getWebhook
      security:
        - apiKey: []
      x-scope: read
      x-order: 128
      parameters:
        - name: webhook_id
          in: path
          required: true
          description: "The webhook's id (`whk_…`)."
          schema:
            type: string
      responses:
        200:
          description: The webhook.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Webhook"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `WEBHOOK_NOT_FOUND`: no webhook with this id in your organization. List your webhooks to find it.
            `RESOURCE_NOT_FOUND`: the id is not a webhook id (`whk_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
    patch:
      tags:
        - Webhooks
      summary: Change a webhook
      description: |-
        Changes the URL, description, events or `enabled`; fields you leave out keep their value. Enabling a disabled webhook clears its failure history. Deliveries still waiting for a disabled webhook are cancelled when they come due.

        **Idempotency:** the same request applied twice leaves the webhook the same.

        **Scope:** `write`.
      operationId: updateWebhook
      security:
        - apiKey: []
      x-scope: write
      x-order: 129
      parameters:
        - name: webhook_id
          in: path
          required: true
          description: "The webhook's id (`whk_…`)."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateWebhookRequest"
      responses:
        200:
          description: The changed webhook.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Webhook"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `WEBHOOK_NOT_FOUND`: no webhook with this id in your organization. List your webhooks to find it.
            `RESOURCE_NOT_FOUND`: the id is not a webhook id (`whk_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `CONFLICT`: the webhook was changed at the same time. Get it again and retry.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        413:
          description: |-
            `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        422:
          description: |-
            `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
            `url` `REQUIRED` or `INVALID_WEBHOOK_URL`: send an `https` URL on the public internet, with no user name or password, of at most 2,048 characters.
            `events` `INVALID_WEBHOOK_EVENTS`: send 1 to 100 entries, each an event type from `GET /webhook-event-types`, `<resource>.*`, or `*`.
            `description` `INVALID_DESCRIPTION`: send at most 255 characters.
            `UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
    delete:
      tags:
        - Webhooks
      summary: Delete a webhook
      description: |-
        Deletes a webhook and its delivery history. Nothing more is sent to it.

        **Idempotency:** a retry is not replayed. Once the webhook is deleted, a repeat returns `404`; treat that as done.

        **Scope:** `write`. Destructive: an AI app's key also needs `delete`.
      operationId: deleteWebhook
      security:
        - apiKey: []
      x-scope: write
      x-destructive: true
      x-order: 130
      parameters:
        - name: webhook_id
          in: path
          required: true
          description: "The webhook's id (`whk_…`)."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        204:
          description: Deleted.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `WEBHOOK_NOT_FOUND`: no webhook with this id in your organization. List your webhooks to find it.
            `RESOURCE_NOT_FOUND`: the id is not a webhook id (`whk_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/webhooks/{webhook_id}/test:
    post:
      tags:
        - Webhooks
      summary: Send a test event
      description: |-
        Queues a `webhook.test` event to this webhook, whatever it subscribes to, and returns the delivery. It is sent within a few seconds and retried like any other.

        **Idempotency:** a retry is not replayed: each request sends another test event.

        **Scope:** `write`.
      operationId: testWebhook
      security:
        - apiKey: []
      x-scope: write
      x-order: 131
      parameters:
        - name: webhook_id
          in: path
          required: true
          description: "The webhook's id (`whk_…`)."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: The queued delivery.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookDeliveryDetail"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `WEBHOOK_NOT_FOUND`: no webhook with this id in your organization. List your webhooks to find it.
            `RESOURCE_NOT_FOUND`: the id is not a webhook id (`whk_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `WEBHOOK_DISABLED`: the webhook is disabled. Enable it first.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/webhooks/{webhook_id}/rotate-secret:
    post:
      tags:
        - Webhooks
      summary: Rotate a webhook's secret
      description: |-
        Replaces the signing secret, which is in this response only. For 24 hours every delivery is signed with both the new and the old secret, so you can update your receiver without dropping events.

        **Idempotency:** a retry is not replayed: each request rotates again, and only the latest secret and the one before it sign.

        **Scope:** `write`.
      operationId: rotateWebhookSecret
      security:
        - apiKey: []
      x-scope: write
      x-order: 132
      parameters:
        - name: webhook_id
          in: path
          required: true
          description: "The webhook's id (`whk_…`)."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        200:
          description: The webhook with its new `secret`.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookWithSecret"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `WEBHOOK_NOT_FOUND`: no webhook with this id in your organization. List your webhooks to find it.
            `RESOURCE_NOT_FOUND`: the id is not a webhook id (`whk_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `CONFLICT`: the webhook was changed at the same time. Retry.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/webhooks/{webhook_id}/deliveries:
    get:
      tags:
        - Webhooks
      summary: List a webhook's deliveries
      description: |-
        Every event sent or waiting to be sent to this webhook, newest first, kept for 30 days: its status, attempts, and how the last attempt went.

        **Scope:** `read`.
      operationId: listWebhookDeliveries
      security:
        - apiKey: []
      x-scope: read
      x-order: 133
      parameters:
        - name: webhook_id
          in: path
          required: true
          description: "The webhook's id (`whk_…`)."
          schema:
            type: string
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
      responses:
        200:
          description: Deliveries.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookDeliveryList"
        400:
          description: |-
            `INVALID_PAGE`: `limit` is not an integer from 1 to 100.
            `INVALID_CURSOR`: the cursor is not valid for this list. Start again without one.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `WEBHOOK_NOT_FOUND`: no webhook with this id in your organization. List your webhooks to find it.
            `RESOURCE_NOT_FOUND`: the id is not a webhook id (`whk_…`). Check the id.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/webhooks/{webhook_id}/deliveries/{delivery_id}:
    get:
      tags:
        - Webhooks
      summary: Get a delivery
      description: |-
        One delivery, with `payload`: the exact body sent.

        **Scope:** `read`.
      operationId: getWebhookDelivery
      security:
        - apiKey: []
      x-scope: read
      x-order: 134
      parameters:
        - name: webhook_id
          in: path
          required: true
          description: "The webhook's id (`whk_…`)."
          schema:
            type: string
        - name: delivery_id
          in: path
          required: true
          description: "The delivery's id (`whd_…`)."
          schema:
            type: string
      responses:
        200:
          description: The delivery.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookDeliveryDetail"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `WEBHOOK_NOT_FOUND`: no webhook with this id in your organization. List your webhooks to find it.
            `WEBHOOK_DELIVERY_NOT_FOUND`: no delivery with this id for this webhook.
            `RESOURCE_NOT_FOUND`: an id is not a webhook id (`whk_…`) or delivery id (`whd_…`). Check the ids.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
  /public/v1/webhooks/{webhook_id}/deliveries/{delivery_id}/redeliver:
    post:
      tags:
        - Webhooks
      summary: Send a delivery again
      description: |-
        Queues a delivery that succeeded, failed or was cancelled to be sent again from its first attempt, with the same event `id`.

        **Idempotency:** a retry while it is being sent returns `409 WEBHOOK_DELIVERY_IN_PROGRESS`; treat that as done.

        **Scope:** `write`.
      operationId: redeliverWebhook
      security:
        - apiKey: []
      x-scope: write
      x-order: 135
      parameters:
        - name: webhook_id
          in: path
          required: true
          description: "The webhook's id (`whk_…`)."
          schema:
            type: string
        - name: delivery_id
          in: path
          required: true
          description: "The delivery's id (`whd_…`)."
          schema:
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        202:
          description: The queued delivery.
          headers:
            RateLimit-Limit:
              description: Requests allowed per window for this key.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests left in this window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-Request-Id:
              description: "This request's id."
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookDelivery"
        400:
          description: |-
            `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.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        401:
          $ref: "#/components/responses/Unauthenticated"
        403:
          $ref: "#/components/responses/Forbidden"
        404:
          description: |-
            `WEBHOOK_NOT_FOUND`: no webhook with this id in your organization. List your webhooks to find it.
            `WEBHOOK_DELIVERY_NOT_FOUND`: no delivery with this id for this webhook.
            `RESOURCE_NOT_FOUND`: an id is not a webhook id (`whk_…`) or delivery id (`whd_…`). Check the ids.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        409:
          description: |-
            `WEBHOOK_DISABLED`: the webhook is disabled. Enable it first.
            `WEBHOOK_DELIVERY_IN_PROGRESS`: the delivery is still being attempted. Wait for it to finish.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
        429:
          $ref: "#/components/responses/RateLimited"
        503:
          description: |-
            `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
            `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
          content:
            "application/problem+json":
              schema:
                $ref: "#/components/schemas/Problem"
components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: "API key (PAN_…)"
      description: "An API key from the console: `Authorization: Bearer PAN_…`."
  parameters:
    Limit:
      name: limit
      in: query
      description: "Items per page, 1 to 100. Defaults to 50."
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 50
    Cursor:
      name: cursor
      in: query
      description: "The previous page's `next_cursor`, to get the next page. Leave out for the first page; keep the other parameters the same."
      schema:
        type: string
    Q:
      name: q
      in: query
      description: "Only items whose name contains this text (case-insensitive), at most 255 characters."
      schema:
        type: string
        maxLength: 255
    DesiredState:
      name: desired_state
      in: query
      schema:
        type: string
        enum:
          - present
          - deleted
      description: "Only items with this `desired_state`: `present` or `deleted`."
    ObservedState:
      name: observed_state
      in: query
      schema:
        type: string
        enum:
          - pending
          - provisioning
          - active
          - deleting
          - deleted
          - failed
          - unknown
      description: "Only items with this `observed_state`."
    InstanceDesiredState:
      name: desired_state
      in: query
      schema:
        type: string
        enum:
          - running
          - stopped
          - deleted
      description: "Only items with this `desired_state`: `running`, `stopped` or `deleted`."
    InstanceObservedState:
      name: observed_state
      in: query
      schema:
        type: string
        enum:
          - pending
          - provisioning
          - running
          - stopping
          - stopped
          - deleting
          - deleted
          - failed
          - unknown
      description: "Only items with this `observed_state`."
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: "A unique value per change, e.g. a UUID. Retrying with the same key returns the original result instead of repeating the change; reusing it for a different request is an error. Required on every POST, PUT and DELETE."
      schema:
        type: string
        maxLength: 255
  responses:
    Unauthenticated:
      description: |-
        `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.
      headers:
        WWW-Authenticate:
          schema:
            type: string
      content:
        "application/problem+json":
          schema:
            $ref: "#/components/schemas/Problem"
    Forbidden:
      description: |-
        `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.
      content:
        "application/problem+json":
          schema:
            $ref: "#/components/schemas/Problem"
    QuotaUnavailable:
      description: "`SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry."
      content:
        "application/problem+json":
          schema:
            $ref: "#/components/schemas/Problem"
    RateLimited:
      description: |-
        `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.
      headers:
        RateLimit-Scope:
          schema:
            type: string
            enum: [api_key, user, organization]
        RateLimit-Limit:
          schema: {type: integer}
        RateLimit-Remaining:
          schema: {type: integer}
        RateLimit-Reset:
          schema: {type: integer}
        Retry-After:
          schema:
            type: integer
      content:
        "application/problem+json":
          schema:
            $ref: "#/components/schemas/Problem"
  schemas:
    Accepted:
      type: object
      description: "Returned with `202 Accepted` by every asynchronous write."
      required:
        - operation_id
        - resource_id
        - status
      properties:
        operation_id:
          type: string
          description: "The operation carrying out the change. Poll `GET /public/v1/operations/{operation_id}` (also the `Location` header) until `status` is `succeeded` or `failed`."
        resource_id:
          type: string
          description: "The resource being created or changed, e.g. the new snapshot's id for a snapshot create."
        status:
          type: string
          description: "The operation's status when the request was accepted, usually `submitting`. Follow `operation_id` for the outcome."
    AttachVolumeRequest:
      type: object
      required:
        - instance_id
      properties:
        instance_id:
          type: string
          description: "A running instance in the volume's zone, e.g. `vm_…`."
          examples: ["vm_06gk3x9tq7m2v8r4y1c5d0zp8w"]
    ChangeSecurityGroupRequest:
      type: object
      required:
        - security_group_id
      properties:
        security_group_id:
          type: string
          description: "One of your security groups in the instance's zone, e.g. `sg_…`. If the instance has a private database network interface, the group must allow nothing from `10.250.0.0/20`."
          examples: ["sg_06gk3x9tq7m2v8r4y1c5d0zp8w"]
    CreateFirewallRuleRequest:
      type: object
      required:
        - number
        - protocol
        - cidr
      properties:
        number:
          type: integer
          description: "Evaluation order, 100 to 9999, unique on the subnet; lower numbers are evaluated first and the first match decides."
          minimum: 100
          maximum: 9999
          examples: [100]
        direction:
          type: string
          default: ingress
          description: "`ingress` (traffic into the subnet) or `egress` (traffic out of it). Defaults to `ingress`."
          enum:
            - ingress
            - egress
        protocol:
          type: string
          description: "`tcp`, `udp`, `icmp` or `all`."
          enum:
            - tcp
            - udp
            - icmp
            - all
        port_start:
          type: integer
          description: "First port, 1 to 65535. For `tcp` and `udp` only; leave out for `icmp` and `all`."
          examples: [443]
        port_end:
          type: integer
          description: "Last port, 1 to 65535, at least `port_start`. Defaults to `port_start` (one port)."
          examples: [443]
        icmp_type:
          type: integer
          description: "ICMP type, for `icmp` only, e.g. `8` for echo request. Leave out for every type."
          examples: [8]
        icmp_code:
          type: integer
          description: "ICMP code, for `icmp` only. Leave out for every code."
          examples: [0]
        cidr:
          type: string
          description: "One IPv4 CIDR: the source for `ingress`, the destination for `egress`. `0.0.0.0/0` means anywhere."
          examples: ["203.0.113.0/24"]
        action:
          type: string
          default: allow
          description: "`allow` or `deny`. Defaults to `allow`."
          enum:
            - allow
            - deny
    CreateInstanceRequest:
      type: object
      required:
        - name
        - plan_slug
        - image_slug
        - ssh_key_id
      properties:
        name:
          type: string
          description: "1 to 255 characters of lowercase letters a-z, digits 0-9 and hyphens (-), starting and ending with a letter or digit, unique among your instances. Anything else is refused with 422 INVALID_INSTANCE_NAME. The guest hostname is derived from it (at most 63 characters)."
          minLength: 1
          maxLength: 255
          pattern: "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$"
          examples: ["web-1"]
        plan_slug:
          type: string
          description: "The size, from `GET /public/v1/plans`."
          examples: ["starter"]
        image_slug:
          type: string
          description: "The operating system, from `GET /public/v1/images`. It must run in the instance's zone."
          examples: ["ubuntu-24-04"]
        encrypted:
          type: boolean
          description: |-
            Optional. `true` creates the encrypted version of `image_slug`: the disk is encrypted on first boot with a key held by Pantech's key servers, and the instance needs outbound access to them at every boot (the platform keeps that open). Refused with `ENCRYPTION_NOT_SUPPORTED` when the image has no encrypted version available to your organization. `false` or omitted creates the image as named. Naming an encrypted image's own slug already creates an encrypted instance; sending `false` with that slug is refused as `ENCRYPTION_CONFLICT`.
          examples: [true]
        ssh_key_id:
          type: string
          description: "An SSH key from `GET /public/v1/ssh-keys`, installed for the default user. Required: images have no password login, so this key is how you log in."
          examples: ["sshk_06gk3x9tq7m2v8r4y1c5d0zp8w"]
        region:
          type: string
          description: "A region from `GET /public/v1/regions`, e.g. `af-abj`. Defaults to the platform's region."
          examples: ["af-abj"]
        subnet_id:
          type: string
          description: "Place the instance in this VPC subnet, with a private address only. Leave out for a standard instance with its own public IPv4."
          examples: ["snet_06gk3x9tq7m2v8r4y1c5d0zp8w"]
        network_id:
          type: string
          description: "The subnet's VPC. Optional when `subnet_id` is set; if sent, the subnet must belong to it."
          examples: ["net_06gk3x9tq7m2v8r4y1c5d0zp8w"]
        security_group_id:
          type: string
          description: "Standard instances only: one of your security groups. Defaults to the organization's default group, which starts with one rule: ICMP (ping) from anywhere."
          examples: ["sg_06gk3x9tq7m2v8r4y1c5d0zp8w"]
        tags:
          type: object
          description: "Your own labels, as string keys and values. Not interpreted by the platform."
          examples: [{"env": "production"}]
          additionalProperties:
            type: string
    CreateNetworkRequest:
      type: object
      required:
        - name
        - cidr
      properties:
        name:
          type: string
          description: "1 to 255 characters, unique among your VPC networks."
          minLength: 1
          maxLength: 255
          examples: ["prod"]
        cidr:
          type: string
          description: "The VPC's private IPv4 range, e.g. `10.0.0.0/16`. Subnets are carved from it."
          examples: ["10.0.0.0/16"]
        region:
          type: string
          description: "A region from `GET /public/v1/regions` that offers VPC networks. Defaults to the platform's region."
          examples: ["af-abj"]
    CreateLoadBalancerRequest:
      type: object
      required:
        - name
        - public_port
      properties:
        name:
          type: string
          description: "1 to 63 characters."
          examples: ["web"]
        zone_id:
          type: string
          description: "The zone to place the load balancer in."
          examples: ["zone_06gk3x9tq7m2v8r4y1c5d0zp8w"]
        kind:
          type: string
          description: "`vpc_native` (a VPC zone: needs `public_ip_id` and `subnet_id`) or `appliance` (a standard zone: needs `zone_id`; a load balancer VM with its own public address, billed as that VM). Left out, it is `vpc_native` when `subnet_id` is given and `appliance` when only `zone_id` is. Anything else is `422 INVALID_LOAD_BALANCER_KIND`; an appliance in a zone that does not offer one fails its operation with `LOAD_BALANCER_APPLIANCE_UNAVAILABLE`."
          enum:
            - vpc_native
            - appliance
        public_ip_id:
          type: string
          description: "An active public IP with `purpose` `load_balancer` (required for `vpc_native`)."
          examples: ["pip_06gk3x9tq7m2v8r4y1c5d0zp8w"]
        subnet_id:
          type: string
          description: "A subnet of the public IP's private network. Every target must be in it (required for `vpc_native`)."
          examples: ["snet_06gk3x9tq7m2v8r4y1c5d0zp8w"]
        algorithm:
          type: string
          default: roundrobin
          description: "`roundrobin` takes turns, `leastconn` picks the target with the fewest connections, `source` keeps a client on one target."
          enum:
            - roundrobin
            - leastconn
            - source
        public_port:
          type: integer
          description: "The TCP port clients connect to, 1 to 65535."
          examples: [443]
        private_port:
          type: integer
          description: "The port on each target. Defaults to `public_port`."
          examples: [8443]
        cidr_list:
          type: array
          description: "Source addresses allowed to connect, as IPv4 CIDRs (at most 20). Leave out for anywhere."
          items:
            type: string
          examples: [["0.0.0.0/0"]]
        instance_ids:
          type: array
          description: "Instances to spread traffic across (at most 50). You can add them later."
          items:
            type: string
          examples: [["vm_06gk3x9tq7m2v8r4y1c5d0zp8w"]]
    UpdateLoadBalancerRequest:
      type: object
      properties:
        name:
          type: string
          description: "A new name, 1 to 63 characters."
          examples: ["web"]
        algorithm:
          type: string
          enum:
            - roundrobin
            - leastconn
            - source
        instance_ids:
          type: array
          description: "The whole new set of targets (at most 50). Send `[]` to remove them all."
          items:
            type: string
          examples: [["vm_06gk3x9tq7m2v8r4y1c5d0zp8w"]]
    LoadBalancerMember:
      type: object
      required:
        - instance_id
        - desired_state
        - observed_state
      properties:
        instance_id:
          type: string
        instance_name:
          type:
            - string
            - "null"
          description: "The instance's name; null when it is gone."
        desired_state:
          type: string
          description: "`present` while it should receive traffic; `deleted` once you removed it and that has not finished."
          enum:
            - present
            - deleted
        observed_state:
          type: string
          description: "`active` once it receives traffic."
          enum:
            - pending
            - active
            - deleting
            - deleted
            - failed
            - unknown
    LoadBalancer:
      type: object
      required:
        - id
        - name
        - zone_id
        - kind
        - public_ip_id
        - public_ip_address
        - network_id
        - subnet_id
        - protocol
        - algorithm
        - public_port
        - private_port
        - cidr_list
        - members
        - desired_state
        - observed_state
        - in_sync
        - created_at
        - updated_at
      properties:
        id:
          type: string
          description: "e.g. `lb_...`."
        name:
          type: string
        zone_id:
          type: string
        kind:
          type: string
          enum:
            - vpc_native
            - appliance
        public_ip_id:
          type: string
        public_ip_address:
          type:
            - string
            - "null"
          description: "The address clients connect to; null until it is known."
        network_id:
          type:
            - string
            - "null"
        subnet_id:
          type: string
        protocol:
          type: string
          enum:
            - tcp
            - http
            - https
        algorithm:
          type: string
          enum:
            - roundrobin
            - leastconn
            - source
        public_port:
          type: integer
        private_port:
          type: integer
        cidr_list:
          type: array
          description: "Source addresses allowed to connect; empty is anywhere."
          items:
            type: string
        members:
          type: array
          items:
            $ref: "#/components/schemas/LoadBalancerMember"
        desired_state:
          type: string
          description: "What you asked for: `present`, or `deleted` once you deleted it."
          enum:
            - present
            - deleted
        observed_state:
          type: string
          description: "`active` once it serves traffic."
          enum:
            - pending
            - provisioning
            - active
            - deleting
            - deleted
            - failed
            - unknown
        in_sync:
          type: boolean
          description: "False while a change you made has not reached the load balancer yet."
        created_at:
          type:
            - string
            - "null"
          format: date-time
        updated_at:
          type:
            - string
            - "null"
          format: date-time
    LoadBalancerList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/LoadBalancer"
        next_cursor:
          type:
            - string
            - "null"
          description: "Always null: this list is returned whole."
    KubernetesVersion:
      type: object
      required:
        - id
        - zone_id
        - version
        - status
        - min_cpu
        - min_memory_mb
      properties:
        id:
          type: string
          description: "e.g. `k8sv_...`."
        zone_id:
          type: string
        version:
          type: string
          examples: ["1.31.2"]
        status:
          type: string
          description: "`withdrawn` versions take no new clusters."
          enum:
            - available
            - withdrawn
        min_cpu:
          type: integer
          description: "The fewest vCPUs a node plan needs for this version."
        min_memory_mb:
          type: integer
          description: "The least memory, in MB, a node plan needs for this version."
    KubernetesVersionList:
      type: object
      required:
        - data
        - ha_zone_ids
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/KubernetesVersion"
        ha_zone_ids:
          type: array
          description: "Zones where a cluster can have 3 control nodes."
          items:
            type: string
    CreateKubernetesClusterRequest:
      type: object
      required:
        - name
        - zone_id
        - kubernetes_version_id
        - node_plan
      properties:
        name:
          type: string
          description: "1 to 40 lowercase letters, digits and hyphens, starting with a letter."
          examples: ["prod-web"]
        zone_id:
          type: string
          examples: ["zone_06gk3x9tq7m2v8r4y1c5d0zp8w"]
        kubernetes_version_id:
          type: string
          description: "From the Kubernetes versions list, in `zone_id`."
          examples: ["k8sv_06gk3x9tq7m2v8r4y1c5d0zp8w"]
        subnet_id:
          type: string
          description: "Required in a VPC zone: the subnet the nodes go in. Leave out in a standard zone."
          examples: ["snet_06gk3x9tq7m2v8r4y1c5d0zp8w"]
        node_plan:
          type: string
          description: "The plan slug every node uses, from the plans list. In a zone where every node has one fixed size (af-abj-2: 2 vCPU, 4 GB), the plan must be that size: `422 KUBERNETES_PLAN_NOT_IN_ZONE` on `node_plan`."
          examples: ["s-2vcpu-4gb"]
        control_nodes:
          type: integer
          default: 1
          description: "1, or 3 for a highly available control plane where the zone offers it."
          enum:
            - 1
            - 3
        workers:
          type: integer
          description: "Worker nodes, 1 to 10. Required unless `autoscaling` is enabled, when it defaults to `min_workers` and must lie within the range."
          minimum: 1
          maximum: 10
          examples: [2]
        autoscaling:
          $ref: "#/components/schemas/KubernetesAutoscaling"
        api_allowed_cidrs:
          type: array
          description: "VPC zone only: the IPv4 CIDRs allowed to reach the API server (at most 20). Leave out for any address."
          items:
            type: string
          examples: [["203.0.113.0/24"]]
        kind:
          type: string
          description: "Cluster architecture. Only `appliance` (the default) is offered: nodes are VMs in your account running k3s. Any other value is `422 INVALID_KUBERNETES_KIND`."
          enum: [appliance]
          examples: ["appliance"]
        cni_plugin:
          type: string
          description: "CNI backend plugin (`flannel`, `wireguard`, or `calico`). Defaults to `flannel`."
          examples: ["flannel"]
        pod_cidr:
          type: string
          description: "IPv4 CIDR range allocated for cluster pods. Defaults to `10.42.0.0/16`."
          examples: ["10.42.0.0/16"]
        service_cidr:
          type: string
          description: "IPv4 CIDR range allocated for cluster services. Defaults to `10.43.0.0/16`."
          examples: ["10.43.0.0/16"]
        endpoint_access:
          type: string
          enum:
            - private
            - public
            - dual
          default: private
          description: "Who can reach the API server. `private` (the default) is reachable only from inside the VPC. `public` and `dual` give the API server a public address of its own, billed as a VPC public IP for every hour it is held."
          examples: ["private"]
    KubernetesAutoscaling:
      type: object
      description: "The platform's autoscaler: about once a minute it adds a worker when a pod that a worker could run is waiting for room, and removes the newest worker once it has run nothing but DaemonSet pods for 10 minutes, one step at a time and always within min_workers and max_workers. You pay for the workers that run."
      required:
        - enabled
        - min_workers
        - max_workers
      properties:
        enabled:
          type: boolean
          description: "The cluster autoscaler adds and removes workers within the range as pods need them."
        min_workers:
          type: integer
          description: "1 to 10 when enabled; 0 when not."
          examples: [2]
        max_workers:
          type: integer
          description: "min_workers to 10 when enabled; 0 when not. Billing and your account limits are checked for the maximum."
          examples: [5]
    UpdateKubernetesClusterRequest:
      type: object
      description: "Send any of the fields; those left out stay as they are."
      properties:
        workers:
          type: integer
          description: "A fixed worker count, 1 to 10. Not with autoscaling enabled: the autoscaler sets it."
          minimum: 1
          maximum: 10
          examples: [3]
        autoscaling:
          $ref: "#/components/schemas/KubernetesAutoscaling"
        api_allowed_cidrs:
          type: array
          description: "VPC zone only: the whole new list of IPv4 CIDRs allowed to reach the API server. Send `[]` to allow any address."
          items:
            type: string
          examples: [["203.0.113.0/24"]]
    UpgradeKubernetesClusterRequest:
      type: object
      required:
        - kubernetes_version_id
      properties:
        kubernetes_version_id:
          type: string
          description: "One of the cluster's `available_upgrades`."
          examples: ["k8sv_06gk3x9tq7m2v8r4y1c5d0zp8w"]
    ResizeKubernetesClusterPlanRequest:
      type: object
      required:
        - node_plan
      properties:
        node_plan:
          type: string
          description: "The new node plan slug, from the plans list. In a zone with one fixed node size the plan must be that size (`422 KUBERNETES_PLAN_NOT_IN_ZONE`)."
          examples: ["s-2vcpu-4gb"]
    UpgradeKubernetesNetworkRequest:
      type: object
      properties:
        cni_plugin:
          type: string
          description: "The new CNI plugin (`flannel`, `wireguard`, or `calico`)."
          examples: ["wireguard"]
        pod_cidr:
          type: string
          description: "The new IPv4 CIDR range allocated for cluster pods."
          examples: ["10.42.0.0/16"]
        service_cidr:
          type: string
          description: "The new IPv4 CIDR range allocated for cluster services."
          examples: ["10.43.0.0/16"]
        api_allowed_cidrs:
          type: array
          description: "The new allowed IPv4 CIDRs reaching the API server."
          items:
            type: string
          examples: [["203.0.113.0/24"]]
    KubernetesNode:
      type: object
      required:
        - vcpu
        - memory_mb
        - disk_gb
      properties:
        vcpu:
          type: integer
        memory_mb:
          type: integer
        disk_gb:
          type: integer
    KubernetesCluster:
      type: object
      required:
        - id
        - name
        - zone_id
        - kubernetes_version_id
        - kubernetes_version
        - network_id
        - subnet_id
        - node_plan_id
        - node
        - control_nodes
        - workers
        - nodes
        - desired_state
        - observed_state
        - in_sync
        - failure_code
        - failure_reason
        - available_upgrades
        - autoscaling
        - api_allowed_cidrs
        - endpoint
        - volume_storage_gb
        - kind
        - cni_plugin
        - pod_cidr
        - service_cidr
        - endpoint_access
        - created_at
        - updated_at
      properties:
        id:
          type: string
          description: "e.g. `k8s_...`."
        name:
          type: string
        zone_id:
          type: string
        kubernetes_version_id:
          type: string
        kubernetes_version:
          type: string
          examples: ["1.31.2"]
        network_id:
          type:
            - string
            - "null"
          description: "The private network (VPC) the nodes are in; null in a standard zone."
        subnet_id:
          type:
            - string
            - "null"
        node_plan_id:
          type: string
        node:
          $ref: "#/components/schemas/KubernetesNode"
        control_nodes:
          type: integer
        workers:
          type: integer
        nodes:
          type: integer
          description: "control_nodes plus workers: the VMs billed."
        desired_state:
          type: string
          enum:
            - running
            - stopped
            - deleted
        observed_state:
          type: string
          description: "`updating` while a scale or upgrade runs."
          enum:
            - pending
            - provisioning
            - running
            - updating
            - stopping
            - stopped
            - retired
            - deleted
            - failed
            - unknown
        in_sync:
          type: boolean
          description: "False while a scale or upgrade has not finished."
        failure_code:
          type:
            - string
            - "null"
        failure_reason:
          type:
            - string
            - "null"
        available_upgrades:
          type: array
          description: "Versions this cluster can be upgraded to now."
          items:
            $ref: "#/components/schemas/KubernetesVersion"
        autoscaling:
          $ref: "#/components/schemas/KubernetesAutoscaling"
        api_allowed_cidrs:
          type: array
          description: "Sources allowed to reach the API server; empty is any address."
          items:
            type: string
        endpoint:
          type:
            - string
            - "null"
          description: "The API server's URL, once the cluster has one: the public address when the endpoint is public or dual and one is routed, else the private one."
        public_ip:
          type:
            - string
            - "null"
          description: "The public address the API server is reachable on; null until one is routed, and for a private endpoint."
          examples: ["203.0.113.20"]
        public_port:
          type:
            - integer
            - "null"
          description: "The public port the API server is reachable on; null until one is routed, and for a private endpoint."
          examples: [6443]
        volume_storage_gb:
          type: integer
          description: "Storage your workloads' persistent volume claims use, billed with the nodes' disks."
        kind:
          type: string
          description: "Cluster architecture kind, e.g. `appliance`."
          examples: ["appliance"]
        cni_plugin:
          type: string
          description: "CNI backend plugin (`flannel`, `wireguard`, or `calico`)."
          examples: ["flannel"]
        pod_cidr:
          type: string
          description: "IPv4 CIDR range allocated for cluster pods."
          examples: ["10.42.0.0/16"]
        service_cidr:
          type: string
          description: "IPv4 CIDR range allocated for cluster services."
          examples: ["10.43.0.0/16"]
        endpoint_access:
          type: string
          description: "API server endpoint network access mode (`public`, `private`, or `dual`)."
          enum:
            - public
            - private
            - dual
          examples: ["public"]
        created_at:
          type:
            - string
            - "null"
          format: date-time
        updated_at:
          type:
            - string
            - "null"
          format: date-time
    KubernetesClusterList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/KubernetesCluster"
        next_cursor:
          type:
            - string
            - "null"
          description: "Always null: this list is returned whole."
    Kubeconfig:
      type: object
      required:
        - kubeconfig
      properties:
        kubeconfig:
          type: string
          description: "A kubeconfig file (YAML). Secret: it grants cluster-admin."
    KubernetesNodePoolTaint:
      type: object
      required:
        - key
        - value
        - effect
      properties:
        key:
          type: string
        value:
          type: string
        effect:
          type: string
          enum:
            - NoSchedule
            - PreferNoSchedule
            - NoExecute
    KubernetesNodePool:
      type: object
      required:
        - id
        - cluster_id
        - name
        - node_plan_id
        - workers
        - autoscaling_enabled
        - min_workers
        - max_workers
        - labels
        - taints
        - status
        - observed_workers
        - created_at
        - updated_at
      properties:
        status:
          type: string
          description: "`provisioning` (workers being built), `ready` (every worker has joined), `updating` (a scale in progress), `deleting`, or `unavailable` (a pool made while node pools were a preview, with no node size: delete it and create it again)."
          enum: [provisioning, ready, updating, deleting, unavailable]
        observed_workers:
          type: integer
          description: "The pool's workers that have joined the cluster. This is what is billed, never `workers` alone."
        id:
          type: string
          description: "e.g. `k8spool_...`."
        cluster_id:
          type: string
        name:
          type: string
        node_plan_id:
          type: string
        workers:
          type: integer
        autoscaling_enabled:
          type: boolean
        min_workers:
          type: integer
        max_workers:
          type: integer
        labels:
          type: object
          additionalProperties:
            type: string
        taints:
          type: array
          items:
            $ref: "#/components/schemas/KubernetesNodePoolTaint"
        created_at:
          type:
            - string
            - "null"
          format: date-time
        updated_at:
          type:
            - string
            - "null"
          format: date-time
    KubernetesNodePoolList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/KubernetesNodePool"
        next_cursor:
          type:
            - string
            - "null"
    CreateKubernetesNodePoolRequest:
      type: object
      required:
        - name
        - plan_id
      properties:
        name:
          type: string
          description: "Unique name for the node pool."
          examples: ["cpu-workers"]
        plan_id:
          type: string
          description: "Compute plan ID or slug for the worker VMs."
          examples: ["standard-2"]
        workers:
          type: integer
          description: "Worker nodes, 1 to 10. Required unless autoscaling_enabled, when it defaults to min_workers."
        autoscaling_enabled:
          type: boolean
          default: false
        min_workers:
          type: integer
          description: "With autoscaling_enabled: fewest workers, 1 to 10."
        max_workers:
          type: integer
          description: "With autoscaling_enabled: most workers, min_workers to 10. Your credit and limits are checked for this many."
        labels:
          type: object
          additionalProperties:
            type: string
        taints:
          type: array
          items:
            $ref: "#/components/schemas/KubernetesNodePoolTaint"
    ScaleKubernetesNodePoolRequest:
      type: object
      properties:
        workers:
          type: integer
          description: "Worker nodes, 1 to 10. Always send the pool's whole size: autoscaling_enabled left out turns autoscaling off."
        autoscaling_enabled:
          type: boolean
        min_workers:
          type: integer
        max_workers:
          type: integer
    KubernetesClusterAddon:
      type: object
      required:
        - id
        - cluster_id
        - name
        - version
        - status
        - enabled
        - configuration
        - created_at
        - updated_at
      properties:
        id:
          type: string
          description: "e.g. `k8saddon_...`."
        cluster_id:
          type: string
        name:
          type: string
        version:
          type: string
        status:
          type: string
        enabled:
          type: boolean
        configuration:
          type: string
          description: "JSON-encoded add-on configuration."
        created_at:
          type:
            - string
            - "null"
          format: date-time
        updated_at:
          type:
            - string
            - "null"
          format: date-time
    KubernetesClusterAddonList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/KubernetesClusterAddon"
        next_cursor:
          type:
            - string
            - "null"
    ToggleKubernetesAddonRequest:
      type: object
      required:
        - enabled
      properties:
        enabled:
          type: boolean
          description: "Whether the add-on should be enabled or disabled."
    ConfigureKubernetesEndpointAccessRequest:
      type: object
      required:
        - endpoint_access
      properties:
        endpoint_access:
          type: string
          enum:
            - public
            - private
            - dual
          description: "Endpoint access mode."
        api_allowed_cidrs:
          type: array
          items:
            type: string
          description: "IPv4 CIDRs allowed to connect to the cluster API."
    CreatePortForwardingRuleRequest:
      type: object
      required:
        - instance_id
        - protocol
        - public_port_start
      properties:
        instance_id:
          type: string
          description: "An instance in the public IP's VPC."
          examples: ["vm_06gk3x9tq7m2v8r4y1c5d0zp8w"]
        protocol:
          type: string
          description: "`tcp` or `udp`."
          enum:
            - tcp
            - udp
        public_port_start:
          type: integer
          description: "First public port, 1 to 65535."
          examples: [443]
        public_port_end:
          type: integer
          description: "Last public port. Defaults to `public_port_start` (one port)."
          examples: [443]
        private_port_start:
          type: integer
          description: "First port on the instance. Defaults to `public_port_start`."
          examples: [8443]
        private_port_end:
          type: integer
          description: "Last port on the instance; the private range must be as wide as the public one. Defaults to match it."
          examples: [8443]
    CreatePublicIPRequest:
      type: object
      required:
        - network_id
      properties:
        network_id:
          type: string
          description: "The VPC network to allocate the address in. It must be active."
          examples: ["net_06gk3x9tq7m2v8r4y1c5d0zp8w"]
        purpose:
          type: string
          default: static_nat
          description: "`static_nat` maps every port to one instance (`instance_id`). `port_forwarding` carries per-port rules instead, and `load_balancer` carries load balancers. Defaults to `static_nat`."
          enum:
            - static_nat
            - port_forwarding
            - load_balancer
        instance_id:
          type: string
          description: "For `static_nat` only: an instance in `network_id` without a public IP. Leave it out to reserve the address now and attach it later (`POST /public/v1/public-ips/{public_ip_id}/attach`). Leave out for `port_forwarding` and `load_balancer`."
          examples: ["vm_06gk3x9tq7m2v8r4y1c5d0zp8w"]
    CreateSSHKeyRequest:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          description: "1 to 255 characters, unique among your SSH keys."
          minLength: 1
          maxLength: 255
          examples: ["deploy"]
        public_key:
          type: string
          description: "One OpenSSH public key line, e.g. `ssh-ed25519 AAAA… deploy@ci`. Leave out to have a key pair generated; its private key is then returned once."
          examples: ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExample deploy@ci"]
    CreateSecurityGroupRequest:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          description: "1 to 255 characters, unique among your live security groups."
          minLength: 1
          maxLength: 255
          examples: ["web"]
        rules:
          type: array
          description: "The group's rules; at least one. Inbound traffic no rule allows is dropped."
          items:
            $ref: "#/components/schemas/SecurityGroupRule"
    ResizeDatabaseStorageRequest:
      type: object
      required:
        - storage_gb
      properties:
        storage_gb:
          type: integer
          minimum: 1
          description: "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."
          examples: [100]
    CreateSnapshotRequest:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          description: "1 to 255 characters, unique among this resource's snapshots, e.g. `pre-upgrade`."
          minLength: 1
          maxLength: 255
          examples: ["pre-upgrade"]
    CreateSubnetRequest:
      type: object
      required:
        - name
        - cidr
      properties:
        name:
          type: string
          description: "1 to 255 characters."
          minLength: 1
          maxLength: 255
          examples: ["app"]
        cidr:
          type: string
          description: "An IPv4 range inside the VPC's CIDR that does not overlap its other subnets, e.g. `10.0.1.0/24`."
          examples: ["10.0.1.0/24"]
    CreateVolumeRequest:
      type: object
      required:
        - name
        - disk_offering_slug
      properties:
        name:
          type: string
          description: "1 to 255 characters."
          minLength: 1
          maxLength: 255
          examples: ["data"]
        disk_offering_slug:
          type: string
          description: "The volume type, from `GET /public/v1/disk-offerings`."
          examples: ["medium-20gb"]
        size_gb:
          type: integer
          description: "Size in GB. Required for an offering with `custom_size: true`; ignored otherwise."
          examples: [100]
        region:
          type: string
          description: "A region from `GET /public/v1/regions`. Defaults to the platform's region."
          examples: ["af-abj"]
        instance_id:
          type: string
          description: "Attach the new volume at once to this running instance in the same zone."
          examples: ["vm_06gk3x9tq7m2v8r4y1c5d0zp8w"]
        mount_point:
          type: string
          description: "With `instance_id`: an absolute path to mount the volume at, e.g. `/data`."
          examples: ["/data"]
        encrypted:
          type: boolean
          description: |-
            Optional. `true` asks for an **encrypted data volume**. It is a request, not encryption yet: the volume reports `encryption.status` `requested` until its disk has been formatted with a key from Pantech, and `encrypted` only once the platform has confirmed that. The format happens inside the instance, by the Pantech volume agent (`pantech-vkey`): attach the volume to an instance that runs the agent and it formats the new, empty disk for you as soon as it appears, so you only create a filesystem on `/dev/mapper/pvk-...` and mount it. Creating or attaching alone encrypts nothing, an instance without the agent cannot encrypt it, and a disk that already holds data is never formatted. Its key is released only to the instance of your organization the volume is attached to, so the volume can be used with any instance that runs the agent, an encrypted VM or a plain one. Refused with `ENCRYPTION_NOT_SUPPORTED` when encrypted volumes are not enabled for your organization, and `ENCRYPTED_VOLUMES_UNAVAILABLE` while the service is off. `false` or omitted creates a plain volume, as before.
          examples: [true]
    ChangeDatabasePasswordRequest:
      type: object
      required:
        - password
      properties:
        password:
          type: string
          minLength: 16
          maxLength: 128
          writeOnly: true
          description: "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."
          pattern: "^[!#-&(-\\[\\]-~]{16,128}$"
          examples: ["c0rrect-H0rse-battery-St4ple"]
    CreateDatabaseRequest:
      type: object
      required:
        - name
        - engine
        - version
        - plan_slug
        - zone_id
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 255
          description: "1 to 255 characters, unique among your databases."
          examples: ["orders-db"]
        engine:
          type: string
          description: "`postgresql`, `mysql` or `mariadb`; see `GET /public/v1/database-engines`."
          enum:
            - postgresql
            - mysql
            - mariadb
        version:
          type: string
          description: "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."
          examples: ["18"]
        plan_slug:
          type: string
          description: "The size, from `GET /public/v1/plans`: its vCPU and memory, and its `disk_gb` as the smallest data disk."
          examples: ["starter"]
        zone_id:
          type: string
          description: "Where to create it: a zone from the version's `zones`, e.g. `af-abj-1` (standard) or `af-abj-2` (VPC)."
          examples: ["af-abj-2"]
        subnet_id:
          type: string
          description: "Required in a VPC zone (`af-abj-2`): one of your subnets there, which the database joins. Leave out in a standard zone."
          examples: ["snet_06gk3x9tq7m2v8r4y1c5d0zp8w"]
        access_rules:
          type: array
          maxItems: 50
          description: "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."
          examples: [["10.0.1.0/24", "10.250.3.17/32"]]
          items:
            type: string
        admin_username:
          type: string
          minLength: 3
          maxLength: 32
          pattern: "^[a-z][a-z0-9_]{2,31}$"
          description: "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`."
          default: "dbadmin"
          examples: ["app_admin"]
        password:
          type: string
          minLength: 16
          maxLength: 128
          writeOnly: true
          description: "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."
          pattern: "^[!#-&(-\\[\\]-~]{16,128}$"
          examples: ["c0rrect-H0rse-battery-St4ple"]
        storage_gb:
          type: integer
          minimum: 1
          description: "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."
          examples: [100]
    Database:
      type: object
      required:
        - id
        - name
        - engine
        - version
        - port
        - plan_id
        - data_volume_size_gb
        - pending_data_volume_size_gb
        - zone_id
        - subnet_id
        - hostname
        - private_ip
        - desired_state
        - observed_state
        - generation
        - observed_generation
        - access_rules
        - failure_code
        - created_at
        - updated_at
        - admin_username
        - security_group_ids
        - effective_access_rules
        - ignored_security_group_rules
      properties:
        admin_username:
          type: string
          description: "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."
          examples: ["dbadmin"]
        security_group_ids:
          type: array
          description: "Your security groups attached to the database, in the order attached (at most 5). `[]` when none."
          items:
            type: string
        effective_access_rules:
          type: array
          description: "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`."
          items:
            $ref: "#/components/schemas/DatabaseEffectiveAccessRule"
        ignored_security_group_rules:
          type: array
          description: "Rules of the attached security groups that add nothing to the database, and why. `[]` when none."
          items:
            $ref: "#/components/schemas/DatabaseIgnoredSecurityGroupRule"
        id:
          type: string
          description: "The database's id, `db_` followed by 26 characters."
          readOnly: true
          examples: ["db_06gk3x9tq7m2v8r4y1c5d0zp8w"]
        name:
          type: string
          description: "Its name, unique among your databases."
          examples: ["orders-db"]
        engine:
          type: string
          description: "`postgresql`, `mysql` or `mariadb`."
          enum:
            - postgresql
            - mysql
            - mariadb
        version:
          type: string
          description: "The engine's version line, e.g. `18` or `8.4`. Minor updates within the line are applied by the platform."
          examples: ["18"]
        port:
          type: integer
          description: "The TCP port to connect to: 5432 for PostgreSQL, 3306 for MySQL and MariaDB."
          examples: [5432]
        plan_id:
          type: string
          description: "The plan setting its vCPU and memory."
          examples: ["plan_06gk3x9tq7m2v8r4y1c5d0zp8w"]
        data_volume_size_gb:
          type: integer
          description: "The data disk's size in GB, as in place and billed."
          examples: [80]
        pending_data_volume_size_gb:
          type:
            - integer
            - "null"
          description: "The size a storage resize in flight is growing the data disk to; null when none."
        zone_id:
          type: string
          description: "The zone it runs in, e.g. `af-abj-2`."
          examples: ["af-abj-2"]
        subnet_id:
          type:
            - string
            - "null"
          description: "Its VPC subnet in a VPC zone; null in a standard zone."
        hostname:
          type:
            - string
            - "null"
          description: "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."
          examples: ["db-7k2q9x4m1a.af-abj-2.db.pantechdynamics.com"]
        private_ip:
          type:
            - string
            - "null"
          description: "The database's private address, never public. Null until assigned."
          examples: ["10.0.1.12"]
        desired_state:
          type: string
          description: "What you asked for: `running`, `stopped` or `deleted`."
          enum:
            - running
            - stopped
            - deleted
        observed_state:
          type: string
          description: "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."
          enum:
            - pending
            - provisioning
            - running
            - stopping
            - stopped
            - deleting
            - deleted
            - failed
            - unknown
        generation:
          type: integer
          description: "Increases by one with every change you request."
          readOnly: true
        observed_generation:
          type: integer
          description: "The `generation` last applied: equal to `generation` once the last change (access rules, security groups, password) is in effect."
          readOnly: true
        access_rules:
          type: array
          description: "The database's own allow-list, as set at create or with *Replace access rules*."
          items:
            $ref: "#/components/schemas/DatabaseAccessRule"
        failure_code:
          type:
            - string
            - "null"
          description: "A stable code for why the last change failed; null when nothing failed. The failed operation's `failure.reason` explains it."
        created_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it was created (RFC 3339, UTC)."
          readOnly: true
        updated_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it last changed (RFC 3339, UTC)."
          readOnly: true
    DatabaseAccessRule:
      type: object
      required:
        - cidr
        - protocol
        - port
      properties:
        cidr:
          type: string
          description: "An allowed IPv4 range, in canonical form."
          examples: ["10.0.1.0/24"]
        protocol:
          type: string
          description: "Always `tcp`."
          enum:
            - tcp
        port:
          type: integer
          description: "The engine's port: 5432 or 3306."
          examples: [5432]
    DatabaseEffectiveAccessRule:
      type: object
      required:
        - cidr
        - protocol
        - port
        - source
      properties:
        cidr:
          type: string
          description: "An allowed IPv4 range, in canonical form."
          examples: ["10.0.1.0/24"]
        protocol:
          type: string
          description: "Always `tcp`."
          enum:
            - tcp
        port:
          type: integer
          description: "The engine's port: 5432 or 3306."
          examples: [5432]
        source:
          type: string
          description: "Where the range comes from: `access_rule`, or `security_group:` followed by the group's id (e.g. `security_group:sg_…`)."
          examples: ["access_rule"]
    DatabaseIgnoredSecurityGroupRule:
      type: object
      required:
        - security_group_id
        - direction
        - protocol
        - port_range
        - cidr
        - reason
      properties:
        security_group_id:
          type: string
          description: "The attached group the rule belongs to."
        direction:
          type: string
          description: "The rule's direction."
          enum:
            - ingress
            - egress
        protocol:
          type: string
          description: "The rule's protocol."
          enum:
            - tcp
            - udp
            - icmp
            - all
        port_range:
          type:
            - string
            - "null"
          description: "`80`, `3000-4000`, or null for all ports."
        cidr:
          type: string
          description: "The rule's CIDR."
          examples: ["0.0.0.0/0"]
        reason:
          type: string
          description: "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`."
          enum:
            - egress_rule
            - protocol_not_tcp
            - port_not_covered
            - cidr_too_wide
            - cidr_not_ipv4
    SetDatabaseSecurityGroupsRequest:
      type: object
      required:
        - security_group_ids
      properties:
        security_group_ids:
          type: array
          maxItems: 5
          description: "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."
          uniqueItems: true
          examples: [["sg_06gk3x9tq7m2v8r4y1c5d0zp8w"]]
          items:
            type: string
    DatabaseAccessRuleRequest:
      type: object
      required:
        - cidr
      properties:
        cidr:
          type: string
          description: "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`."
          examples: ["10.250.3.17/32"]
    DatabaseEngine:
      type: object
      required:
        - engine
        - display_name
        - port
        - versions
        - storage
      properties:
        engine:
          type: string
          description: "The value `engine` takes in *Create a database*: `postgresql`, `mysql` or `mariadb`."
          examples: ["postgresql"]
        display_name:
          type: string
          description: "The engine's name for display."
          examples: ["PostgreSQL"]
        port:
          type: integer
          description: "The port its databases listen on: 5432 or 3306."
          examples: [5432]
        versions:
          type: array
          description: "Version lines offered for new databases, long-term-support lines only. Empty when none is offered right now."
          items:
            type: object
            required:
              - version
              - eol_date
              - zones
            properties:
              version:
                type: string
              eol_date:
                type:
                  - string
                  - "null"
                format: date
              zones:
                type: array
                items:
                  type: string
        storage:
          type: array
          description: "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."
          items:
            type: object
            required:
              - zone_id
              - min_gb
              - min_is_plan_disk
              - max_gb
              - step_gb
              - price_per_gb_month_minor
              - currency
            properties:
              zone_id:
                type: string
                description: "The zone, e.g. `af-abj-2`."
                examples: ["af-abj-2"]
              min_gb:
                type: integer
                description: "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."
                minimum: 0
                examples: [0]
              min_is_plan_disk:
                type: boolean
                description: "True when `min_gb` is 0, so the chosen plan's `disk_gb` is the minimum."
              max_gb:
                type: integer
                description: "The largest data disk in GB."
                examples: [2000]
              step_gb:
                type: integer
                description: "Above the base size (the minimum), `storage_gb` must be a multiple of this."
                examples: [10]
              price_per_gb_month_minor:
                type:
                  - integer
                  - "null"
                format: int64
                description: "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:
                type:
                  - string
                  - "null"
                description: "`NGN` or `USD`; null when not priced."
    DatabaseEngineList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/DatabaseEngine"
        next_cursor:
          type:
            - string
            - "null"
          description: "Always null: this list is returned whole."
    DatabaseList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Database"
        next_cursor:
          type:
            - string
            - "null"
          description: "Pass as `cursor` for the next page; null on the last page."
    DatabaseOrder:
      type: object
      description: "How a new database is paid for and provisioned. Never includes a password. The console API returns the same order, naming the id `order_id`."
      required:
        - id
        - order_id
        - attempt_id
        - database_id
        - status
        - operation_id
        - failure_code
        - amount_minor
        - currency
        - created_at
        - updated_at
        - admin_username
      properties:
        admin_username:
          type: string
          description: "The admin login the database is created with."
        id:
          type: string
          description: "The order's id, `ord_…`."
          readOnly: true
        order_id:
          type: string
          description: "The same value as `id`."
        attempt_id:
          type:
            - string
            - "null"
          description: "The card payment attempt; null when the order was paid from credit or not charged yet."
        database_id:
          type: string
          description: "The id the database has, or will have once provisioned."
        status:
          type: string
          description: "`awaiting_payment`, then `paid`, `provisioning` and `provisioned` (final: the database exists). `payment_failed` (no payment could be taken; nothing charged, or it was returned) and `failed` (provisioning could not start; any payment taken is returned to your credit) are final too."
          enum:
            - awaiting_payment
            - paid
            - provisioning
            - provisioned
            - payment_failed
            - failed
        operation_id:
          type:
            - string
            - "null"
          description: "The database's create operation, set once provisioning starts; null before."
        failure_code:
          type:
            - string
            - "null"
          description: "Why the order failed, when it did: e.g. `insufficient_credit`, `card_declined`, `payment_expired` (not paid within an hour), `database_name_taken`, `provisioning_failed`. Null otherwise. `organization_deleted`: the order was cancelled because its organization was deleted; any payment taken is returned to credit."
        amount_minor:
          type: integer
          format: int64
          description: "The initial payment in minor units of `currency` (kobo or cents)."
        currency:
          type: string
          description: "`NGN` or `USD`: your organization's billing currency."
          enum:
            - NGN
            - USD
        created_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it was created (RFC 3339, UTC)."
          readOnly: true
        updated_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it last changed (RFC 3339, UTC)."
          readOnly: true
    DatabaseOrderList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/DatabaseOrder"
        next_cursor:
          type:
            - string
            - "null"
          description: "Pass as `cursor` for the next page; null on the last page."
    DatabaseOrderAccepted:
      type: object
      description: "Returned with `202 Accepted` by `POST /public/v1/databases`, with `Cache-Control: no-store`: the order that pays for and then provisions the database."
      required:
        - order_id
        - database_id
        - resource_id
        - status
        - operation_id
        - failure_code
        - amount_minor
        - currency
        - password_returned
        - password
        - admin_username
      properties:
        admin_username:
          type: string
          description: "The admin login the database is created with: the one you sent, or `dbadmin`."
        order_id:
          type: string
          description: "Follow it at `GET /public/v1/database-orders/{order_id}` (also the `Location` header)."
        database_id:
          type: string
          description: The id the database will have.
        resource_id:
          type: string
          description: Same as `database_id`.
        status:
          type: string
          description: "The order's status now, usually `awaiting_payment`. See `DatabaseOrder.status`."
          enum:
            - awaiting_payment
            - paid
            - provisioning
            - provisioned
            - payment_failed
            - failed
        operation_id:
          type:
            - string
            - "null"
          description: Set once provisioning starts.
        failure_code:
          type:
            - string
            - "null"
          description: "Null for a new order; on a retry, the order's `failure_code` if it has failed since."
        amount_minor:
          type: integer
          description: "The initial payment, in minor units (kobo or cents): the price of an instance of the same plan with no public address."
          format: int64
        currency:
          type: string
          description: "`NGN` or `USD`: your organization's billing currency."
          enum:
            - NGN
            - USD
        password_returned:
          type: boolean
          description: "True only when the password was generated for you and this request created the order. Otherwise `password` is null: you chose the password, or this is a retry."
        password:
          type:
            - string
            - "null"
          description: "The generated admin password (32 letters and digits), in this response only. Store it securely now: it is never readable again. Null when you chose the password or on a retry."
          examples: ["q8Vn3TzK1mXw7RbLc4Hs9JdP2fYa6GeU"]
    DatabasePasswordAccepted:
      type: object
      description: "Returned with `202 Accepted` by `reset-password`, with `Cache-Control: no-store`."
      required:
        - operation_id
        - resource_id
        - status
        - password_returned
        - password
      properties:
        operation_id:
          type: string
          description: "The operation carrying out the change. Poll `GET /public/v1/operations/{operation_id}` (also the `Location` header) until `status` is `succeeded` or `failed`."
        resource_id:
          type: string
          description: The database.
        status:
          type: string
          description: "The operation's status when the request was accepted, usually `submitting`."
        password_returned:
          type: boolean
          description: "False on a retry with the same Idempotency-Key; `password` is then null."
        password:
          type:
            - string
            - "null"
          description: "The generated admin password (32 letters and digits), in this response only. Store it securely now: it is never stored and cannot be read again. Null on a retry."
          examples: ["q8Vn3TzK1mXw7RbLc4Hs9JdP2fYa6GeU"]
    DiskOffering:
      type: object
      required:
        - slug
        - name
        - custom_size
        - currency
        - hourly_price_minor
      properties:
        slug:
          type: string
          description: "Use this in `disk_offering_slug`."
        name:
          type: string
          description: "Its name for display."
        size_gb:
          type:
            - integer
            - "null"
          description: "Fixed size; null for a custom-size offering."
        custom_size:
          type: boolean
          description: "When true, send `size_gb` on create; the price is then per GB."
        storage_type:
          type:
            - string
            - "null"
          description: "The storage class, e.g. `shared` or `local`; null when not stated."
        currency:
          type: string
          description: "`NGN` or `USD`: your organization's currency."
        hourly_price_minor:
          type: integer
          format: int64
          description: "Price per hour in minor units of `currency`: for the whole volume, or per GB for a custom-size offering."
    DiskOfferingList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/DiskOffering"
        next_cursor:
          type:
            - string
            - "null"
          description: "Always null: this list is returned whole."
    Failure:
      type:
        - object
        - "null"
      description: "Why it failed, when it did; otherwise null."
      required:
        - code
        - reason
      properties:
        code:
          type: string
          description: "A stable failure code to branch on, e.g. `INSTANCE_BUSY`. Provisioning failures use `PROVISIONING_CAPACITY_UNAVAILABLE` (no capacity in the zone right now: try another plan or zone, or try later), `PROVISIONING_LIMIT_EXCEEDED` (an account limit was reached: delete unused resources or contact support), `PROVISIONING_ADDRESSES_EXHAUSTED` (no free addresses in the zone), `PROVISIONING_IMAGE_UNAVAILABLE` (the image is not ready), `PROVISIONING_RESOURCE_BUSY` (the resource was busy: try again once it settles), `PROVISIONING_RESOURCE_CONFLICT`, `PROVISIONING_RESOURCE_NOT_FOUND`, `PROVISIONING_INVALID_REQUEST`, `PROVISIONING_UNAVAILABLE` (temporarily unavailable: retry), `PROVISIONING_INTERNAL_ERROR` (a fault on our side: our team is alerted, and retrying will not help) and `PROVISIONING_FAILED` (anything else)."
          examples: ["INSTANCE_BUSY"]
        reason:
          type: string
          description: "A readable sentence saying what went wrong and what to do; safe to show to your users. Its wording may change: branch on `code`."
    FirewallRule:
      type: object
      required:
        - id
        - subnet_id
        - number
        - direction
        - protocol
        - cidr
        - action
        - desired_state
        - observed_state
      properties:
        id:
          type: string
          description: "e.g. `aclr_...`."
        subnet_id:
          type: string
          description: "The subnet the rule applies to, as a whole."
        number:
          type: integer
          description: "Evaluation order, 100-9999; lowest first."
          minimum: 100
          maximum: 9999
        direction:
          type: string
          description: "`ingress` (into the subnet) or `egress` (out of it)."
          enum:
            - ingress
            - egress
        protocol:
          type: string
          description: "`tcp`, `udp`, `icmp` or `all`."
          enum:
            - tcp
            - udp
            - icmp
            - all
        port_start:
          type:
            - integer
            - "null"
          description: "First port, for `tcp`/`udp`; null otherwise."
        port_end:
          type:
            - integer
            - "null"
          description: "Last port, for `tcp`/`udp`; null otherwise."
        icmp_type:
          type:
            - integer
            - "null"
          description: "ICMP type, for `icmp`; null for every type."
        icmp_code:
          type:
            - integer
            - "null"
          description: "ICMP code, for `icmp`; null for every code."
        cidr:
          type: string
          description: "Source (ingress) or destination (egress), e.g. `0.0.0.0/0`."
        action:
          type: string
          description: "`allow` or `deny`."
          enum:
            - allow
            - deny
        desired_state:
          type: string
          description: "What you asked for: `present`, or `deleted` once you deleted it."
          enum:
            - present
            - deleted
        observed_state:
          type: string
          description: "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."
          enum:
            - pending
            - provisioning
            - active
            - deleting
            - deleted
            - failed
            - unknown
        created_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it was created (RFC 3339, UTC)."
          readOnly: true
        updated_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it last changed (RFC 3339, UTC)."
          readOnly: true
    FirewallRuleFields:
      type: object
      required:
        - number
        - direction
        - protocol
        - cidr
        - action
      properties:
        number:
          type: integer
          description: "Evaluation order, 100-9999; lowest first."
          minimum: 100
          maximum: 9999
        direction:
          type: string
          description: "`ingress` (into the subnet) or `egress` (out of it)."
          enum:
            - ingress
            - egress
        protocol:
          type: string
          description: "`tcp`, `udp`, `icmp` or `all`."
          enum:
            - tcp
            - udp
            - icmp
            - all
        port_start:
          type:
            - integer
            - "null"
          description: "First port, for `tcp`/`udp`; null otherwise."
        port_end:
          type:
            - integer
            - "null"
          description: "Last port, for `tcp`/`udp`; null otherwise."
        icmp_type:
          type:
            - integer
            - "null"
          description: "ICMP type, for `icmp`; null for every type."
        icmp_code:
          type:
            - integer
            - "null"
          description: "ICMP code, for `icmp`; null for every code."
        cidr:
          type: string
          description: "Source (ingress) or destination (egress), e.g. `0.0.0.0/0`."
        action:
          type: string
          description: "`allow` or `deny`."
          enum:
            - allow
            - deny
    FirewallRuleList:
      type: object
      required:
        - data
        - next_cursor
        - system_rules
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/FirewallRule"
        next_cursor:
          type:
            - string
            - "null"
          description: Always null.
        system_rules:
          type: array
          description: "The platform's own rules around yours. They always apply and cannot be changed."
          items:
            $ref: "#/components/schemas/FirewallRuleFields"
    GeneratedSSHKey:
      allOf:
        - $ref: "#/components/schemas/SSHKey"
        - type: object
          required:
            - private_key
          properties:
            private_key:
              type: string
              description: "The private key, in this response only. It is never stored and cannot be fetched again."
    Image:
      type: object
      required:
        - id
        - slug
        - name
        - version
        - zones
      properties:
        id:
          type: string
          description: "The image's id."
          readOnly: true
        slug:
          type: string
          description: "Use this in `image_slug`."
        name:
          type: string
          description: "Its name for display, e.g. `Ubuntu`."
        version:
          type: string
          description: "Its version, e.g. `24.04`."
        zones:
          type: array
          description: Zones this image can be deployed in.
          items:
            type: string
        encrypted:
          type: boolean
          description: "True for an encrypted image: the instance's root disk is encrypted on our storage, with its own key, and the instance needs our key servers to start. Encrypted images are listed only for organizations they are enabled for."
        encrypted_variant_of:
          type:
            - string
            - "null"
          description: "On an encrypted image, the slug of the plain image it is the encrypted version of (e.g. `ubuntu-24-04` for `ubuntu-24-04-encrypted`); null otherwise."
    ImageList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Image"
        next_cursor:
          type:
            - string
            - "null"
          description: "Always null: this list is returned whole."
    Instance:
      type: object
      required:
        - id
        - name
        - plan_id
        - image_id
        - desired_state
        - observed_state
        - tags
      properties:
        id:
          type: string
          description: "e.g. `vm_06gf...`."
        name:
          type: string
          description: "Its name, unique among your instances."
          examples: ["web-1"]
        plan_id:
          type: string
          description: "The plan's id."
        plan_slug:
          type:
            - string
            - "null"
          description: "The plan's slug, as `plan_slug` takes it (e.g. `starter`); null if the plan has since been retired."
        image_id:
          type: string
          description: "The image's id."
        image_slug:
          type:
            - string
            - "null"
          description: "The image's slug, as `image_slug` takes it (e.g. `ubuntu-24-04`); null if the image has since been retired."
        encrypted:
          type: boolean
          description: "True when the instance runs an encrypted image: its root disk is encrypted on our storage, with its own key."
        region:
          type:
            - string
            - "null"
          description: "e.g. `af-abj`."
        zone:
          type:
            - string
            - "null"
          description: "e.g. `af-abj-1`."
        network_id:
          type:
            - string
            - "null"
          description: "The VPC, for an instance in one."
        subnet_id:
          type:
            - string
            - "null"
          description: "Its VPC subnet; null for a standard instance."
        security_group_id:
          type:
            - string
            - "null"
          description: "The security group, for a standard instance."
        public_ipv4:
          type:
            - string
            - "null"
          description: "The instance's own public address (standard instances). A VPC instance is reached through a public IP: see `GET /public/v1/public-ips`."
        private_ipv4:
          type:
            - string
            - "null"
          description: "Its private address: in its VPC subnet, or on the zone's guest network."
        private_network_state:
          type: string
          description: "Its interface on the zone's private database network: `none`, `attaching`, `attached` or `detaching`. See *Add a private database network interface*."
          enum:
            - none
            - attaching
            - attached
            - detaching
        deletion_protection:
          type: boolean
          description: Platform VM deletion is refused while true.
        private_network_ip:
          type:
            - string
            - "null"
          description: "That interface's address once attached (kept while it is being removed); null otherwise. Allow it on a managed database as a `/32` access rule."
          examples: ["10.250.3.17"]
        desired_state:
          type: string
          description: "What you asked for: `running`, `stopped` or `deleted`."
          enum:
            - running
            - stopped
            - deleted
        observed_state:
          type: string
          description: "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."
          enum:
            - pending
            - provisioning
            - running
            - stopping
            - stopped
            - deleting
            - deleted
            - failed
            - unknown
        spec:
          type:
            - object
            - "null"
          description: The capacity this instance was created with.
          required:
            - vcpu
            - memory_mb
            - disk_gb
          properties:
            vcpu:
              type: integer
            memory_mb:
              type: integer
            disk_gb:
              type: integer
        tags:
          type: object
          description: "Your own labels, as sent at create."
          additionalProperties:
            type: string
        failure:
          $ref: "#/components/schemas/Failure"
          description: "Why the last change failed, when it did; otherwise null."
        created_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it was created (RFC 3339, UTC)."
          readOnly: true
        updated_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it last changed (RFC 3339, UTC)."
          readOnly: true
    InstanceList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Instance"
        next_cursor:
          type:
            - string
            - "null"
          description: "Pass as `cursor` for the next page; null on the last page."
    InstanceOrder:
      type: object
      required:
        - id
        - instance_id
        - status
        - amount_minor
        - currency
      properties:
        id:
          type: string
          description: "e.g. `ord_...`."
        instance_id:
          type: string
          description: "The id the instance has, or will have once provisioned."
        status:
          type: string
          description: "`awaiting_payment`, then `paid`, `provisioning` and `provisioned` (final: the instance exists). `payment_failed` and `failed` are final too; any payment taken is returned to your credit."
          enum:
            - awaiting_payment
            - paid
            - provisioning
            - provisioned
            - payment_failed
            - failed
        operation_id:
          type:
            - string
            - "null"
          description: "The instance's create operation, set once provisioning starts."
        failure_code:
          type:
            - string
            - "null"
          description: "Why the order failed, e.g. `insufficient_credit`, `card_declined`, `payment_expired`; null otherwise. `organization_deleted`: the order was cancelled because its organization was deleted; any payment taken is returned to credit."
        amount_minor:
          type: integer
          format: int64
          description: "The initial payment in minor units of `currency`."
        currency:
          type: string
          description: "`NGN` or `USD`."
          enum:
            - NGN
            - USD
        created_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it was created (RFC 3339, UTC)."
          readOnly: true
        updated_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it last changed (RFC 3339, UTC)."
          readOnly: true
    InstanceOrderList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/InstanceOrder"
        next_cursor:
          type:
            - string
            - "null"
          description: "Pass as `cursor` for the next page; null on the last page."
    Me:
      type: object
      required:
        - organization_id
        - project_id
        - api_key
      properties:
        organization_id:
          type: string
          description: "The organization the key belongs to."
          examples: ["org_06gk3x9tq7m2v8r4y1c5d0zp8w"]
        project_id:
          type: string
          description: "The organization's project; every resource belongs to it."
        api_key:
          type: object
          required:
            - id
            - name
            - kind
            - client_name
            - scopes
            - expires_at
          properties:
            id:
              type: string
            name:
              type: string
            kind:
              type: string
              description: "`standard` for a key made under Organization › API keys; `ai_app` for the key an AI app was given when a member connected it."
              enum:
                - standard
                - ai_app
            client_name:
              type:
                - string
                - "null"
              description: "The AI app's name as it introduced itself (not verified by Pantech), e.g. `Research assistant`. Null for a standard key."
              examples: ["Research assistant"]
            scopes:
              type: array
              description: "`delete` appears only on an AI app's key, together with `write`."
              items:
                type: string
                enum:
                  - read
                  - write
                  - delete
            expires_at:
              type:
                - string
                - "null"
              format: date-time
    MetricsSeries:
      type: object
      required:
        - interval
        - available
        - points
      properties:
        interval:
          type: string
          description: "The window the points cover: `1h`, `24h`, `7d` or `30d`."
          enum:
            - "1h"
            - "24h"
            - "7d"
            - "30d"
        available:
          type: boolean
          description: "False when no samples exist yet, for example just after the instance was created."
        sampled_at:
          type:
            - string
            - "null"
          description: "When the newest sample was taken; null when none."
        points:
          type: array
          description: "One point per bucket, oldest first. A null value means no sample in that bucket."
          items:
            type: object
            required:
              - bucket_start
              - bucket_end
            properties:
              bucket_start:
                type: string
                format: date-time
              bucket_end:
                type: string
                format: date-time
              cpu_percent:
                type:
                  - number
                  - "null"
              memory_percent:
                type:
                  - number
                  - "null"
              network_in_bytes:
                type:
                  - integer
                  - "null"
              network_out_bytes:
                type:
                  - integer
                  - "null"
              disk_read_peak_bytes_per_second:
                type:
                  - integer
                  - "null"
              disk_write_peak_bytes_per_second:
                type:
                  - integer
                  - "null"
    MonthlyQuote:
      type: object
      required:
        - currency
        - monthly_minor
        - nominal_month_hours
      properties:
        currency:
          type: string
          description: "`NGN` or `USD`."
        monthly_minor:
          type: integer
          format: int64
          description: "Price for a full month in minor units of `currency`."
        nominal_month_hours:
          type: integer
          description: "Hours in the month the price assumes: 730."
    Network:
      type: object
      description: "A VPC: a private network of your own."
      required:
        - id
        - name
        - cidr
        - desired_state
        - observed_state
      properties:
        id:
          type: string
          description: "e.g. `net_...`."
        name:
          type: string
          description: "Its name, unique among your VPC networks."
          examples: ["prod"]
        cidr:
          type: string
          description: "e.g. `10.0.0.0/16`."
        region:
          type:
            - string
            - "null"
          description: "e.g. `af-abj`."
        zone:
          type:
            - string
            - "null"
          description: "e.g. `af-abj-2`."
        instance_count:
          type:
            - integer
            - "null"
          description: "Instances in this VPC; null when it could not be counted."
        desired_state:
          type: string
          description: "What you asked for: `present`, or `deleted` once you deleted it."
          enum:
            - present
            - deleted
        observed_state:
          type: string
          description: "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."
          enum:
            - pending
            - provisioning
            - active
            - deleting
            - deleted
            - failed
            - unknown
        created_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it was created (RFC 3339, UTC)."
          readOnly: true
        updated_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it last changed (RFC 3339, UTC)."
          readOnly: true
    NetworkList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Network"
        next_cursor:
          type:
            - string
            - "null"
          description: "Pass as `cursor` for the next page; null on the last page."
    Operation:
      type: object
      required:
        - id
        - resource_type
        - resource_id
        - kind
        - status
      properties:
        id:
          type: string
          description: "e.g. `op_...`."
        resource_type:
          type: string
          description: "e.g. `instance`, `volume`, `network`, `security_group`, `database`."
          enum:
            - instance
            - network
            - subnet
            - security_group
            - snapshot
            - volume
            - ssh_access_grant
            - relay_vm
            - network_acl_rule
            - public_ip
            - port_forwarding_rule
            - database
        resource_id:
          type: string
          description: "The resource the operation acts on, e.g. a `vm_…`, `db_…` or `snap_…` id. For a create, the new resource's id."
        kind:
          type: string
          description: "What it does, e.g. `create_instance`, `stop_instance`. For a database: `create_database`, `start_database`, `stop_database`, `delete_database`, `apply_database_access_rules` (an access-rule change, attaching or detaching security groups, or the automatic re-apply after an attached group's rules changed), `change_database_password`, `reset_database_password`, `resize_database_storage`. New kinds may be added."
        status:
          type: string
          description: "`submitting` and `submitted` while it runs; `succeeded` and `failed` are final."
          enum:
            - submitting
            - submitted
            - succeeded
            - failed
        failure:
          $ref: "#/components/schemas/Failure"
          description: "Why it failed, when `status` is `failed`; otherwise null."
        created_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it was created (RFC 3339, UTC)."
          readOnly: true
        updated_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it last changed (RFC 3339, UTC)."
          readOnly: true
    OptionalQuote:
      type: object
      required:
        - priced
        - nominal_month_hours
      properties:
        priced:
          type: boolean
          description: "False when no price applies; `unpriced_reason` then says why."
        unpriced_reason:
          type:
            - string
            - "null"
          description: "Why no price applies; null when priced."
        currency:
          type:
            - string
            - "null"
          description: "`NGN` or `USD`; null when not priced."
        monthly_minor:
          type:
            - integer
            - "null"
          description: "Price for a full month in minor units; null when not priced."
        nominal_month_hours:
          type: integer
          description: "Hours in the month the price assumes: 730."
    OrderAccepted:
      type: object
      description: "An instance is bought through an order: the initial payment is taken first (from credit, else the default card), then the instance is provisioned."
      required:
        - order_id
        - instance_id
        - status
        - amount_minor
        - currency
      properties:
        order_id:
          type: string
          description: "Follow it at `GET /public/v1/instance-orders/{order_id}` (also the `Location` header)."
        instance_id:
          type: string
          description: The id the instance will have.
        status:
          type: string
          description: "The order's status now, usually `awaiting_payment`. See `InstanceOrder.status`."
          enum:
            - awaiting_payment
            - paid
            - provisioning
            - provisioned
            - payment_failed
            - failed
        operation_id:
          type:
            - string
            - "null"
          description: Set once provisioning starts.
        failure_code:
          type:
            - string
            - "null"
          description: "Null for a new order; on a retry, the order's `failure_code` if it has failed since."
        amount_minor:
          type: integer
          description: "The initial payment, in minor units (kobo or cents)."
          format: int64
        currency:
          type: string
          description: "`NGN` or `USD`."
          enum:
            - NGN
            - USD
    Plan:
      type: object
      required:
        - id
        - slug
        - name
        - vcpu
        - memory_mb
        - disk_gb
      properties:
        id:
          type: string
          description: "The plan's id."
          readOnly: true
        slug:
          type: string
          description: "Use this in `plan_slug`."
        name:
          type: string
          description: "Its name for display."
        vcpu:
          type: integer
          description: "Virtual CPUs."
          examples: [2]
        memory_mb:
          type: integer
          description: "Memory in MB."
          examples: [4096]
        disk_gb:
          type: integer
          description: "Root disk in GB; for a database, the smallest data disk."
          examples: [80]
        price:
          type:
            - object
            - "null"
          description: "In the organization's currency; null when it cannot be priced."
          required:
            - currency
            - monthly_estimate_minor
            - storage_floor_minor
            - initial_payment_minor
          properties:
            currency:
              type: string
            monthly_estimate_minor:
              type: integer
              description: A full month running.
              format: int64
            storage_floor_minor:
              type: integer
              description: "A full month stopped (storage only)."
              format: int64
            initial_payment_minor:
              type: integer
              description: Taken when the instance is ordered.
              format: int64
        unpriced_reason:
          type:
            - string
            - "null"
          description: "Why `price` is null; null when priced."
    PlanList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Plan"
        next_cursor:
          type:
            - string
            - "null"
          description: "Always null: this list is returned whole."
    PortForwardingRule:
      type: object
      required:
        - id
        - public_ip_id
        - instance_id
        - protocol
        - public_port_start
        - public_port_end
        - private_port_start
        - private_port_end
        - desired_state
        - observed_state
      properties:
        id:
          type: string
          description: "e.g. `pfr_...`."
        public_ip_id:
          type: string
        instance_id:
          type: string
        instance_name:
          type:
            - string
            - "null"
          description: "The target instance's name; null when it is gone."
        protocol:
          type: string
          description: "`tcp` or `udp`."
          enum:
            - tcp
            - udp
        public_port_start:
          type: integer
        public_port_end:
          type: integer
        private_port_start:
          type: integer
        private_port_end:
          type: integer
        desired_state:
          type: string
          description: "What you asked for: `present`, or `deleted` once you deleted it."
          enum:
            - present
            - deleted
        observed_state:
          type: string
          description: "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."
          enum:
            - pending
            - provisioning
            - active
            - deleting
            - deleted
            - failed
            - unknown
        created_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it was created (RFC 3339, UTC)."
          readOnly: true
        updated_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it last changed (RFC 3339, UTC)."
          readOnly: true
    PortForwardingRuleList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PortForwardingRule"
        next_cursor:
          type:
            - string
            - "null"
          description: "Always null: this list is returned whole."
    PricingEstimate:
      type: object
      required:
        - currency
        - nominal_month_hours
        - machines
        - pricing_complete
        - priced_subtotal_minor
      properties:
        currency:
          type: string
          description: "`NGN` or `USD`, as requested."
        nominal_month_hours:
          type: integer
          description: "Hours in the month prices assume: 730."
        machines:
          type: array
          items:
            type: object
            required:
              - plan_slug
              - quantity
              - running_hours
              - priced
            properties:
              plan_slug:
                type: string
              quantity:
                type: integer
              running_hours:
                type: integer
              priced:
                type: boolean
              unpriced_reason:
                type:
                  - string
                  - "null"
              unit_monthly_estimate_minor:
                type:
                  - integer
                  - "null"
              total_monthly_estimate_minor:
                type:
                  - integer
                  - "null"
        extra_storage_gb:
          type: object
          required:
            - requested
            - priced
          properties:
            requested:
              type: integer
            priced:
              type: boolean
            unpriced_reason:
              type:
                - string
                - "null"
            amount_minor:
              type:
                - integer
                - "null"
        networks:
          type: object
          required:
            - requested
            - priced
          properties:
            requested:
              type: integer
            priced:
              type: boolean
            unpriced_reason:
              type:
                - string
                - "null"
            amount_minor:
              type:
                - integer
                - "null"
        snapshot_gb:
          type: object
          required:
            - requested
            - priced
          properties:
            requested:
              type: integer
            priced:
              type: boolean
            unpriced_reason:
              type:
                - string
                - "null"
            amount_minor:
              type:
                - integer
                - "null"
        pricing_complete:
          type: boolean
          description: False when any line could not be priced.
        priced_subtotal_minor:
          type: integer
          format: int64
          description: "The sum of every priced line, in minor units. Lines that could not be priced are left out."
    PricingEstimateRequest:
      type: object
      required:
        - currency
      properties:
        currency:
          type: string
          description: "The currency to price in: `NGN` or `USD`."
          enum:
            - NGN
            - USD
        machines:
          type: array
          maxItems: 50
          description: "Instances to price: up to 50 lines, each a plan, a quantity and running hours per month."
          items:
            type: object
            required:
              - plan_slug
              - quantity
              - running_hours
            properties:
              plan_slug:
                type: string
              quantity:
                type: integer
                minimum: 1
              running_hours:
                type: integer
                minimum: 0
                maximum: 730
              placement:
                type: string
                enum:
                  - standard
                  - vpc
        extra_storage_gb:
          type: integer
          description: "Extra volume storage in GB."
          minimum: 0
        network_count:
          type: integer
          description: "Number of VPC networks."
          minimum: 0
        snapshot_gb:
          type: integer
          description: "Snapshot storage in GB."
          minimum: 0
    Problem:
      type: object
      description: "Every error is an RFC 9457 problem document (`application/problem+json`)."
      required:
        - type
        - title
        - status
        - code
        - detail
        - request_id
      properties:
        type:
          type: string
          description: A URI naming the problem class.
          format: uri
        title:
          type: string
          description: "A short summary of the problem class."
        status:
          type: integer
          description: The HTTP status code.
        code:
          type: string
          description: "Stable machine-readable error code, e.g. `INSUFFICIENT_CREDIT`. Branch on this, never on `title` or `detail`."
        detail:
          type: string
          description: "What went wrong in this case and what to do next, in plain language. Safe to show to your users. Its wording may change: branch on `code`."
        request_id:
          type: string
          description: "This request's id (also in the `X-Request-Id` response header). Quote it to support."
        errors:
          type: array
          description: "Present on `VALIDATION_FAILED`: one entry per invalid field."
          items:
            type: object
            required:
              - field
              - code
              - message
            properties:
              field:
                type: string
                description: "The request field, e.g. `plan_slug`."
              code:
                type: string
                description: "Stable code for this field's problem, e.g. `REQUIRED`."
              message:
                type: string
                description: What the field must be.
    AttachPublicIPRequest:
      type: object
      required:
        - instance_id
      properties:
        instance_id:
          type: string
          description: "An instance in the address's private network."
          examples: ["vm_06gk3x9tq7m2v8r4y1c5d0zp8w"]
    PublicIP:
      type: object
      required:
        - id
        - network_id
        - purpose
        - desired_state
        - observed_state
        - in_sync
      properties:
        id:
          type: string
          description: "e.g. `pip_...`."
        network_id:
          type: string
        network_name:
          type:
            - string
            - "null"
          description: "The VPC's name."
        purpose:
          type: string
          description: "`static_nat` maps every port to one instance; `port_forwarding` carries per-port rules."
          enum:
            - static_nat
            - port_forwarding
            - load_balancer
        address:
          type:
            - string
            - "null"
          description: "The public IPv4 address; null until allocated."
        instance_id:
          type:
            - string
            - "null"
          description: "static_nat: the instance it maps to; null while it is detached (still yours, and billed once)."
        instance_name:
          type:
            - string
            - "null"
          description: "The mapped instance's name; null when none."
        region:
          type:
            - string
            - "null"
        zone:
          type:
            - string
            - "null"
        desired_state:
          type: string
          description: "What you asked for: `present`, or `deleted` once you deleted it."
          enum:
            - present
            - deleted
        observed_state:
          type: string
          description: "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."
          enum:
            - pending
            - provisioning
            - active
            - deleting
            - deleted
            - failed
            - unknown
        in_sync:
          type: boolean
          description: "False while an attach or detach has not reached the address yet."
        created_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it was created (RFC 3339, UTC)."
          readOnly: true
        updated_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it last changed (RFC 3339, UTC)."
          readOnly: true
    PublicIPList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PublicIP"
        next_cursor:
          type:
            - string
            - "null"
          description: "Always null: this list is returned whole."
    PutSnapshotScheduleRequest:
      type: object
      required:
        - frequency
      properties:
        frequency:
          type: string
          description: "How often: `daily`, `weekly` or `monthly`."
          enum:
            - daily
            - weekly
            - monthly
        retention_count:
          type: integer
          minimum: 1
          maximum: 168
          default: 7
          description: "How many successful automatic snapshots to keep, 1 to 168; older ones are deleted. Defaults to 7."
        enabled:
          type: boolean
          default: true
          description: "False pauses the schedule without forgetting it. Defaults to true."
    RebuildRequest:
      type: object
      properties:
        image_slug:
          type: string
          description: "The image to install, from `GET /public/v1/images`. Leave out to reinstall the current image."
          examples: ["ubuntu-24-04"]
    Region:
      type: object
      required:
        - code
        - name
        - placements
      properties:
        code:
          type: string
          description: "e.g. `af-abj`."
        name:
          type: string
          description: "Its name for display."
        placements:
          type: array
          items:
            type: object
            required:
              - kind
              - zone
              - available
            properties:
              kind:
                type: string
                description: "`standard` instances or `vpc` networks."
                enum:
                  - standard
                  - vpc
              zone:
                type: string
              available:
                type: boolean
              unavailable_reason:
                type:
                  - string
                  - "null"
              private_network_cidr:
                type:
                  - string
                  - "null"
                description: "The range of the zone's private database network, e.g. `10.250.0.0/20`, when instances there can add a private network interface (`POST /public/v1/instances/{instance_id}/private-network`); null otherwise, and in every VPC zone. Allow this range nowhere in the security group of an instance with that interface."
                examples: ["10.250.0.0/20"]
    RegionList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Region"
        next_cursor:
          type:
            - string
            - "null"
          description: "Always null: this list is returned whole."
    RenameRequest:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          description: "The new name: 1 to 255 characters of lowercase letters a-z, digits 0-9 and hyphens (-), starting and ending with a letter or digit, unique among your instances. Anything else is refused with 422 INVALID_INSTANCE_NAME."
          minLength: 1
          maxLength: 255
          pattern: "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$"
          examples: ["web-2"]
    ReplaceDatabaseAccessRulesRequest:
      type: object
      required:
        - rules
      properties:
        rules:
          type: array
          maxItems: 50
          description: "The complete new allow-list, at most 50 rules with no range twice. `[]` removes every rule."
          items:
            $ref: "#/components/schemas/DatabaseAccessRuleRequest"
    ReplaceSecurityGroupRulesRequest:
      type: object
      required:
        - rules
      properties:
        rules:
          type: array
          description: "The complete new rule list; at least one rule. It replaces every existing rule."
          items:
            $ref: "#/components/schemas/SecurityGroupRule"
    ChangeSSHKeyRequest:
      type: object
      required:
        - ssh_key_id
      properties:
        ssh_key_id:
          type: string
          description: "The SSH key the instance should accept from now on, from `GET /public/v1/ssh-keys`. It replaces the instance's current key."
    ResizeRequest:
      type: object
      required:
        - plan_slug
      properties:
        plan_slug:
          type: string
          description: "A bigger plan than the instance's current one, from `GET /public/v1/plans`. Instances only grow."
    ResizeVolumeRequest:
      type: object
      required:
        - disk_offering_slug
      properties:
        disk_offering_slug:
          type: string
          description: "The volume type after the resize, from `GET /public/v1/disk-offerings`."
        size_gb:
          type: integer
          description: "The new size in GB, for a custom-size offering: larger than the current size. Volumes only grow."
    RestoreRequest:
      type: object
      required:
        - snapshot_id
        - confirm_name
      properties:
        snapshot_id:
          type: string
          description: "A snapshot of this instance's root disk."
          examples: ["snap_06gk3x9tq7m2v8r4y1c5d0zp8w"]
        confirm_name:
          type: string
          description: "The instance's current name, exactly, to confirm: everything written since the snapshot is lost."
          examples: ["web-1"]
    RestoreSnapshotRequest:
      type: object
      required:
        - name
        - disk_offering_slug
      properties:
        name:
          type: string
          description: "Name of the new volume, 1 to 255 characters."
          examples: ["data-restored"]
        disk_offering_slug:
          type: string
          description: "The new volume's type, from `GET /public/v1/disk-offerings`."
        size_gb:
          type: integer
          description: "For a custom-size offering: at least the snapshot's size in GB."
    SSHAccessGrant:
      type: object
      required:
        - id
        - instance_id
        - status
      properties:
        id:
          type: string
          description: "e.g. `sshg_...`."
        instance_id:
          type: string
        status:
          type: string
          description: "`pending` while the port is being opened, `active` once you can connect, then `revoking`, `revoked`, `expired` or `failed`."
          enum:
            - pending
            - active
            - revoking
            - revoked
            - expired
            - failed
        host:
          type:
            - string
            - "null"
          description: "Connect here once `active`."
        port:
          type:
            - integer
            - "null"
          description: "The port to connect to once `active`, usually 22."
        expires_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When the port closes by itself, 15 minutes after it opened."
        revoked_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it was revoked; null otherwise."
        created_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it was created (RFC 3339, UTC)."
          readOnly: true
    SSHKey:
      type: object
      required:
        - id
        - name
        - fingerprint
        - public_key
      properties:
        id:
          type: string
          description: "e.g. `sshk_...`."
        name:
          type: string
          description: "Its name, unique among your SSH keys."
        fingerprint:
          type: string
          description: "The key's SHA256 fingerprint."
        public_key:
          type: string
          description: "The OpenSSH public key."
        created_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it was created (RFC 3339, UTC)."
          readOnly: true
    SSHKeyList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/SSHKey"
        next_cursor:
          type:
            - string
            - "null"
          description: "Always null: this list is returned whole."
    SecurityGroup:
      type: object
      description: A reusable firewall for standard instances.
      required:
        - id
        - name
        - rules
        - desired_state
        - observed_state
      properties:
        id:
          type: string
          description: "e.g. `sg_...`."
        name:
          type: string
          description: "Its name, unique among your live security groups. The organization's default group is called `default`."
          examples: ["web"]
        rules:
          type: array
          description: "Its rules. Inbound traffic no rule allows is dropped."
          items:
            $ref: "#/components/schemas/SecurityGroupRule"
        desired_state:
          type: string
          description: "What you asked for: `present`, or `deleted` once you deleted it."
          enum:
            - present
            - deleted
        observed_state:
          type: string
          description: "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."
          enum:
            - pending
            - provisioning
            - active
            - deleting
            - deleted
            - failed
            - unknown
        created_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it was created (RFC 3339, UTC)."
          readOnly: true
        updated_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it last changed (RFC 3339, UTC)."
          readOnly: true
    SecurityGroupList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/SecurityGroup"
        next_cursor:
          type:
            - string
            - "null"
          description: "Pass as `cursor` for the next page; null on the last page."
    SecurityGroupRule:
      type: object
      required:
        - direction
        - protocol
        - cidr
      properties:
        direction:
          type: string
          description: "`ingress` (into the instance) or `egress` (out of it)."
          enum:
            - ingress
            - egress
        protocol:
          type: string
          description: "`tcp`, `udp`, `icmp` or `all`."
          enum:
            - tcp
            - udp
            - icmp
            - all
        port_range:
          type: string
          description: "For `tcp`/`udp`: one port (`22`) or an inclusive range (`8000-8100`). Empty or left out for `icmp` and `all`, meaning every port."
          examples: ["22"]
        cidr:
          type: string
          description: "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."
          examples: ["203.0.113.4/32"]
    Snapshot:
      type: object
      required:
        - id
        - name
        - trigger
        - size_bytes
        - desired_state
        - observed_state
      properties:
        id:
          type: string
          description: "e.g. `snap_...`."
        name:
          type: string
          description: "Its name, unique among its source's snapshots."
          examples: ["pre-upgrade"]
        instance_id:
          type:
            - string
            - "null"
          description: "Set for a snapshot of an instance's root disk."
        source_instance_name:
          type:
            - string
            - "null"
          description: "The source instance's name; null when it is gone or not an instance snapshot."
        volume_id:
          type:
            - string
            - "null"
          description: Set for a snapshot of a volume. Exactly one of instance_id, volume_id and database_id is set.
        volume_name:
          type:
            - string
            - "null"
          description: "The source volume's name; null when it is gone or not a volume snapshot."
        database_id:
          type:
            - string
            - "null"
          description: "Set for a snapshot of a managed database's data disk. Exactly one of `instance_id`, `volume_id` and `database_id` is set."
        region:
          type:
            - string
            - "null"
          description: "The source's region, e.g. `af-abj`."
        trigger:
          type: string
          description: "`automatic` when taken by a snapshot schedule."
          enum:
            - manual
            - automatic
        size_bytes:
          type: integer
          description: "Storage used; 0 until complete."
          format: int64
        desired_state:
          type: string
          description: "What you asked for: `present`, or `deleted` once you deleted it."
          enum:
            - present
            - deleted
        observed_state:
          type: string
          description: "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."
          enum:
            - pending
            - provisioning
            - active
            - deleting
            - deleted
            - failed
            - unknown
        completed_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When the snapshot finished; null until then."
        created_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it was created (RFC 3339, UTC)."
          readOnly: true
        updated_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it last changed (RFC 3339, UTC)."
          readOnly: true
    SnapshotList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Snapshot"
        next_cursor:
          type:
            - string
            - "null"
          description: "Pass as `cursor` for the next page; null on the last page."
    SnapshotSchedule:
      type: object
      required:
        - frequency
        - retention_count
        - enabled
      properties:
        instance_id:
          type:
            - string
            - "null"
          description: "The instance whose root disk is snapshotted; null for a volume's schedule."
        volume_id:
          type:
            - string
            - "null"
          description: "The volume snapshotted; null for an instance's schedule."
        frequency:
          type: string
          description: "`daily`, `weekly` or `monthly`."
          enum:
            - daily
            - weekly
            - monthly
        retention_count:
          type: integer
          description: "Successful copies kept; older ones are deleted."
          minimum: 1
          maximum: 168
        enabled:
          type: boolean
          description: "False while paused."
        next_run_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When the next automatic snapshot is due; null while paused."
    Subnet:
      type: object
      required:
        - id
        - network_id
        - name
        - cidr
        - desired_state
        - observed_state
      properties:
        id:
          type: string
          description: "e.g. `snet_...`."
        network_id:
          type: string
        name:
          type: string
          description: "Its name."
          examples: ["app"]
        cidr:
          type: string
          description: "Inside the VPC's CIDR."
        region:
          type:
            - string
            - "null"
          description: "e.g. `af-abj`."
        zone:
          type:
            - string
            - "null"
          description: "e.g. `af-abj-2`."
        instance_count:
          type:
            - integer
            - "null"
          description: "Instances in this subnet; null when they could not be counted."
        desired_state:
          type: string
          description: "What you asked for: `present`, or `deleted` once you deleted it."
          enum:
            - present
            - deleted
        observed_state:
          type: string
          description: "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."
          enum:
            - pending
            - provisioning
            - active
            - deleting
            - deleted
            - failed
            - unknown
        created_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it was created (RFC 3339, UTC)."
          readOnly: true
        updated_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it last changed (RFC 3339, UTC)."
          readOnly: true
    SubnetList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Subnet"
        next_cursor:
          type:
            - string
            - "null"
          description: "Pass as `cursor` for the next page; null on the last page."
    UsageByResource:
      type: object
      required:
        - currency
        - period_start
        - period_end
        - data
      properties:
        currency:
          type: string
        period_start:
          type: string
        period_end:
          type: string
        data:
          type: array
          items:
            type: object
            properties:
              resource_id:
                type: string
              resource_name:
                type: string
              resource_kind:
                type: string
              type:
                type: string
              days_active:
                type: integer
              window_days:
                type: integer
              rate_unit:
                type: string
              rate_unit_price_micros:
                type: integer
                format: int64
              cost_minor:
                type: integer
                format: int64
    CreateCreditPurchaseRequest:
      type: object
      additionalProperties: false
      required:
        - amount_minor
      properties:
        amount_minor:
          type: integer
          format: int64
          description: "How much credit to buy, in minor units of your organization's currency: kobo for NGN, cents for USD."
    Receipt:
      type: object
      description: "A receipt for money that moved on your account. Amounts are integers in minor units of `currency`."
      required:
        - id
        - number
        - kind
        - issuer_name
        - currency
        - amount_minor
        - issued_at
        - occurred_at
        - description
        - billed_to
        - facts
        - verification
        - transaction_id
        - pdf_bytes
      properties:
        id:
          type: string
          description: "e.g. `rcp_...`."
        number:
          type: string
          description: "`PTD-RCT-<year>-<number>`, sequential within the year."
        kind:
          type: string
          enum:
            - payment
            - invoice_payment
            - credit_purchase
            - wallet_debit
            - refund
          description: "`payment` a card payment, `invoice_payment` an invoice paid by card or from credit, `credit_purchase` a top-up, `wallet_debit` credit taken for a new resource, `refund` money returned to your credit."
        issuer_name:
          type: string
          description: "The Pantech company that issued it: `Pantech Dynamics, Inc.` for USD payments, `Pantech Dynamics Ltd` for NGN payments."
        currency:
          type: string
          enum:
            - NGN
            - USD
        amount_minor:
          type: integer
          format: int64
        issued_at:
          type: [string, "null"]
          format: date-time
          description: When the receipt was issued.
        occurred_at:
          type: [string, "null"]
          format: date-time
          description: When the money moved.
        description:
          type: string
          description: "What it was for, e.g. `Account credit top-up`."
        billed_to:
          type: string
          description: Your organization, as printed on the receipt.
        facts:
          type: array
          description: "The details printed on the receipt, e.g. the payment method (`Visa •••• 4242`) and the date."
          items:
            type: object
            required:
              - label
              - value
            properties:
              label:
                type: string
              value:
                type: string
        verification:
          type: string
          description: "The code printed on the receipt, which Pantech uses to confirm a copy is genuine."
        transaction_id:
          type: string
          description: "The account transaction the receipt is for, e.g. `ltx_...`."
        pdf_bytes:
          type: integer
          description: The size of the PDF.
    ReceiptList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Receipt"
        next_cursor:
          type:
            - string
            - "null"
          description: "Pass as `cursor` for the next page; null on the last page."
    CreditPurchase:
      type: object
      description: One credit top-up.
      required:
        - id
        - status
        - amount_minor
        - currency
        - method
        - provider
        - checkout_url
        - checkout_expires_at
        - failure_code
        - settled_at
        - created_at
        - receipt
      properties:
        id:
          type: string
          description: "e.g. `cpr_...`."
        status:
          type: string
          enum:
            - pending
            - settled
            - failed
            - expired
            - refunded
            - charged_back
          description: "`pending` until paid; `settled` once the credit is in your balance."
        amount_minor:
          type: integer
          format: int64
          description: "The credit bought, in minor units of `currency`."
        currency:
          type: string
          enum:
            - NGN
            - USD
        method:
          type: string
          enum:
            - checkout
            - saved_card
            - bank_transfer
          description: "`checkout` for every purchase made through the API. Purchases made in the console may show another method."
        provider:
          type: string
          description: "An identifier for the payment processor that takes the payment. Treat it as an opaque label: values can be added."
        checkout_url:
          type: [string, "null"]
          format: uri
          description: "The provider's hosted payment page. Present only while the purchase is `pending` and can still be paid."
        checkout_expires_at:
          type: [string, "null"]
          format: date-time
          description: When the payment link stops working, if the provider sets a time.
        failure_code:
          type: [string, "null"]
          description: "Why the payment failed, when `status` is `failed`."
        settled_at:
          type: [string, "null"]
          format: date-time
          description: "When the credit was added; null until then."
        created_at:
          type: [string, "null"]
          format: date-time
          description: "When it was created (RFC 3339, UTC)."
          readOnly: true
        receipt:
          type:
            - object
            - "null"
          description: "The receipt, once the purchase has settled and the receipt is issued (usually within a minute); null until then. Fetch it with `GET /public/v1/billing/receipts/{receipt_id}`."
          required:
            - id
            - number
          properties:
            id:
              type: string
              description: "e.g. `rcp_...`."
            number:
              type: string
              description: "e.g. `PTD-RCT-2026-000123`."
    CreditOutlook:
      type: object
      description: "Credit against the estimated next invoice. Amounts are integers in minor units of `currency`."
      required:
        - currency
        - available_minor
        - pending_minor
        - prepaid_minor
        - estimated_next_invoice_minor
        - next_invoice_at
        - standing
        - has_payment_method
      properties:
        currency:
          type: string
          enum:
            - NGN
            - USD
        available_minor:
          type: integer
          format: int64
          description: Credit you can spend now.
        pending_minor:
          type: integer
          format: int64
          description: "Credit purchases that haven't settled yet. Not spendable until they do."
        prepaid_minor:
          type: integer
          format: int64
          description: "Initial payments for new instances that haven't been used yet. They count toward your next invoice before credit does."
        estimated_next_invoice_minor:
          type: integer
          format: int64
          description: An estimate of your next invoice, including VAT.
        next_invoice_at:
          type: string
          format: date-time
          description: When the current billing period closes and the invoice is issued.
        standing:
          type: string
          description: "`covered` when credit and prepaid instance payments cover the estimate, `low` when they don't, `empty` when no credit is left. Buy credit with `POST /public/v1/billing/credit/purchases`."
          enum:
            - covered
            - low
            - empty
        has_payment_method:
          type: boolean
          description: Whether a default payment method will be charged for anything credit doesn't cover.
    UsageSummary:
      type: object
      required:
        - totals
        - entries
      properties:
        window_start:
          type:
            - string
            - "null"
          format: date-time
          description: "Start of the measured window; null when nothing was measured."
        window_end:
          type:
            - string
            - "null"
          format: date-time
          description: "End of the measured window; null when nothing was measured."
        totals:
          type: object
          properties:
            running_hours:
              type: number
            storage_gb_hours:
              type: number
            allocated_memory_mb_hours:
              type: number
            network_bytes_sent:
              type: number
            network_bytes_received:
              type: number
        entries:
          type: array
          items:
            type: object
            properties:
              resource_id:
                type:
                  - string
                  - "null"
              usage_type:
                type: integer
              usage:
                type: string
              period_start:
                type:
                  - string
                  - "null"
                format: date-time
              period_end:
                type:
                  - string
                  - "null"
                format: date-time
    Volume:
      type: object
      required:
        - id
        - name
        - size_gb
        - disk_offering_slug
        - desired_state
        - observed_state
      properties:
        id:
          type: string
          description: "e.g. `vol_...`."
        name:
          type: string
          description: "Its name."
          examples: ["data"]
        size_gb:
          type: integer
          description: "Its size in GB."
          examples: [100]
        disk_offering_slug:
          type: string
          description: "See `GET /public/v1/disk-offerings`."
        storage_type:
          type:
            - string
            - "null"
          description: "The storage class, e.g. `shared`; null when not stated."
        region:
          type:
            - string
            - "null"
        zone:
          type:
            - string
            - "null"
        attached_instance_id:
          type:
            - string
            - "null"
          description: "The instance it is attached to; null when detached."
        attached_instance_name:
          type:
            - string
            - "null"
          description: "That instance's name; null when detached."
        desired_instance_id:
          type:
            - string
            - "null"
          description: Where an attach in progress is moving it.
        mount_point:
          type:
            - string
            - "null"
          description: "Where it is mounted, when mounted at create; null otherwise."
        source_snapshot_id:
          type:
            - string
            - "null"
          description: Set when restored from a snapshot.
        monthly_cost:
          type:
            - object
            - "null"
          description: "What it costs per month at its size; null when it cannot be priced."
          required:
            - currency
            - amount_minor
          properties:
            currency:
              type: string
            amount_minor:
              type: integer
              format: int64
        desired_state:
          type: string
          description: "What you asked for: `present`, or `deleted` once you deleted it."
          enum:
            - present
            - deleted
        observed_state:
          type: string
          description: "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."
          enum:
            - pending
            - provisioning
            - active
            - deleting
            - deleted
            - failed
            - unknown
        encryption:
          type: object
          description: "What the platform can truthfully say about this volume's encryption. `status` is `none` for a plain volume, `requested` when you asked for encryption at creation and the disk has not been formatted yet, and `encrypted` once the platform has confirmed the disk was formatted with a key from the volume key service. `encrypted` is set only by the platform, never from a claim."
          required:
            - status
          properties:
            status:
              type: string
              enum:
                - none
                - requested
                - encrypted
        created_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it was created (RFC 3339, UTC)."
          readOnly: true
        updated_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it last changed (RFC 3339, UTC)."
          readOnly: true
    VolumeList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Volume"
        next_cursor:
          type:
            - string
            - "null"
          description: "Pass as `cursor` for the next page; null on the last page."
    Webhook:
      type: object
      description: "A URL Pantech sends your organization's events to."
      required:
        - id
        - url
        - description
        - events
        - enabled
        - disabled_reason
        - failing_since
        - created_at
        - updated_at
      properties:
        id:
          type: string
          description: "e.g. `whk_...`."
        url:
          type: string
          description: "Where events are sent: an `https` URL on the public internet."
          examples: ["https://example.com/hooks/pantech"]
        description:
          type: string
          description: "Your note about it; may be empty."
        events:
          type: array
          description: "What it subscribes to: event types, `<resource>.*`, or `*`."
          items:
            type: string
          examples: [["instance.*", "invoice.paid"]]
        enabled:
          type: boolean
          description: "Whether events are sent to it."
        disabled_reason:
          type:
            - string
            - "null"
          description: "Why it is disabled: `disabled_by_user`, or `failing` after three days of failed deliveries. Null while enabled."
          enum:
            - disabled_by_user
            - failing
            - null
        failing_since:
          type:
            - string
            - "null"
          format: date-time
          description: "When its deliveries started failing with no success since (RFC 3339, UTC); null while they succeed."
        created_at:
          type: string
          format: date-time
          description: "When it was created (RFC 3339, UTC)."
        updated_at:
          type: string
          format: date-time
          description: "When it last changed (RFC 3339, UTC)."
    WebhookWithSecret:
      allOf:
        - $ref: "#/components/schemas/Webhook"
        - type: object
          required:
            - secret
          properties:
            secret:
              type:
                - string
                - "null"
              description: "The signing secret (`whsec_…`). Shown only in this response; null when a create is replayed."
    WebhookList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Webhook"
        next_cursor:
          type:
            - string
            - "null"
          description: "Always null: this list is returned whole."
    CreateWebhookRequest:
      type: object
      required:
        - url
        - events
      properties:
        url:
          type: string
          description: "An `https` URL on the public internet, at most 2,048 characters, with no user name or password."
          examples: ["https://example.com/hooks/pantech"]
        description:
          type: string
          description: "A note for yourself, at most 255 characters."
          examples: ["Production alerts"]
        events:
          type: array
          description: "1 to 100 entries: event types from `GET /webhook-event-types`, `<resource>.*`, or `*`."
          items:
            type: string
          examples: [["instance.*", "invoice.paid"]]
    UpdateWebhookRequest:
      type: object
      properties:
        url:
          type: string
          description: "A new `https` URL."
        description:
          type: string
          description: "A new note, at most 255 characters."
        events:
          type: array
          description: "The new subscriptions, replacing the old ones."
          items:
            type: string
        enabled:
          type: boolean
          description: "`false` stops deliveries; `true` resumes them and clears the failure history."
    WebhookDelivery:
      type: object
      description: "One event sent, or waiting to be sent, to one webhook."
      required:
        - id
        - webhook_id
        - event_id
        - event_type
        - status
        - attempts
        - next_attempt_at
        - last_attempt_at
        - last_status_code
        - last_error
        - last_response
        - last_duration_ms
        - delivered_at
        - created_at
      properties:
        id:
          type: string
          description: "e.g. `whd_...`."
        webhook_id:
          type: string
          description: "The webhook it is for."
        event_id:
          type: string
          description: "The event's `id`, the same in every delivery of it: deduplicate on it."
        event_type:
          type: string
          description: "e.g. `instance.created`."
        status:
          type: string
          description: "`pending` until it succeeds, fails eight times, or is cancelled because the webhook was disabled or deleted."
          enum:
            - pending
            - succeeded
            - failed
            - canceled
        attempts:
          type: integer
          description: "How many times it has been sent."
        next_attempt_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it is next sent, while `pending`."
        last_attempt_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it was last sent."
        last_status_code:
          type:
            - integer
            - "null"
          description: "The HTTP status your endpoint last answered; null if it did not answer."
        last_error:
          type:
            - string
            - "null"
          description: "Why the last attempt failed: `http_<status>`, `timeout`, `connection_failed`, `dns_failed`, `tls_failed`, `blocked_address` (the URL resolved to an address that is not on the public internet), `invalid_url`, `endpoint_disabled` or `endpoint_deleted`."
        last_response:
          type:
            - string
            - "null"
          description: "The first 1,024 characters of your endpoint's last response body."
        last_duration_ms:
          type:
            - integer
            - "null"
          description: "How long the last attempt took, in milliseconds."
        delivered_at:
          type:
            - string
            - "null"
          format: date-time
          description: "When it succeeded."
        created_at:
          type: string
          format: date-time
          description: "When the event was queued for this webhook."
    WebhookDeliveryDetail:
      allOf:
        - $ref: "#/components/schemas/WebhookDelivery"
        - type: object
          required:
            - payload
          properties:
            payload:
              $ref: "#/components/schemas/WebhookEvent"
    WebhookDeliveryList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/WebhookDelivery"
        next_cursor:
          type:
            - string
            - "null"
          description: "Pass as `cursor` for the next page; null on the last page."
    WebhookEvent:
      type: object
      description: |-
        The body of every delivery. An infrastructure event's `data` names the resource and the change: `resource_type`, `resource_id`, `operation_id`, `action`, `status` (`succeeded` or `failed`) and, on failure, `error` with the same safe `code` and `message` the operations API returns. Read the resource through the API for more. A billing event's `data` carries its amounts in minor units.
      properties:
        id:
          type: string
          description: "The event's id (`evt_…`). Deduplicate on it."
        type:
          type: string
          description: "e.g. `instance.created`, `invoice.paid`, `webhook.test`."
        api_version:
          type: string
          description: "The shape of this body, as a date: `2026-10-22`."
        created_at:
          type: string
          format: date-time
          description: "When it happened (RFC 3339, UTC)."
        organization_id:
          type: string
          description: "Your organization."
        project_id:
          type: string
          description: "The project (`acc_…`) the resource is in."
        data:
          type: object
          description: "What happened; depends on `type`."
    WebhookEventType:
      type: object
      required:
        - type
        - resource
        - description
      properties:
        type:
          type: string
          description: "e.g. `instance.created`."
        resource:
          type: string
          description: "What it is about, for `<resource>.*` subscriptions."
        description:
          type: string
          description: "What it means."
    WebhookEventTypeList:
      type: object
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/WebhookEventType"
        next_cursor:
          type:
            - string
            - "null"
          description: "Always null: this list is returned whole."
