openapi: 3.0.3
info:
  title: GameServerKings API
  version: 1.0.0
  description: API for ordering and managing game servers and dedicated servers.
servers:
  - url: https://api.gameserverkings.com

security:
  - BearerAuth: []

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer

  schemas:
    Payment:
      type: object
      required: [provider, currency]
      properties:
        provider:
          type: string
          enum: [stripe, paypal, default, account_credit]
          description: >
            Payment source for the order.
            `stripe`/`paypal` charge the supplied `method` or `intent`.
            `default` resolves the user's stored default payment method server-side.
            `account_credit` pays the order entirely from the user's account credit
            balance in `currency` and requires no `method`/`intent`; the order is
            rejected with 402 if the balance does not fully cover the total. In all
            cases available account credit is applied first and only the remainder
            (if any) is charged to the gateway.
        currency:
          type: string
        method:
          type: string
          nullable: true
        intent:
          type: string
          nullable: true

    Period:
      type: integer
      enum: [1, 3, 6, 12]
      description: Billing period in months

    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          example: false
        message:
          type: string
        error:
          type: string

    StripePaymentMethod:
      type: object
      description: >
        A Stripe PaymentMethod object as returned by the Stripe API. Only the
        commonly used fields are documented; the object is passed through, so
        other Stripe-provided properties may also be present.
      properties:
        id:
          type: string
          example: pm_1Nxxxxxxxxxxxxxx
        type:
          type: string
          enum: [card, us_bank_account, sepa_debit]
        card:
          type: object
          nullable: true
          properties:
            brand:
              type: string
            last4:
              type: string
            exp_month:
              type: integer
            exp_year:
              type: integer
        us_bank_account:
          type: object
          nullable: true
          properties:
            bank_name:
              type: string
            last4:
              type: string
        sepa_debit:
          type: object
          nullable: true
          properties:
            last4:
              type: string

    PaypalPaymentMethod:
      type: object
      properties:
        id:
          type: integer
        token:
          type: string
          description: Vault token identifying the saved PayPal method
        type:
          type: string
        payerId:
          type: string
          nullable: true
        email:
          type: string
          nullable: true
        name:
          type: string
          nullable: true
        lastUsedAt:
          type: string
          format: date-time
          nullable: true
        active:
          type: boolean
        expiresAt:
          type: string
          format: date-time
          nullable: true

    CosmicRule:
      type: object
      required: [src_ip, dst_port, protocol, rule]
      properties:
        src_ip:
          type: string
        dst_port:
          type: string
        protocol:
          type: string
        rule:
          type: string

    Attack:
      type: object
      description: >
        A single mitigated DDoS attack event recorded against one IP address.
        `startedAt`/`endedAt` may be null while an event is still open or if
        its timing was not captured. `pps` and `mbps` are the peak observed
        packet rate and throughput for the event.
      properties:
        id:
          type: integer
        eventId:
          type: string
          description: Upstream mitigation event identifier
        address:
          type: string
          description: The attacked IPv4 address
        pps:
          type: integer
          description: Peak packets per second
        mbps:
          type: number
          description: Peak throughput in megabits per second
        startedAt:
          type: string
          format: date-time
          nullable: true
        endedAt:
          type: string
          format: date-time
          nullable: true

    FirewallMetricsBucket:
      type: object
      description: >
        One time bucket of aggregated firewall/filter traffic for an IP.
        Throughput fields (`*_bps`) are in bits per second and packet-rate
        fields (`*_pps`) are in packets per second, each averaged over the
        bucket and summed across mitigation nodes. `all` is total observed
        traffic, `pass` is what the filter forwarded, and `drop` is what the
        filter discarded.
      properties:
        time_bucket:
          type: string
          format: date-time
          description: Start of the bucket (UTC), aligned to the requested bucket size
        all_bps:
          type: number
          description: Total throughput in bits per second
        all_pps:
          type: number
          description: Total packet rate in packets per second
        pass_bps:
          type: number
          description: Passed (forwarded) throughput in bits per second
        pass_pps:
          type: number
          description: Passed (forwarded) packet rate in packets per second
        drop_bps:
          type: number
          description: Dropped throughput in bits per second
        drop_pps:
          type: number
          description: Dropped packet rate in packets per second

    Billable:
      type: object
      description: >
        A subscription that groups one or more services and drives recurring
        billing. Payment-method and analytics identifiers are omitted.
      properties:
        id:
          type: integer
        userId:
          type: integer
          nullable: true
        status:
          type: string
          description: Billable lifecycle status (e.g. ACTIVE, PENDING, CANCELLED, FAILED)
        start:
          type: string
          format: date-time
        end:
          type: string
          format: date-time
          nullable: true
        periodStart:
          type: string
          format: date-time
        periodEnd:
          type: string
          format: date-time
        gateway:
          type: string
          description: Billing gateway (e.g. INTERNAL, STRIPE, PAYPAL, FASTSPRING)
        periodUnit:
          type: string
          description: Recurring interval unit (e.g. MONTH)
        periodLength:
          type: integer
          description: Number of period units per billing cycle
        autoRenew:
          type: boolean
        cancelRequestedAt:
          type: string
          format: date-time
          nullable: true
          description: Set when the subscription is scheduled to cancel at period end
        price:
          type: integer
          description: Recurring price per cycle in the billable's currency (integer cents)
        currency:
          type: string
        taxExempt:
          type: boolean
        referredByAffiliateId:
          type: integer
          nullable: true

    Service:
      type: object
      description: >
        A billing service — one billable line item bound to a provisioned
        resource (a game server or dedicated machine) via `resourceType` +
        `resourceId`. Carries per-service price and cancellation state, with the
        parent billable (subscription) nested.
      properties:
        id:
          type: integer
        resourceType:
          type: string
          enum: [SHARED, DEDICATED, IPV4, OTHER]
        resourceId:
          type: integer
        price:
          type: integer
          nullable: true
          description: Per-service recurring price in the parent billable's currency (integer cents), or null
        description:
          type: string
          nullable: true
        billableId:
          type: integer
          nullable: true
          description: Parent subscription (billable) ID, or null if unattached
        billable:
          nullable: true
          description: The parent subscription this service belongs to, or null if unattached
          allOf:
            - $ref: '#/components/schemas/Billable'
        deliveredAt:
          type: string
          format: date-time
          nullable: true
        suspendedAt:
          type: string
          format: date-time
          nullable: true
        cancelRequestedAt:
          type: string
          format: date-time
          nullable: true
          description: Set when this service is scheduled to cancel at the billable's period end
        canceledAt:
          type: string
          format: date-time
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    Invoice:
      type: object
      description: An invoice issued against a billable. All amounts are in the invoice's currency (integer cents).
      properties:
        id:
          type: integer
        userId:
          type: integer
          nullable: true
        billableId:
          type: integer
          nullable: true
        status:
          type: string
          description: Invoice lifecycle status (e.g. DRAFT, FINALIZED, PAID, PARTIALLY_PAID, OVERDUE, VOID)
        type:
          type: string
          description: Invoice type (e.g. INITIAL, RECURRING, ONE_TIME)
        subtotal:
          type: integer
          description: Sum of line items before tax
        taxTotal:
          type: integer
        total:
          type: integer
          description: subtotal + tax − credits
        amountPaid:
          type: integer
        currency:
          type: string
        periodStart:
          type: string
          format: date-time
          nullable: true
        periodEnd:
          type: string
          format: date-time
          nullable: true
        issuedAt:
          type: string
          format: date-time
          nullable: true
        dueAt:
          type: string
          format: date-time
          nullable: true
        paidAt:
          type: string
          format: date-time
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

