openapi: 3.1.0
info:
  title: Phylex Account API
  version: 1.9.0
  description: Account-scoped automation API with authoritative account-currency balances and prices, optional EUR/USD/GBP display conversions, atomic balance-funded purchases, bounded financial history, service inventory, desired-state renewal settings, idempotent KVM power control, and a read-only Advanced Phylex Shield facade. Display conversions are informational; invoices and ledger mutations always use the account currency. Each key has separate shared quotas of 600 read RPM and 120 write RPM. Inspect RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset on every response; a 429 also includes Retry-After.
  license:
    name: Proprietary
    url: https://phylex.net/terms
servers:
  - url: https://billing.phylex.net/api/proxy/account-api/v1
security:
  - bearerAuth: []
paths:
  /me:
    get:
      operationId: getAccount
      summary: Get the account identity
      x-required-scope: account:read
      responses:
        "200":
          description: Account identity
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Account" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
  /balance:
    get:
      operationId: getBalance
      summary: Get the account balance
      x-required-scope: balance:read
      parameters:
        - $ref: "#/components/parameters/DisplayCurrency"
      responses:
        "200":
          description: Current account balance
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Balance" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
  /invoices:
    get:
      operationId: listInvoices
      summary: List account invoices
      description: Linked service references are emitted only after ownership validation.
      x-required-scope: finance:read
      parameters:
        - $ref: "#/components/parameters/PageLimit"
        - $ref: "#/components/parameters/InvoiceCursor"
      responses:
        "200":
          description: Bounded invoice page in descending public-reference order
          content:
            application/json:
              schema: { $ref: "#/components/schemas/InvoicePage" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
  /orders:
    get:
      operationId: listOrders
      summary: List canonical service orders
      description: Linked service, invoice, and transaction references are emitted only after ownership validation.
      x-required-scope: finance:read
      parameters:
        - $ref: "#/components/parameters/PageLimit"
        - $ref: "#/components/parameters/OrderCursor"
      responses:
        "200":
          description: Bounded order page in descending public-reference order
          content:
            application/json:
              schema: { $ref: "#/components/schemas/OrderPage" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
    post:
      operationId: createOrder
      summary: Buy one Atlas service from the account balance
      description: Atomically validates the server-owned plan price, checks the authoritative account-currency balance, creates the invoice and service, and debits the ledger. Reusing the same Idempotency-Key with the same body returns the completed result; reusing it with another body is rejected.
      x-required-scope: orders:write
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateOrderRequest" }
            examples:
              kvm:
                summary: Buy a KVM plan using public option references
                value:
                  type: kvm
                  plan_code: epc_1
                  os_name: Alma Linux 8
                  period_ref: PHX-PERIOD-00000001
                  region_ref: PHX-REGION-00000001
      responses:
        "200":
          description: Purchase completed, or the stored response was replayed
          headers:
            Idempotent-Replayed:
              schema: { type: string, enum: ["true"] }
              description: Present only when a completed request is replayed.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CreateOrderResponse" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "409": { $ref: "#/components/responses/Conflict" }
        "422":
          description: Billing profile, plan options, price, stock, or balance does not permit the purchase
          content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
        "428":
          description: Idempotency-Key is required
          content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
  /transactions:
    get:
      operationId: listTransactions
      summary: List account ledger transactions
      description: Descriptions are redacted and linked references are emitted only after ownership validation.
      x-required-scope: finance:read
      parameters:
        - $ref: "#/components/parameters/PageLimit"
        - $ref: "#/components/parameters/TransactionCursor"
      responses:
        "200":
          description: Bounded transaction page in descending public-reference order
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TransactionPage" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
  /services:
    get:
      operationId: listServices
      summary: List services owned by the account
      description: Returns only primary Atlas account services; reseller storefront services remain isolated.
      x-required-scope: services:read
      parameters:
        - $ref: "#/components/parameters/PageLimit"
        - $ref: "#/components/parameters/ServiceCursor"
        - in: query
          name: type
          required: false
          schema: { type: string, pattern: "^[a-z0-9][a-z0-9_-]{0,31}$" }
        - in: query
          name: status
          required: false
          schema: { type: string, pattern: "^[a-z0-9][a-z0-9_-]{0,31}$" }
      responses:
        "200":
          description: Bounded service page in descending public-reference order
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ServicePage" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
  /services/{service_ref}:
    parameters:
      - $ref: "#/components/parameters/ServiceRef"
    get:
      operationId: getService
      summary: Get one service owned by the account
      x-required-scope: services:read
      responses:
        "200":
          description: Owned service detail and authoritative server time
          content:
            application/json:
              schema:
                type: object
                required: [service, server_time]
                properties:
                  service: { $ref: "#/components/schemas/Service" }
                  server_time: { type: string, format: date-time }
                additionalProperties: false
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
  /services/stats:
    get:
      operationId: getServiceStats
      summary: Get service counts across every service type and status
      description: Counts only primary Atlas services owned by this account. Reseller storefront services remain isolated.
      x-required-scope: services:read
      responses:
        "200":
          description: Current service inventory aggregates
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ServiceStats" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
  /services/{service_ref}/auto-renew:
    parameters:
      - $ref: "#/components/parameters/ServiceRef"
    put:
      operationId: setServiceAutoRenew
      summary: Set the desired automatic-renewal state
      description: Enabling automatic renewal can authorize future charges under the account billing agreement.
      x-required-scope: services:write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [enabled]
              properties: { enabled: { type: boolean } }
              additionalProperties: false
      responses:
        "200":
          description: Desired state accepted; identical replays return changed false
          content:
            application/json:
              schema:
                type: object
                required: [service_ref, auto_renew, changed, server_time]
                properties:
                  service_ref: { type: string, pattern: "^PHX-SVC-[0-9A-F]+$" }
                  auto_renew: { type: boolean }
                  changed: { type: boolean }
                  server_time: { type: string, format: date-time }
                additionalProperties: false
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
  /services/{service_ref}/power:
    parameters:
      - $ref: "#/components/parameters/ServiceRef"
    post:
      operationId: controlServicePower
      summary: Start, stop, or reboot a supported KVM service
      description: Requires a unique Idempotency-Key. Completed responses are durably replayed; an uncertain processing result is never automatically reclaimed.
      x-required-scope: services:power
      parameters:
        - in: header
          name: Idempotency-Key
          required: true
          schema: { type: string, minLength: 1, maxLength: 128, pattern: "^[A-Za-z0-9_.:-]+$" }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [action]
              properties:
                action: { type: string, enum: [start, stop, reboot] }
              additionalProperties: false
      responses:
        "200":
          description: Provider accepted the action, or the exact completed response was replayed
          content:
            application/json:
              schema:
                type: object
                required: [service_ref, action, accepted, server_time]
                properties:
                  service_ref: { type: string, pattern: "^PHX-SVC-[0-9A-F]+$" }
                  action: { type: string, enum: [start, stop, reboot] }
                  accepted: { type: boolean, const: true }
                  server_time: { type: string, format: date-time }
                additionalProperties: false
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
        "428":
          description: Idempotency-Key is required
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "502":
          description: The provider rejected or failed the action
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
  /services/{service_ref}/shield:
    parameters:
      - $ref: "#/components/parameters/ServiceRef"
    get:
      operationId: getShield
      summary: Get the Advanced Shield entitlement and owned rules
      description: Read-only Atlas facade for one directly owned KVM service. Requires an active or in-grace Advanced account subscription; provider identifiers are never returned.
      x-required-scope: shield:read
      responses:
        "200":
          description: Current entitlement, safe presets, and owned Atlas rule records
          content: { application/json: { schema: { $ref: "#/components/schemas/ShieldOverview" } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
  /services/{service_ref}/shield/capabilities:
    parameters:
      - $ref: "#/components/parameters/ServiceRef"
    get:
      operationId: getShieldCapabilities
      summary: Get Advanced Shield capabilities
      x-required-scope: shield:read
      responses:
        "200":
          description: Current entitlement and versioned safe preset descriptors
          content: { application/json: { schema: { $ref: "#/components/schemas/ShieldCapabilities" } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
  /services/{service_ref}/shield/rules:
    parameters:
      - $ref: "#/components/parameters/ServiceRef"
    get:
      operationId: listShieldRules
      summary: List owned Shield rules
      description: Returns only sanitized Atlas rule state for this service; no remote rule UUID or provider interface is exposed.
      x-required-scope: shield:read
      responses:
        "200":
          description: Owned rule records
          content: { application/json: { schema: { $ref: "#/components/schemas/ShieldRulePage" } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
    post:
      operationId: createShieldRule
      summary: Queue an Advanced custom Shield rule
      description: Atlas validates ownership, account and router quotas, then injects the owned service IP. Provider placement is never accepted from the caller.
      x-required-scope: shield:write
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content: { application/json: { schema: { $ref: "#/components/schemas/ShieldRuleIntent" } } }
      responses:
        "202": { description: Rule queued, content: { application/json: { schema: { $ref: "#/components/schemas/ShieldRuleMutation" } } } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "409": { $ref: "#/components/responses/Conflict" }
        "428": { description: Idempotency-Key is required, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
  /services/{service_ref}/shield/rules/{rule_id}:
    parameters:
      - $ref: "#/components/parameters/ServiceRef"
      - $ref: "#/components/parameters/ShieldRuleID"
    delete:
      operationId: rollbackShieldRule
      summary: Queue rollback of an owned Shield rule or its complete profile bundle
      x-required-scope: shield:write
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "202": { description: Rollback queued }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
        "428": { description: Idempotency-Key is required, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
  /services/{service_ref}/shield/presets/{preset_code}:
    parameters:
      - $ref: "#/components/parameters/ServiceRef"
      - { in: path, name: preset_code, required: true, schema: { type: string } }
    post:
      operationId: applyShieldPreset
      summary: Queue a versioned safe preset
      x-required-scope: shield:write
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "202": { description: Preset queued, content: { application/json: { schema: { $ref: "#/components/schemas/ShieldRuleMutation" } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
        "428": { description: Idempotency-Key is required, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
  /services/{service_ref}/shield/profiles/{profile_code}:
    parameters:
      - $ref: "#/components/parameters/ServiceRef"
      - { in: path, name: profile_code, required: true, schema: { type: string, enum: [off, low, default, strict] } }
    post:
      operationId: applyShieldProfile
      summary: Atomically queue a versioned protection profile bundle
      description: Replaces the previous customer-managed profile. A terminal partial failure automatically rolls back the complete bundle; off never changes emergency infrastructure controls.
      x-required-scope: shield:write
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "202": { description: Profile change queued, content: { application/json: { schema: { $ref: "#/components/schemas/ShieldProfileMutation" } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
        "428": { description: Idempotency-Key is required, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
  /services/{service_ref}/shield/traffic:
    parameters:
      - $ref: "#/components/parameters/ServiceRef"
    get:
      operationId: getShieldTraffic
      summary: Get bounded Advanced Shield traffic aggregates
      x-required-scope: shield:read
      parameters:
        - $ref: "#/components/parameters/ShieldWindowHours"
        - in: query
          name: group_by
          required: false
          schema: { type: string, enum: [source_ip, destination_ip, protocol, source_port, destination_port, tcp_flags], default: source_ip }
      responses:
        "200":
          description: Sanitized traffic aggregates, cached for a short bounded interval
          content: { application/json: { schema: { $ref: "#/components/schemas/ShieldTelemetry" } } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
  /services/{service_ref}/shield/protection-activity:
    parameters:
      - $ref: "#/components/parameters/ServiceRef"
    get:
      operationId: getShieldProtectionActivity
      summary: Get bounded Advanced Shield verdict aggregates
      description: Verdict categories are normalized by Atlas and must not be interpreted as attack classification.
      x-required-scope: shield:read
      parameters:
        - $ref: "#/components/parameters/ShieldWindowHours"
      responses:
        "200":
          description: Sanitized protection activity aggregates
          content: { application/json: { schema: { $ref: "#/components/schemas/ShieldTelemetry" } } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
  /services/{service_ref}/shield/export:
    parameters:
      - $ref: "#/components/parameters/ServiceRef"
    get:
      operationId: exportShieldAggregates
      summary: Export bounded Advanced Shield aggregates
      description: Returns sanitized traffic or protection-activity aggregates as JSON or formula-safe CSV. Provider placement and rule identifiers are never exported.
      x-required-scope: shield:read
      parameters:
        - $ref: "#/components/parameters/ShieldWindowHours"
        - in: query
          name: dataset
          required: true
          schema: { type: string, enum: [traffic, protection_activity] }
        - in: query
          name: format
          required: true
          schema: { type: string, enum: [json, csv] }
        - in: query
          name: group_by
          required: false
          schema: { type: string, enum: [source_ip, destination_ip, protocol, source_port, destination_port, tcp_flags], default: source_ip }
      responses:
        "200":
          description: Sanitized aggregate export
          content:
            application/json: { schema: { $ref: "#/components/schemas/ShieldTelemetry" } }
            text/csv: { schema: { type: string } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
  /services/{service_ref}/shield/rules/{rule_id}/evidence:
    parameters:
      - $ref: "#/components/parameters/ServiceRef"
      - $ref: "#/components/parameters/ShieldRuleID"
    get:
      operationId: getShieldRuleEvidence
      summary: Get bounded evidence for one owned Shield rule
      description: Extended metadata only; packet payloads, provider interfaces, credentials, and remote rule identifiers are never exposed.
      x-required-scope: shield:read
      parameters:
        - in: query
          name: limit
          schema: { type: integer, minimum: 1, maximum: 50, default: 50 }
        - in: query
          name: action
          schema: { type: string, enum: [drop, rate_limit] }
        - in: query
          name: cursor
          schema: { type: string, minLength: 1, maxLength: 2048 }
      responses:
        "200":
          description: Ownership-checked, sanitized evidence page
          content: { application/json: { schema: { $ref: "#/components/schemas/ShieldEvidence" } } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
  /plans:
    get:
      operationId: listPlans
      summary: List orderable public plans
      description: Returns every currently saleable category by default. Private, inactive, non-EUR, and region-ineligible plans are excluded.
      x-required-scope: catalog:read
      parameters:
        - $ref: "#/components/parameters/PageLimit"
        - $ref: "#/components/parameters/PlanCursor"
        - $ref: "#/components/parameters/DisplayCurrency"
        - in: query
          name: type
          schema:
            {
              type: string,
              enum: [kvm, dedicated, dedicated_server, game, storage_box, s3, ip_subnet],
            }
      responses:
        "200":
          description: Bounded saleable plan page in descending public-reference order
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PlanPage" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: phxak
  parameters:
    DisplayCurrency:
      name: display_currency
      in: query
      required: false
      description: Optional informational conversion. The account currency remains authoritative for orders, invoices, and ledger operations.
      schema: { type: string, enum: [EUR, USD, GBP] }
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: Unique stable key for one logical mutation. Keep it until a final response is stored.
      schema: { type: string, minLength: 1, maxLength: 128, pattern: "^[A-Za-z0-9_.:-]+$" }
    PageLimit:
      name: limit
      in: query
      required: false
      description: Page size; defaults to 50 and is bounded to 1 through 100.
      schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
    InvoiceCursor:
      name: cursor
      in: query
      required: false
      schema: { type: string, pattern: "^PHX-INV-[0-9A-F]+$" }
    OrderCursor:
      name: cursor
      in: query
      required: false
      schema: { type: string, pattern: "^PHX-ORD-[0-9A-F]+$" }
    TransactionCursor:
      name: cursor
      in: query
      required: false
      schema: { type: string, pattern: "^PHX-TXN-[0-9A-F]+$" }
    ServiceCursor:
      name: cursor
      in: query
      required: false
      schema: { type: string, pattern: "^PHX-SVC-[0-9A-F]+$" }
    PlanCursor:
      name: cursor
      in: query
      required: false
      schema: { type: string, pattern: "^PHX-PLAN-[0-9A-F]+$" }
    ServiceRef:
      name: service_ref
      in: path
      required: true
      schema: { type: string, pattern: "^PHX-SVC-[0-9A-F]+$" }
    ShieldRuleID:
      name: rule_id
      in: path
      required: true
      schema: { type: string, format: uuid }
    ShieldWindowHours:
      name: window_hours
      in: query
      required: false
      description: Requested trailing window in hours, bounded by the current Advanced entitlement.
      schema: { type: integer, minimum: 1, maximum: 720, default: 1 }
  schemas:
    Error:
      type: object
      required: [error]
      properties: { error: { type: string } }
      additionalProperties: false
    Account:
      type: object
      required:
        [
          account_ref,
          email,
          first_name,
          last_name,
          country_code,
          account_currency,
          preferred_locale,
          status,
        ]
      properties:
        account_ref: { type: string, pattern: "^PHX-ACCT-[0-9A-F]+$" }
        email: { type: string, format: email }
        first_name: { type: string }
        last_name: { type: string }
        country_code: { type: string }
        account_currency: { type: string }
        preferred_locale: { type: string }
        status: { type: string }
      additionalProperties: false
    Balance:
      type: object
      required: [amount, currency, account_currency, as_of]
      properties:
        amount: { type: number }
        currency: { type: string, enum: [EUR, USD, GBP] }
        account_currency: { type: string, enum: [EUR, USD, GBP] }
        as_of: { type: string, format: date-time }
        display: { $ref: "#/components/schemas/DisplayMoney" }
      additionalProperties: false
    Money:
      type: object
      required: [amount, currency]
      properties:
        amount: { type: number }
        currency: { type: string, enum: [EUR, USD, GBP] }
      additionalProperties: false
    DisplayMoney:
      type: object
      required: [amount, currency, rate, as_of]
      properties:
        amount: { type: number }
        currency: { type: string, enum: [EUR, USD, GBP] }
        rate: { type: number, exclusiveMinimum: 0, description: Units of display currency for one unit of account currency. }
        as_of: { type: string, format: date-time }
      additionalProperties: false
    Pagination:
      type: object
      required: [limit, has_more]
      properties:
        limit: { type: integer, minimum: 1, maximum: 100 }
        has_more: { type: boolean }
        next_cursor: { type: string, pattern: "^PHX-(INV|ORD|TXN|SVC|PLAN)-[0-9A-F]+$" }
      additionalProperties: false
    Invoice:
      type: object
      required: [invoice_ref, kind, transaction_kind, amount, fee, penalty, vat_amount, vat_rate, currency, status, issue_date, description, customer_payable]
      properties:
        invoice_ref: { type: string, pattern: "^PHX-INV-[0-9A-F]+$" }
        service_ref: { type: string, pattern: "^PHX-SVC-[0-9A-F]+$" }
        kind: { type: string }
        transaction_kind: { type: string }
        amount: { type: number }
        fee: { type: number }
        penalty: { type: number }
        vat_amount: { type: number }
        vat_rate: { type: number }
        currency: { type: string }
        status: { type: string }
        issue_date: { type: string, format: date-time }
        due_date: { type: string, format: date-time }
        paid_at: { type: string, format: date-time }
        description: { type: string }
        customer_payable: { type: boolean }
      additionalProperties: false
    InvoicePage:
      type: object
      required: [invoices, pagination, server_time]
      properties:
        invoices: { type: array, items: { $ref: "#/components/schemas/Invoice" } }
        pagination: { $ref: "#/components/schemas/Pagination" }
        server_time: { type: string, format: date-time }
      additionalProperties: false
    Order:
      type: object
      required: [order_ref, order_type, status, amount, currency, description]
      properties:
        order_ref: { type: string, pattern: "^PHX-ORD-[0-9A-F]+$" }
        service_ref: { type: string, pattern: "^PHX-SVC-[0-9A-F]+$" }
        invoice_ref: { type: string, pattern: "^PHX-INV-[0-9A-F]+$" }
        transaction_ref: { type: string, pattern: "^PHX-TXN-[0-9A-F]+$" }
        order_type: { type: string }
        status: { type: string }
        amount: { type: number }
        currency: { type: string }
        description: { type: string }
        created_at: { type: string, format: date-time }
      additionalProperties: false
    OrderPage:
      type: object
      required: [orders, pagination, server_time]
      properties:
        orders: { type: array, items: { $ref: "#/components/schemas/Order" } }
        pagination: { $ref: "#/components/schemas/Pagination" }
        server_time: { type: string, format: date-time }
      additionalProperties: false
    Transaction:
      type: object
      required: [transaction_ref, kind, amount, fee, penalty, vat_amount, currency, status, method, description, service_order, created_at]
      properties:
        transaction_ref: { type: string, pattern: "^PHX-TXN-[0-9A-F]+$" }
        invoice_ref: { type: string, pattern: "^PHX-INV-[0-9A-F]+$" }
        service_ref: { type: string, pattern: "^PHX-SVC-[0-9A-F]+$" }
        kind: { type: string }
        amount: { type: number }
        fee: { type: number }
        penalty: { type: number }
        vat_amount: { type: number }
        currency: { type: string }
        status: { type: string }
        method: { type: string }
        description: { type: string }
        service_order: { type: boolean }
        created_at: { type: string, format: date-time }
      additionalProperties: false
    TransactionPage:
      type: object
      required: [transactions, pagination, server_time]
      properties:
        transactions: { type: array, items: { $ref: "#/components/schemas/Transaction" } }
        pagination: { $ref: "#/components/schemas/Pagination" }
        server_time: { type: string, format: date-time }
      additionalProperties: false
    Service:
      type: object
      required: [service_ref, name, service_plan_name, type, status, auto_renew]
      properties:
        service_ref: { type: string, pattern: "^PHX-SVC-[0-9A-F]+$" }
        name: { type: string }
        display_name: { type: string }
        service_plan_name: { type: string }
        type: { type: string }
        status: { type: string }
        auto_renew: { type: boolean }
        primary_ip: { type: string }
        ip_addresses: { type: array, items: { type: string } }
        expires_at: { type: string }
      additionalProperties: true
    ServicePage:
      type: object
      required: [services, pagination, server_time]
      properties:
        services: { type: array, items: { $ref: "#/components/schemas/Service" } }
        pagination: { $ref: "#/components/schemas/Pagination" }
        server_time: { type: string, format: date-time }
      additionalProperties: false
    ServiceStats:
      type: object
      required: [total, active, by_type, by_status, server_time]
      properties:
        total: { type: integer, minimum: 0 }
        active: { type: integer, minimum: 0 }
        by_type:
          type: object
          additionalProperties: { type: integer, minimum: 0 }
        by_status:
          type: object
          additionalProperties: { type: integer, minimum: 0 }
        server_time: { type: string, format: date-time }
      additionalProperties: false
    ShieldEntitlement:
      type: object
      required: [tier, status, rules_per_ip, rules_per_account, rate_rules_per_ip, custom_rules, customer_api, history_days, analyzer_hours, mutations_blocked]
      properties:
        tier: { type: string, enum: [advanced] }
        status: { type: string, enum: [active, grace] }
        rules_per_ip: { type: integer, const: 30 }
        rules_per_account: { type: integer, const: 300 }
        rate_rules_per_ip: { type: integer, const: 10 }
        custom_rules: { type: boolean, const: true }
        customer_api: { type: boolean, const: true }
        history_days: { type: integer, const: 30 }
        analyzer_hours: { type: integer, const: 720 }
        grace_ends_at: { type: string, format: date-time }
        mutations_blocked: { type: boolean }
        mutation_blocked_reason: { type: string }
      additionalProperties: false
    ShieldPreset:
      type: object
      required: [code, version, name, description]
      properties:
        code: { type: string }
        version: { type: string }
        name: { type: string }
        description: { type: string }
      additionalProperties: false
    ShieldProfile:
      type: object
      required: [code, version, name, description, available]
      properties:
        code: { type: string, enum: [off, low, default, strict] }
        version: { type: string }
        name: { type: string }
        description: { type: string }
        available: { type: boolean }
      additionalProperties: false
    ShieldProfileState:
      type: object
      required: [code, state]
      properties:
        code: { type: string, enum: [off, low, default, strict] }
        version: { type: string }
        state: { type: string, enum: [off, applying, active, rolling_back, failed] }
      additionalProperties: false
    ShieldProfileMutation:
      type: object
      required: [profile]
      properties:
        profile: { $ref: "#/components/schemas/ShieldProfileState" }
      additionalProperties: false
    ShieldRuleIntent:
      type: object
      required: [name, mode, action, risk, protocol]
      properties:
        name: { type: string, minLength: 1, maxLength: 200 }
        mode: { type: string, enum: [observe, enforce] }
        action: { type: string, enum: [pass, drop, rate_limit] }
        risk: { type: string, enum: [low, medium, high, critical] }
        protocol: { type: string, enum: [any, tcp, udp, icmp, icmpv6] }
        source_cidr: { type: string }
        destination_port: { type: integer, minimum: 1, maximum: 65535 }
        tcp_flags_mask: { type: integer, minimum: 0, maximum: 255 }
        tcp_flags_value: { type: integer, minimum: 0, maximum: 255 }
        rate_pps: { type: integer, minimum: 100, maximum: 1000000000 }
        rate_bps: { type: integer, minimum: 1000000, maximum: 800000000000 }
        burst_packets: { type: integer, minimum: 64, maximum: 10000000 }
        duration_hours: { type: integer, minimum: 0, maximum: 8784 }
        explicit_broad_ack: { type: boolean }
      additionalProperties: false
    ShieldRule:
      type: object
      required: [id, service_ref, protected_ip, kind, tier, state, desired_enabled, created_at, updated_at]
      properties:
        id: { type: string, format: uuid, description: Atlas-local public rule identifier. }
        service_ref: { type: string, pattern: "^PHX-SVC-[0-9A-F]+$" }
        protected_ip: { type: string, format: ip }
        kind: { type: string, enum: [preset, custom, profile] }
        preset_code: { type: string }
        preset_version: { type: string }
        tier: { type: string, enum: [basic, advanced] }
        state: { type: string }
        desired_enabled: { type: boolean }
        expires_at: { type: string, format: date-time }
        last_error_code: { type: string }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
      additionalProperties: false
    ShieldOverview:
      type: object
      required: [entitlement, rules, presets, profiles, profile]
      properties:
        entitlement: { $ref: "#/components/schemas/ShieldEntitlement" }
        rules: { type: array, items: { $ref: "#/components/schemas/ShieldRule" } }
        presets: { type: array, items: { $ref: "#/components/schemas/ShieldPreset" } }
        profiles: { type: array, items: { $ref: "#/components/schemas/ShieldProfile" } }
        profile: { $ref: "#/components/schemas/ShieldProfileState" }
      additionalProperties: false
    ShieldCapabilities:
      type: object
      required: [entitlement, presets, profiles]
      properties:
        entitlement: { $ref: "#/components/schemas/ShieldEntitlement" }
        presets: { type: array, items: { $ref: "#/components/schemas/ShieldPreset" } }
        profiles: { type: array, items: { $ref: "#/components/schemas/ShieldProfile" } }
      additionalProperties: false
    ShieldRuleMutation:
      type: object
      required: [rule]
      properties:
        rule: { $ref: "#/components/schemas/ShieldRule" }
      additionalProperties: false
    ShieldRulePage:
      type: object
      required: [rules]
      properties:
        rules: { type: array, items: { $ref: "#/components/schemas/ShieldRule" } }
      additionalProperties: false
    ShieldTelemetryItem:
      type: object
      required: [key, packets, bytes]
      properties:
        key: { type: string }
        packets: { type: integer, minimum: 0 }
        bytes: { type: integer, minimum: 0 }
        samples: { type: integer, minimum: 0 }
      additionalProperties: false
    ShieldTelemetryTotals:
      type: object
      required: [packets, bytes]
      properties:
        packets: { type: integer, minimum: 0 }
        bytes: { type: integer, minimum: 0 }
        samples: { type: integer, minimum: 0 }
      additionalProperties: false
    ShieldTelemetry:
      type: object
      required: [start, end, items, totals, sampled, cache_seconds, telemetry]
      properties:
        start: { type: string, format: date-time }
        end: { type: string, format: date-time }
        group_by: { type: string }
        items: { type: array, maxItems: 100, items: { $ref: "#/components/schemas/ShieldTelemetryItem" } }
        totals: { $ref: "#/components/schemas/ShieldTelemetryTotals" }
        sampled: { type: boolean }
        cache_seconds: { type: integer, minimum: 1 }
        telemetry: { type: string, enum: [traffic, protection-activity] }
      additionalProperties: false
    ShieldEvidenceItem:
      type: object
      required: [observed_at, received_at, source_address, destination_address, source_port, destination_port, protocol, action, packet_length, sample_rate, generation]
      properties:
        observed_at: { type: string, format: date-time }
        received_at: { type: string, format: date-time }
        source_address: { type: string, format: ip }
        destination_address: { type: string, format: ip }
        source_port: { type: integer, minimum: 0, maximum: 65535 }
        destination_port: { type: integer, minimum: 0, maximum: 65535 }
        protocol: { type: string }
        action: { type: string, enum: [drop, rate_limit] }
        reason: { type: string }
        packet_length: { type: integer, minimum: 0, maximum: 65535 }
        sample_rate: { type: integer, minimum: 1 }
        generation: { type: integer, minimum: 0 }
      additionalProperties: false
    ShieldEvidence:
      type: object
      required: [rule_id, items]
      properties:
        rule_id: { type: string, format: uuid }
        items: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/ShieldEvidenceItem" } }
        next_cursor: { type: string, minLength: 1, maxLength: 2048 }
      additionalProperties: false
    Plan:
      type: object
      required: [plan_ref, name, code, service_type, purchase_url, account_price]
      properties:
        plan_ref: { type: string, pattern: "^PHX-PLAN-[0-9A-F]+$" }
        name: { type: string }
        code: { type: string }
        service_type: { type: string }
        purchase_url: { type: string, format: uri, pattern: "^https://" }
        account_price: { $ref: "#/components/schemas/PlanPricing" }
        display_price: { $ref: "#/components/schemas/PlanPricing" }
      additionalProperties: true
    PlanPricing:
      type: object
      required: [currency, base, final]
      properties:
        currency: { type: string, enum: [EUR, USD, GBP] }
        base: { type: number }
        final: { type: number }
        period_prices:
          type: object
          additionalProperties: { type: number }
      additionalProperties: false
    CreateOrderAddon:
      type: object
      required: [addon_ref, units]
      properties:
        addon_ref: { type: string, pattern: "^PHX-ADDON-[0-9A-F]+$" }
        units: { type: integer, minimum: 1 }
      additionalProperties: false
    CreateOrderRequest:
      type: object
      required: [type, plan_code]
      properties:
        type: { type: string, enum: [kvm, dedicated, dedicated_server, game, storage_box, s3, ip_subnet] }
        plan_code: { type: string, minLength: 1, maxLength: 128 }
        os_name: { type: string, maxLength: 160 }
        windows: { type: boolean, default: false }
        ipv6_only: { type: boolean, default: false }
        os_image_ref: { type: string, pattern: "^PHX-OS-[0-9A-F]+$" }
        period_ref: { type: string, pattern: "^PHX-PERIOD-[0-9A-F]+$" }
        region_ref: { type: string, pattern: "^PHX-REGION-[0-9A-F]+$" }
        addons: { type: array, maxItems: 64, items: { $ref: "#/components/schemas/CreateOrderAddon" } }
        quote_version: { type: string, maxLength: 200, description: Optional quote version returned by the catalog/order configuration flow. The server always reprices authoritatively. }
      additionalProperties: false
    CreatedService:
      type: object
      required: [name, service_plan_name, type, status, monthly_price_eur]
      properties:
        name: { type: string }
        display_name: { type: string }
        service_plan_name: { type: string }
        type: { type: string }
        status: { type: string }
        monthly_price_eur: { type: number, description: Legacy normalized reporting field; do not use as the charged amount. }
        location: { type: string }
        cores: { type: integer }
        ram_gb: { type: integer }
        disk_gb: { type: integer }
        bandwidth_mbps: { type: integer }
        os_name: { type: string }
        last_period_unit: { type: string }
        last_period_amount: { type: integer }
        expires_at: { type: string }
      additionalProperties: false
    CreateOrderResponse:
      type: object
      required: [service_ref, service, balance, idempotent_replayed, server_time]
      properties:
        service_ref: { type: string, pattern: "^PHX-SVC-[0-9A-F]+$" }
        service: { $ref: "#/components/schemas/CreatedService" }
        balance: { $ref: "#/components/schemas/Money" }
        idempotent_replayed: { type: boolean }
        server_time: { type: string, format: date-time }
      additionalProperties: false
    PlanPage:
      type: object
      required: [plans, pagination, server_time]
      properties:
        plans: { type: array, items: { $ref: "#/components/schemas/Plan" } }
        pagination: { $ref: "#/components/schemas/Pagination" }
        server_time: { type: string, format: date-time }
      additionalProperties: false
  responses:
    BadRequest:
      description: Invalid path, service reference, or JSON request body
      content:
        { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    Unauthorized:
      description: Missing or invalid API key
      content:
        { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    Forbidden:
      description: Insufficient scope or source IP not allowed
      content:
        { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    NotFound:
      description: Service does not exist or does not belong to the account
      content:
        { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    Conflict:
      description: Account or service lifecycle state no longer permits the requested mutation
      content:
        application/json:
          schema:
            type: object
            required: [error]
            properties:
              error: { type: string }
              status: { type: string }
            additionalProperties: false