paths:
  /order/shared:
    post:
      summary: Order a shared game server
      description: >
        Provisions a shared game server for the configured game, resources, and
        billing period, charging the selected payment method. Account credits are
        applied automatically and a fully credit-covered order is settled without
        a charge.


        Authenticating with a TEST-mode API key runs the full validation and
        pricing path but does not create any order records, charge payment, or
        provision; the response includes `test: true` alongside a `pricing`
        breakdown instead of the created resource IDs.
      tags: [Orders]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [game, memory, storage, compute, location, period, payment]
              properties:
                game:
                  type: integer
                  minimum: 1
                variant:
                  type: integer
                  minimum: 1
                  description: Optional Pterodactyl egg/variant to provision instead of the game default
                memory:
                  type: integer
                  minimum: 1
                storage:
                  type: integer
                  minimum: 1
                compute:
                  type: integer
                  minimum: 1
                location:
                  type: string
                dedicatedIp:
                  type: boolean
                  default: false
                period:
                  $ref: '#/components/schemas/Period'
                expectedAmount:
                  type: integer
                  minimum: 0
                  description: >
                    Optional pre-tax, pre-coupon, pre-credit amount in USD cents
                    that the client expects to be charged. When provided, the
                    server compares it against its own re-priced total and aborts
                    with 409 if they differ. Omit to skip the drift check.
                coupon:
                  type: string
                payment:
                  $ref: '#/components/schemas/Payment'
      responses:
        '200':
          description: >
            Order processed successfully (live keys) or validated (TEST keys).
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  test:
                    type: boolean
                    description: Present and true only when called with a TEST-mode API key
                  message:
                    type: string
                    description: Present only for TEST-mode responses
                  serverId:
                    type: integer
                    description: Created server ID (live keys only)
                  billableId:
                    type: integer
                    description: Created billable ID (live keys only)
                  invoiceId:
                    type: integer
                    description: Created invoice ID (live keys only)
                  serviceId:
                    type: integer
                    description: Created service ID (live keys only)
                  pricing:
                    type: object
                    description: Pricing breakdown in minor currency units; present only for TEST-mode responses
                    properties:
                      currency:
                        type: string
                      subtotal:
                        type: integer
                      tax:
                        type: integer
                      credit:
                        type: integer
                      total:
                        type: integer
        '400':
          description: Invalid input, invalid coupon, or payment info not valid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '402':
          description: >
            Payment failed, or (for `account_credit`) the account credit balance
            does not fully cover the order total. No charges were made.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: >
            The re-priced total did not match the supplied `expectedAmount`
            (pricing changed since the page was loaded).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /order/dedicated:
    post:
      summary: Order a dedicated server
      description: >
        Provisions a dedicated server for the configured type, disks, uplink, and
        billing period, charging the selected payment method. Account credits are
        applied automatically.


        Authenticating with a TEST-mode API key runs the full validation and
        pricing path but does not enqueue provisioning, charge payment, or persist
        anything; the response includes `test: true` alongside a `pricing`
        breakdown instead of the created resource IDs.
      tags: [Orders]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [type, memory, disks, uplink, location, period, payment]
              properties:
                type:
                  type: integer
                  minimum: 1
                memory:
                  type: integer
                  minimum: 1
                disks:
                  type: array
                  minItems: 1
                  description: >
                    Disk selection for the build. Each entry references a storage
                    configurable-option by `optionId` and how many of that disk to
                    include via `quantity`.
                  items:
                    type: object
                    required: [optionId, quantity]
                    properties:
                      optionId:
                        type: integer
                        minimum: 1
                      quantity:
                        type: integer
                        minimum: 1
                uplink:
                  type: integer
                  minimum: 1
                location:
                  type: string
                templateId:
                  type: integer
                  minimum: 1
                rootPassword:
                  type: string
                  minLength: 8
                  maxLength: 128
                machineId:
                  type: integer
                  minimum: 1
                  description: >
                    Optional. Target a specific machine that is listed at a
                    reduced price override. The server validates that the
                    machine still matches the requested type, location, and
                    options (including disk quantities) and that its override is
                    strictly lower than the normal type+options price before
                    applying it. If the machine has been claimed or no longer
                    qualifies by the time the order is processed, the request
                    fails with 409 — it will not silently fall back to a
                    different machine.
                period:
                  $ref: '#/components/schemas/Period'
                expectedAmount:
                  type: integer
                  minimum: 0
                  description: >
                    Optional pre-tax, pre-coupon, pre-credit amount in USD cents
                    that the client expects to be charged for the configured
                    selection. When provided, the server compares it against its
                    own re-priced total and aborts with 409 if they differ
                    (protecting against stale pricing in the browser). Omit to
                    skip the drift check entirely — useful for API-driven
                    clients that trust the server's pricing.
                coupon:
                  type: string
                payment:
                  $ref: '#/components/schemas/Payment'
      responses:
        '200':
          description: >
            Order processed successfully (live keys) or validated (TEST keys).
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  test:
                    type: boolean
                    description: Present and true only when called with a TEST-mode API key
                  message:
                    type: string
                    description: Present only for TEST-mode responses
                  orderId:
                    type: integer
                    description: Created invoice ID (live keys only). Alias of `invoiceId`, kept for backwards compatibility.
                  machineId:
                    type: integer
                    description: Assigned machine ID (live keys only)
                  billableId:
                    type: integer
                    description: Created billable ID (live keys only)
                  invoiceId:
                    type: integer
                    description: Created invoice ID (live keys only)
                  serviceId:
                    type: integer
                    description: Created service ID (live keys only)
                  pricing:
                    type: object
                    description: Pricing breakdown in minor currency units; present only for TEST-mode responses
                    properties:
                      currency:
                        type: string
                      subtotal:
                        type: integer
                      tax:
                        type: integer
                      credit:
                        type: integer
                      total:
                        type: integer
        '400':
          description: Invalid input or payment info
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '402':
          description: >
            Payment failed, or (for `account_credit`) the account credit balance
            does not fully cover the order total. No charges were made.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: >
            Specified `machineId` is no longer available, doesn't match the
            requested configuration, or its price override no longer beats
            the normal type+options price.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: >
            Provisioning service temporarily unavailable; the order was not
            placed. Safe to retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /shared:
    get:
      summary: List your game servers
      description: >
        Returns all of the authenticated user's game servers (excluding deleted
        ones), each including its game and location, ordered by most recently
        created.
      tags: [Game Servers]
      security:
        - BearerAuth: []
      responses:
        '200':
          description: List of the user's game servers
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  total:
                    type: integer
                    description: Number of game servers returned
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        status:
                          type: string
                          description: Server lifecycle status
                        gameId:
                          type: integer
                        variantId:
                          type: integer
                          nullable: true
                        ip:
                          type: string
                          nullable: true
                        port:
                          type: integer
                          nullable: true
                        dedicatedIp:
                          type: boolean
                        createdAt:
                          type: string
                          format: date-time
                        game:
                          type: object
                          description: The game this server runs
                          properties:
                            id:
                              type: integer
                            name:
                              type: string
                        location:
                          type: object
                          properties:
                            id:
                              type: string
                            name:
                              type: string
                            code:
                              type: string
                            region:
                              type: string
                            city:
                              type: string
                            country:
                              type: string
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /shared/{id}:
    get:
      summary: Get a game server by ID
      description: >
        Returns details for a specific game server owned by the authenticated
        user, including its game, location, active plan(s), variant, and the
        associated billing service (with its parent billable nested, if any).
      tags: [Game Servers]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: Server ID
      responses:
        '200':
          description: Game server details
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      id:
                        type: integer
                      status:
                        type: string
                        description: Server lifecycle status
                      gameId:
                        type: integer
                      variantId:
                        type: integer
                        nullable: true
                      ip:
                        type: string
                        nullable: true
                      port:
                        type: integer
                        nullable: true
                      dedicatedIp:
                        type: boolean
                      createdAt:
                        type: string
                        format: date-time
                      game:
                        type: object
                        description: The game this server runs
                        properties:
                          id:
                            type: integer
                          name:
                            type: string
                      location:
                        type: object
                        properties:
                          id:
                            type: string
                          name:
                            type: string
                          code:
                            type: string
                          region:
                            type: string
                          city:
                            type: string
                          country:
                            type: string
                      variant:
                        type: object
                        nullable: true
                        description: The Pterodactyl egg/variant this server was provisioned against, or null
                        properties:
                          id:
                            type: integer
                          name:
                            type: string
                      plans:
                        type: array
                        description: Active (non-cancelled) plan(s) attached to this server
                        items:
                          type: object
                      service:
                        nullable: true
                        description: >
                          The billing service associated with this server (with its
                          parent billable nested), or null if the server has no billing
                          entry.
                        allOf:
                          - $ref: '#/components/schemas/Service'
        '400':
          description: Invalid server ID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Server not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /shared/{id}/configuration:
    post:
      summary: Change a shared server's plan
      description: >
        Changes the compute/memory/storage tiers (and optional dedicated IP) of
        an existing shared game server, re-pricing the billable and charging the
        prorated difference for upgrades. Downgrades and same-price changes are
        applied without a charge. A configuration change cannot be started while
        another is pending, and is unavailable for subscription-style billables
        (FastSpring, internal, or Stripe/PayPal subscriptions).


        Authenticating with a TEST-mode API key runs the full validation and
        proration/pricing path but does not swap plans, create an invoice,
        charge, or re-provision; the response includes `test: true` alongside
        the computed `proration`, `delta`, and `taxAmount`.
      tags: [Game Servers]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: Shared server ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [compute, memory, storage]
              properties:
                compute:
                  type: integer
                  minimum: 1
                  description: Target compute tier amount
                memory:
                  type: integer
                  minimum: 1
                  description: Target memory tier amount
                storage:
                  type: integer
                  minimum: 1
                  description: Target storage tier amount
                dedicatedIp:
                  type: boolean
                  description: Optional. Add or remove a dedicated IP as part of the change; defaults to the server's current setting.
      responses:
        '200':
          description: >
            Configuration change applied (live keys) or validated (TEST keys).
            All amounts are in minor currency units (e.g. USD cents).
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  test:
                    type: boolean
                    description: Present and true only when called with a TEST-mode API key
                  message:
                    type: string
                    description: Present only for TEST-mode responses
                  proration:
                    type: number
                    description: Fraction of the current billing period remaining, applied to the price difference
                  delta:
                    type: integer
                    description: Prorated pre-tax price difference; positive for an upgrade, zero or negative otherwise
                  taxAmount:
                    type: integer
                    description: Tax charged on the delta (only for upgrades)
                  totalDelta:
                    type: integer
                    description: delta + taxAmount; present only for TEST-mode responses
                  currency:
                    type: string
                    description: Billable currency; present only for TEST-mode responses
        '400':
          description: >
            Invalid plan options, missing billable, unsupported subscription
            gateway, or payment validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Server or active plan not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: A configuration change is already pending for this server
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /server/{id}/network/allocations/rules:
    get:
      summary: List Cosmic firewall rules per allocation
      description: >
        Returns the Cosmic firewall rules that apply to each of the game
        server's allocations, keyed by the allocation id returned from
        `/server/{id}/network/allocations`.

        Rules are resolved per unique allocation IP and then filtered to those
        whose `dst_port` matches that allocation's own port, so on a shared
        protected IP the response only ever contains rules for the IP/port
        pairs this server actually holds — never another customer's.

        If rules for one IP cannot be fetched, that IP degrades to an empty
        rule list rather than failing the whole response, so an allocation may
        legitimately map to `[]`.
      tags: [Game Servers]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Pterodactyl short identifier (8 hex characters)
          schema:
            type: string
            pattern: '^[a-f0-9]{8}$'
      responses:
        '200':
          description: Rules grouped by allocation id
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  rules:
                    type: object
                    description: >
                      Map of allocation id to the rules targeting that
                      allocation's port. Every allocation on the server is
                      present as a key, with an empty array when no rules apply.
                    additionalProperties:
                      type: array
                      items:
                        $ref: '#/components/schemas/CosmicRule'
        '404':
          description: >
            Server not found, not owned by the caller, or the identifier is not
            a valid panel short identifier.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /dedicated/template-groups:
    get:
      summary: List OS template groups and their templates
      description: >
        Returns all template groups, each with its nested OS templates
        (id, profile id, and version). Response is cached for 1h.
      tags: [Dedicated]
      security: []
      responses:
        '200':
          description: List of template groups
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        name:
                          type: string
                        image:
                          type: string
                        type:
                          type: string
                          enum: [LINUX, WINDOWS, UNIX, OTHER]
                        templates:
                          type: array
                          items:
                            type: object
                            properties:
                              id:
                                type: integer
                              profileId:
                                type: integer
                              version:
                                type: string
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /dedicated/types:
    get:
      summary: List available dedicated server types
      description: >
        Always returns each type's configurable options alongside the type
        metadata. Response is cached for 1h.
      tags: [Dedicated]
      security: []
      responses:
        '200':
          description: List of dedicated server types
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        key:
                          type: string
                        name:
                          type: string
                        cpuModel:
                          type: string
                          nullable: true
                        description:
                          type: string
                          nullable: true
                        basePrice:
                          type: integer
                          description: Base price in cents
                        options:
                          type: array
                          description: >
                            The type's configurable options with per-type
                            override pricing and the joined option / category
                            metadata.
                          items:
                            type: object
                            properties:
                              optionId:
                                type: integer
                              priceOverride:
                                type: integer
                                nullable: true
                              additionalPriceOverride:
                                type: integer
                                nullable: true
                              option:
                                type: object
                                properties:
                                  id:
                                    type: integer
                                  name:
                                    type: string
                                  priceDelta:
                                    type: integer
                                  additionalPriceDelta:
                                    type: integer
                                    nullable: true
                                  category:
                                    type: object
                                    properties:
                                      id:
                                        type: integer
                                      key:
                                        type: string
                                      name:
                                        type: string
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /dedicated/locations:
    get:
      summary: List available locations
      tags: [Dedicated]
      security: []
      responses:
        '200':
          description: List of locations
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        name:
                          type: string
                        code:
                          type: string
                        region:
                          type: string
                        city:
                          type: string
                        country:
                          type: string
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /dedicated/benchmarks:
    get:
      summary: List benchmark scores for available dedicated types
      description: >
        Returns benchmark categories, the dedicated types that have at least
        one score recorded, and the per (type, category) scores joining them.
        Only types currently marked `available` are included.
      tags: [Dedicated]
      security: []
      responses:
        '200':
          description: Benchmark catalog
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      categories:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: integer
                            key:
                              type: string
                            name:
                              type: string
                            unit:
                              type: string
                              nullable: true
                            sortOrder:
                              type: integer
                      types:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: integer
                            key:
                              type: string
                            name:
                              type: string
                      scores:
                        type: array
                        items:
                          type: object
                          properties:
                            typeId:
                              type: integer
                            categoryId:
                              type: integer
                            score:
                              type: number
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /dedicated/inventory:
    get:
      summary: List dedicated server inventory
      tags: [Dedicated]
      security: []
      parameters:
        - name: search
          in: query
          schema:
            type: string
          description: Search by name or CPU model
        - name: key
          in: query
          schema:
            type: string
          description: Filter by type key
        - name: locationId
          in: query
          schema:
            type: string
          description: Filter by location ID
        - name: minPrice
          in: query
          schema:
            type: integer
            minimum: 0
          description: Minimum base price filter
        - name: maxPrice
          in: query
          schema:
            type: integer
            minimum: 0
          description: Maximum base price filter
        - name: sortBy
          in: query
          schema:
            type: string
            enum: [name, basePrice, createdAt]
            default: name
        - name: sortOrder
          in: query
          schema:
            type: string
            enum: [asc, desc]
            default: asc
        - name: options
          in: query
          required: false
          schema:
            type: string
          description: >
            Pass `true` (or `1`) to embed each type's configurable options on
            the response (same shape as /dedicated/types). Per-machine option
            selections are always included on every machine entry regardless
            of this flag.
      responses:
        '200':
          description: Inventory list
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        key:
                          type: string
                        name:
                          type: string
                        cpuModel:
                          type: string
                        description:
                          type: string
                        basePrice:
                          type: integer
                        resellerDiscount:
                          type: integer
                          description: >
                            Per-month discount in cents applied to this type's
                            base price when ordering as a reseller. Only present
                            on the response when the request is authenticated
                            (Bearer token or session) as a reseller account.
                        startingPrice:
                          type: integer
                          description: >
                            Lowest effective monthly price in cents available
                            for this type. Equal to base + cheapest option per
                            category, unless a machine-level priceOverride beats
                            that total — in which case the override wins.
                        hasStock:
                          type: boolean
                          description: >
                            True when at least one unassigned AVAILABLE machine
                            exists for this type (matching the location filter
                            if one was supplied).
                        availableCount:
                          type: integer
                          description: >
                            Number of individual AVAILABLE, unassigned machines
                            backing this type (pre-deduplication). Each entry in
                            `machines` may represent multiple of these — see
                            `machines[].count`.
                        options:
                          type: array
                          description: >
                            Only present when `options=true` is supplied.
                            Lists the type's configurable options with
                            per-type override pricing and the joined option /
                            category metadata.
                          items:
                            type: object
                            properties:
                              optionId:
                                type: integer
                              priceOverride:
                                type: integer
                                nullable: true
                              additionalPriceOverride:
                                type: integer
                                nullable: true
                              option:
                                type: object
                                properties:
                                  id:
                                    type: integer
                                  name:
                                    type: string
                                  priceDelta:
                                    type: integer
                                  additionalPriceDelta:
                                    type: integer
                                    nullable: true
                                  category:
                                    type: object
                                    properties:
                                      id:
                                        type: integer
                                      key:
                                        type: string
                                      name:
                                        type: string
                        machines:
                          type: array
                          items:
                            type: object
                            properties:
                              id:
                                type: integer
                                description: >
                                  Pass this back as `machineId` on POST
                                  `/order/dedicated` to claim this specific
                                  machine at its override price.
                              location:
                                type: string
                              locationCode:
                                type: string
                              specs:
                                type: object
                                additionalProperties:
                                  type: string
                              monthlyPrice:
                                type: integer
                                description: >
                                  Effective monthly price in cents. Equals
                                  `normalPrice` unless a machine-level override
                                  beats it, in which case it matches the
                                  override.
                              normalPrice:
                                type: integer
                                description: >
                                  Monthly price in cents computed from the type
                                  base + selected options, ignoring any
                                  machine-level override.
                              usedOverride:
                                type: boolean
                                description: >
                                  True when `monthlyPrice` reflects a
                                  machine-level price override rather than the
                                  normal type+options total.
                              count:
                                type: integer
                                description: >
                                  Number of machines available with this exact
                                  location + options configuration.
                              options:
                                type: array
                                description: >
                                  This machine's option selections with
                                  quantities and the joined option / category
                                  metadata. Always reflects the
                                  lowest-priced machine in the dedup group.
                                items:
                                  type: object
                                  properties:
                                    optionId:
                                      type: integer
                                    quantity:
                                      type: integer
                                    option:
                                      type: object
                                      properties:
                                        id:
                                          type: integer
                                        name:
                                          type: string
                                        priceDelta:
                                          type: integer
                                        additionalPriceDelta:
                                          type: integer
                                          nullable: true
                                        category:
                                          type: object
                                          properties:
                                            id:
                                              type: integer
                                            key:
                                              type: string
                                            name:
                                              type: string
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /dedicated:
    get:
      summary: List your dedicated servers
      description: >
        Returns all active dedicated servers owned by the authenticated user,
        each including its type and location, ordered by most recently assigned.
      tags: [Dedicated]
      security:
        - BearerAuth: []
      responses:
        '200':
          description: List of the user's dedicated servers
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  total:
                    type: integer
                    description: Number of dedicated servers returned
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        serverName:
                          type: string
                        status:
                          type: string
                        ipv4:
                          type: array
                          items:
                            type: string
                        assignedAt:
                          type: string
                          format: date-time
                          nullable: true
                        type:
                          type: object
                          properties:
                            id:
                              type: integer
                            key:
                              type: string
                            name:
                              type: string
                            cpuModel:
                              type: string
                            basePrice:
                              type: integer
                        location:
                          type: object
                          properties:
                            id:
                              type: string
                            name:
                              type: string
                            code:
                              type: string
                            region:
                              type: string
                            city:
                              type: string
                            country:
                              type: string
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /dedicated/{id}:
    get:
      summary: Get a dedicated server by ID
      description: Returns details for a specific dedicated server owned by the authenticated user, including type, location, options, and the associated billing service (if any).
      tags: [Dedicated]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: Machine ID
      responses:
        '200':
          description: Machine details
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      id:
                        type: integer
                      status:
                        type: string
                      ipv4:
                        type: array
                        items:
                          type: string
                      assignedAt:
                        type: string
                        format: date-time
                      type:
                        type: object
                        properties:
                          id:
                            type: integer
                          key:
                            type: string
                          name:
                            type: string
                          cpuModel:
                            type: string
                          basePrice:
                            type: integer
                      location:
                        type: object
                        properties:
                          id:
                            type: string
                          name:
                            type: string
                          code:
                            type: string
                          region:
                            type: string
                          city:
                            type: string
                          country:
                            type: string
                      options:
                        type: array
                        items:
                          type: object
                      service:
                        nullable: true
                        description: >
                          The billing service associated with this machine (with its
                          parent billable nested), or null if the machine has no billing
                          entry (e.g. admin-created machines).
                        allOf:
                          - $ref: '#/components/schemas/Service'
        '404':
          description: Machine not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /dedicated/{id}/power:
    get:
      summary: Get a dedicated server's power status
      description: Returns the current power state of a dedicated server owned by the authenticated user, sourced from TenantOS.
      tags: [Dedicated]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: Machine ID
      responses:
        '200':
          description: Power status
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    description: Power status payload as returned by TenantOS.
        '400':
          description: Invalid machine ID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Machine not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Machine is not yet provisioned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /dedicated/{id}/power/on:
    post:
      summary: Power on a dedicated server
      description: Requests a power-on for a dedicated server owned by the authenticated user via TenantOS.
      tags: [Dedicated]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: Machine ID
      responses:
        '200':
          description: Power on requested
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data:
                    description: Result payload as returned by TenantOS.
        '400':
          description: Invalid machine ID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Machine not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Machine is not yet provisioned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /dedicated/{id}/power/off:
    post:
      summary: Power off a dedicated server
      description: Requests a power-off for a dedicated server owned by the authenticated user via TenantOS.
      tags: [Dedicated]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: Machine ID
      responses:
        '200':
          description: Power off requested
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data:
                    description: Result payload as returned by TenantOS.
        '400':
          description: Invalid machine ID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Machine not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Machine is not yet provisioned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /dedicated/{id}/reinstall:
    post:
      summary: Reinstall a dedicated server
      description: >
        Starts a reinstallation of a dedicated server owned by the authenticated
        user using the given OS template profile id. The profile id must map to a
        configured template; arbitrary TenantOS profiles are rejected.
      tags: [Dedicated]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: Machine ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [profileId, rootPassword]
              properties:
                profileId:
                  type: integer
                  description: TenantOS template profile id (must belong to a configured template).
                rootPassword:
                  type: string
                  minLength: 8
                  description: >
                    Root password for the freshly installed OS. Required by TenantOS on
                    every reinstallation.
      responses:
        '200':
          description: Reinstallation started
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data:
                    description: Result payload as returned by TenantOS.
        '400':
          description: Invalid machine ID or template profile
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Machine not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Machine is not yet provisioned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /firewall/profiles:
    get:
      summary: List firewall profiles
      tags: [Firewall]
      security:
        - BearerAuth: []
      responses:
        '200':
          description: List of profiles
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /firewall/{ip}:
    get:
      summary: Get firewall rules for an IP
      tags: [Firewall]
      security:
        - BearerAuth: []
      parameters:
        - name: ip
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Rules for the IP
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  rules:
                    type: array
                    items:
                      $ref: '#/components/schemas/CosmicRule'
        '400':
          description: IP not owned by user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      summary: Update firewall rules for an IP
      description: >
        Replaces the firewall rules for the IP with the supplied set. An empty
        array clears all rules for the IP. The response returns the canonical
        rules after the update.
      tags: [Firewall]
      security:
        - BearerAuth: []
      parameters:
        - name: ip
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/CosmicRule'
      responses:
        '200':
          description: Rules updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  rules:
                    type: array
                    items:
                      $ref: '#/components/schemas/CosmicRule'
        '400':
          description: IP not owned by user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /firewall/attacks:
    get:
      summary: Get DDoS attack history across the account
      description: >
        Returns the paginated history of mitigated DDoS attack events recorded
        against the authenticated user's IPs, newest first.


        When `addresses` is omitted, results span every IP the user owns — those
        bound to their machines plus any attached directly to their account.
        When `addresses` is supplied, results are restricted to those IPs; if
        any requested address is not assigned to the user, the request is
        rejected with 400.
      tags: [Firewall]
      security:
        - BearerAuth: []
      parameters:
        - name: addresses
          in: query
          required: false
          schema:
            type: array
            items:
              type: string
          style: form
          explode: true
          description: >
            One or more IPv4 addresses to filter by. Repeat the parameter
            (`?addresses=a&addresses=b`) or pass a comma-separated list
            (`?addresses=a,b`). Omit to aggregate across the whole account.
        - name: count
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
          description: Number of attack records to return (page size)
        - name: offset
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
            default: 0
          description: Number of attack records to skip (for pagination)
      responses:
        '200':
          description: Attack history for the IP
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  attacks:
                    type: array
                    items:
                      $ref: '#/components/schemas/Attack'
                  total:
                    type: integer
                    description: Total number of matching attack records (ignores paging)
        '400':
          description: A requested address is not assigned to the user, or invalid query parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /firewall/{ip}/metrics:
    get:
      summary: Get firewall traffic metrics for an IP
      description: >
        Returns time-bucketed firewall/filter traffic metrics for one of the
        authenticated user's IPs, powering the live throughput and packet-rate
        charts in the client area. Data is aggregated per bucket and summed
        across mitigation nodes, ordered oldest to newest.


        This endpoint is rate limited per user (a token bucket of 20 requests
        refilling at ~1 every 5s) and globally; when exhausted it responds with
        429 and a `Retry-After` header (seconds).
      tags: [Firewall]
      security:
        - BearerAuth: []
      parameters:
        - name: ip
          in: path
          required: true
          schema:
            type: string
          description: An IPv4 address assigned to one of your machines or your account
        - name: seconds
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 3600
            default: 60
          description: Length of the look-back window in seconds (how far back to query)
        - name: bucket
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 3600
            default: 1
          description: Bucket size in seconds; each returned entry aggregates this many seconds
      responses:
        '200':
          description: Time-bucketed traffic metrics for the IP
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/FirewallMetricsBucket'
        '400':
          description: IP not owned by user, or invalid query parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: >
            Rate limit exceeded. Retry after the number of seconds given in the
            `Retry-After` response header.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds to wait before retrying
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /billing/invoices:
    get:
      summary: List invoices
      description: >
        Returns the authenticated user's invoices, newest first, with pagination.
        Each entry includes billing status, totals, and the parent billable ID.
      tags: [Billing]
      security:
        - BearerAuth: []
      parameters:
        - name: offset
          in: query
          required: false
          schema:
            type: integer
            default: 0
            minimum: 0
          description: Number of invoices to skip
        - name: count
          in: query
          required: false
          schema:
            type: integer
            default: 20
            minimum: 1
            maximum: 100
          description: Page size (capped at 100)
      responses:
        '200':
          description: Paginated list of invoices
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        type:
                          type: string
                          description: Invoice type
                        status:
                          type: string
                          description: Invoice lifecycle status
                        total:
                          type: integer
                          description: Invoice total in minor currency units
                        amountPaid:
                          type: integer
                          description: Amount already paid, in minor currency units
                        currency:
                          type: string
                        issuedAt:
                          type: string
                          format: date-time
                          nullable: true
                        dueAt:
                          type: string
                          format: date-time
                          nullable: true
                        createdAt:
                          type: string
                          format: date-time
                        billable:
                          type: object
                          nullable: true
                          properties:
                            id:
                              type: integer
                  pagination:
                    type: object
                    properties:
                      offset:
                        type: integer
                      count:
                        type: integer
                      total:
                        type: integer
                      hasNext:
                        type: boolean
                      hasPrev:
                        type: boolean
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /billing/invoices/pay:
    post:
      summary: Pay one or more invoices
      description: >
        Pays one or more invoices in a single request. Pass a single ID in
        `invoiceIds` to pay one invoice, or multiple IDs to bulk-pay. All invoices
        must belong to the authenticated user and be in a payable status
        (`FINALIZED`, `PARTIALLY_PAID`, `UNPAID`, or `OVERDUE`).


        Account credits are applied first; any invoice fully covered by credits is
        settled without a charge. If a balance remains, `gateway` and `methodId`
        are required, and the remaining invoices are charged — one payment is
        created per currency group across the paid invoices.
      tags: [Billing]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [invoiceIds]
              properties:
                invoiceIds:
                  type: array
                  minItems: 1
                  items:
                    type: integer
                    minimum: 1
                  description: IDs of the invoices to pay (one for single, many for bulk)
                gateway:
                  type: string
                  enum: [STRIPE, PAYPAL]
                  description: Required when a balance remains after credits are applied
                methodId:
                  type: string
                  description: >
                    Saved payment method identifier for the gateway. Use `CAPTURE`
                    for one-shot flows (e.g. iDEAL/PayPal one-time); such methods
                    are ignored by `setDefaultMethod` since they can't recur.
                intent:
                  type: string
                  description: Optional gateway payment intent reference
                setDefaultMethod:
                  type: boolean
                  default: false
                  description: >
                    Opt-in: also store the charged method as the renewal default on
                    the subscription(s) behind the paid invoices.
      responses:
        '200':
          description: Payment processed (or all invoices covered by credits)
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                    example: Payment processed
                  data:
                    type: object
                    properties:
                      creditsApplied:
                        type: integer
                        description: Total account credits applied, in minor currency units
                      fullyPaidByCredits:
                        type: array
                        items:
                          type: integer
                        description: IDs of invoices fully settled by credits alone
                      payments:
                        type: array
                        description: One payment per currency group; empty when everything was covered by credits
                        items:
                          type: object
                          properties:
                            currency:
                              type: string
                            paymentId:
                              type: integer
                            status:
                              type: string
                              description: Gateway charge status (e.g. succeeded, pending, failed)
                            gatewayRef:
                              type: string
                            invoiceIds:
                              type: array
                              items:
                                type: integer
                            amount:
                              type: integer
                              description: Charged amount for this currency group, in minor currency units
        '400':
          description: One or more invoices not payable, or a payment method is required for the remaining balance
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: One or more invoices not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /billing/billables/{id}:
    get:
      summary: Get a billable (subscription) by ID
      description: >
        Returns a single subscription owned by the authenticated user, with its
        services and invoices (newest first) nested. Payment-method and analytics
        identifiers are omitted. For lightweight order-confirmation polling, use
        `/billing/order-status/{billableId}` instead.
      tags: [Billing]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: Billable subscription ID
      responses:
        '200':
          description: Subscription details
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    allOf:
                      - $ref: '#/components/schemas/Billable'
                      - type: object
                        properties:
                          services:
                            type: array
                            items:
                              $ref: '#/components/schemas/Service'
                          invoices:
                            type: array
                            description: Invoices for this subscription, newest first
                            items:
                              $ref: '#/components/schemas/Invoice'
        '400':
          description: Invalid billable ID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Subscription not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /billing/billables/{id}/cancel:
    post:
      summary: Cancel a subscription
      description: >
        Cancels a billable subscription. If `immediate` is true or the billing period has already ended,
        the subscription is cancelled immediately and associated resources are terminated. Otherwise,
        the cancellation is scheduled for the end of the current billing period.
      tags: [Billing]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: Billable subscription ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                immediate:
                  type: boolean
                  default: false
                  description: If true, cancel immediately instead of at end of period
      responses:
        '200':
          description: Cancellation processed
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                      message:
                        type: string
                        example: Subscription cancelled immediately
                      refund:
                        type: object
                        nullable: true
                        description: >
                          Refund outcome for the immediate cancellation, or null
                          when nothing was refunded (e.g. outside the money-back
                          window).
                        properties:
                          gatewayRefunded:
                            type: integer
                            description: Amount refunded to the payment gateway, in minor currency units
                          creditRestored:
                            type: integer
                            description: Amount restored as account credit, in minor currency units
                          currency:
                            type: string
                          failed:
                            type: boolean
                            description: True if the gateway refund was attempted but failed
                  - type: object
                    properties:
                      success:
                        type: boolean
                      message:
                        type: string
                        example: Subscription will be cancelled on March 22, 2026
                      cancelDate:
                        type: string
                        format: date-time
        '400':
          description: Already cancelled or cancellation already scheduled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Subscription not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /billing/billables/{id}/undo-cancel:
    post:
      summary: Undo a scheduled cancellation
      description: >
        Reverses a cancellation that was scheduled for the end of the current billing period,
        keeping the subscription active. Only works while the subscription is still `ACTIVE`,
        has a pending cancellation, and the current billing period has not yet ended.
      tags: [Billing]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: Billable subscription ID
      responses:
        '200':
          description: Cancellation reversed
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                    example: Cancellation has been reversed
        '400':
          description: >
            Invalid subscription ID, no pending cancellation to undo, or the billing period
            has already ended
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Subscription not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /billing/order-status/{billableId}:
    get:
      summary: Get order status
      description: >
        Returns the current status of a billable order, its most recent
        invoice, and the provisioning status of each resource it covers.
        Intended for polling an order confirmation page until payment settles
        and the resources finish provisioning. Only the authenticated user's
        own billables are accessible.
      tags: [Billing]
      security:
        - BearerAuth: []
      parameters:
        - name: billableId
          in: path
          required: true
          schema:
            type: integer
          description: Billable ID
      responses:
        '200':
          description: Order status
          content:
            application/json:
              schema:
                type: object
                properties:
                  billable:
                    type: object
                    properties:
                      status:
                        type: string
                        description: Billable lifecycle status
                  invoice:
                    type: object
                    nullable: true
                    description: Most recent invoice for the billable, or null if none exists
                    properties:
                      status:
                        type: string
                        description: Invoice status
                      paidAt:
                        type: string
                        format: date-time
                        nullable: true
                  services:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        resourceType:
                          type: string
                          enum: [SHARED, DEDICATED]
                        resourceId:
                          type: integer
                        resourceStatus:
                          type: string
                          nullable: true
                          description: Live status of the underlying server (SHARED) or machine (DEDICATED), or null if not yet created
                        deliveredAt:
                          type: string
                          format: date-time
                          nullable: true
        '400':
          description: Invalid billable ID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Billable not found or not owned by the authenticated user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /stripe/methods:
    get:
      summary: List saved Stripe payment methods
      description: >
        Returns the authenticated user's saved Stripe payment methods (card,
        US bank account/ACH, and SEPA debit) along with their default method.
        Results are cached per Stripe customer for 10 minutes; the `cached`
        flag indicates whether this response was served from cache. Items are
        raw Stripe PaymentMethod objects, so additional Stripe-provided fields
        may be present beyond those documented here.
      tags: [Billing]
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Saved Stripe payment methods
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  paymentMethods:
                    type: array
                    items:
                      $ref: '#/components/schemas/StripePaymentMethod'
                  defaultPaymentMethod:
                    allOf:
                      - $ref: '#/components/schemas/StripePaymentMethod'
                    nullable: true
                    description: The customer's default payment method, or null if none is set
                  cached:
                    type: boolean
                    description: Whether the result was served from the Redis cache
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /paypal/methods:
    get:
      summary: List saved PayPal payment methods
      description: >
        Returns the authenticated user's vaulted PayPal payment methods along
        with their default method. The default is only populated when PayPal is
        the user's default billing gateway; otherwise it is null.
      tags: [Billing]
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Saved PayPal payment methods
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  methods:
                    type: array
                    items:
                      $ref: '#/components/schemas/PaypalPaymentMethod'
                  default:
                    allOf:
                      - $ref: '#/components/schemas/PaypalPaymentMethod'
                    nullable: true
                    description: The user's default PayPal method, or null if PayPal is not their default gateway
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /firewall/cache/{cacheType}/{ip}:
    get:
      summary: Get cache ports for a cache type and IP
      tags: [Firewall]
      security:
        - BearerAuth: []
      parameters:
        - name: cacheType
          in: path
          required: true
          schema:
            type: string
            enum: [steam_a2s, raknet, samp, fivem]
          description: >
            Game cache type identifier. An unrecognized value is rejected with a
            500 error rather than a validation error.
        - name: ip
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Cache ports data
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
        '400':
          description: IP not owned by user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      summary: Add cache ports for a cache type and IP
      tags: [Firewall]
      security:
        - BearerAuth: []
      parameters:
        - name: cacheType
          in: path
          required: true
          schema:
            type: string
        - name: ip
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ports]
              properties:
                ports:
                  type: array
                  items:
                    type: integer
      responses:
        '200':
          description: Cache ports added
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
        '400':
          description: IP not owned by user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /firewall/cache/{cacheType}/{ip}/ports:
    delete:
      summary: Delete specific cache ports
      tags: [Firewall]
      security:
        - BearerAuth: []
      parameters:
        - name: cacheType
          in: path
          required: true
          schema:
            type: string
        - name: ip
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ports]
              properties:
                ports:
                  type: array
                  items:
                    type: integer
      responses:
        '200':
          description: Cache ports deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
        '400':
          description: IP not owned by user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /firewall/cache/{cacheType}/{ip}/all:
    delete:
      summary: Delete all cache ports for a cache type and IP
      tags: [Firewall]
      security:
        - BearerAuth: []
      parameters:
        - name: cacheType
          in: path
          required: true
          schema:
            type: string
        - name: ip
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: All cache ports deleted for type
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
        '400':
          description: IP not owned by user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /firewall/cache/{ip}:
    delete:
      summary: Delete all cache data for an IP
      tags: [Firewall]
      security:
        - BearerAuth: []
      parameters:
        - name: ip
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: All cache deleted for IP
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
        '400':
          description: IP not owned by user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
