openapi: 3.1.0
info:
  title: MIGO IP Customer API v1
  version: 1.1.0
  description: |
    Workspace-scoped proxy, traffic, commerce and reporting API for customer
    integrations. Authenticate with one tenant API key sent as
    `Authorization: Bearer <key>`; no cookie, CSRF token, signature or tenant id
    is needed or accepted. Keys are issued only from an authenticated customer
    portal session and are bound to that tenant.

    Static instances support effective pricing, purchase and optimization-upgrade
    quotes, orders, credentials, notes, auto-renew settings and single/batch renewal.
    Dynamic proxies support standard and optimization traffic purchases, saved
    direct/optimized connections, balances and usage analytics. Wallet/recharge,
    observability and referral operations share the portal's authorization and
    release policy. Customer webhooks deliver signed asynchronous business events.
    Expired instances lose access with no retention window. Supplier provisioning
    retries, cancellation/refunds and forwarder administration remain operator-only.

    Rules that apply to every operation:
    - Money is integer fen encoded as strings (never binary floating point).
    - Timestamps are UTC ISO 8601 (RFC 3339).
    - Errors use the shared `{code, request_id}` envelope; surface `request_id`
      for support. Every response is `Cache-Control: no-store`.
    - Authenticated keys are limited to 120 requests/minute per key. A limited
      request returns 429 with `Retry-After`.
    - Spending is quote then confirm: a quote freezes its price; order and
      renewal submission require an `Idempotency-Key`. An exact retry returns
      the same result without spending twice; a changed body conflicts.
    - The simulator scenario is never accepted on this surface.
    - Base permission is an upper bound; each operation lists its exact required
      scopes in x-required-scopes. New read keys omit credentials:read by default.
      Legacy keys retain their pre-policy permission-only behavior.
    - Expiry and source-IP policy apply on every request. Wallet spending caps
      are lifetime gross API debits; refunds do not restore capacity. Capped keys
      cannot opt into auto-renew, including when accepting an older quote.

servers:
  - url: https://portal.migoip.com
    description: Production application API; the marketing site migoip.com does not host authenticated API routes.
  - url: http://localhost:8080
    description: Local development API. Use the configured application origin for previews.
security:
  - apiKey: []
tags:
  - name: Account
  - name: Catalog
  - name: Orders
  - name: Proxies
x-webhook-delivery:
  content-type: application/json
  body-schema: '#/components/schemas/WebhookEvent'
  headers: [X-Webhook-Id, X-Webhook-Event, X-Webhook-Delivery, X-Webhook-Timestamp, X-Webhook-Signature]
  signature: 'v1=<lowercase hex HMAC-SHA256 of Unix timestamp text + period + exact raw request body; key is the complete whsec_ secret>'
  receiver: 'Verify in constant time, enforce timestamp freshness, then deduplicate event ID. Deliveries may arrive out of order.'
  retries: '8 attempts per round; exponential delay from 30 seconds capped at 1 hour; at most 3 explicit retry rounds.'
  targets: 'Public HTTPS on port 443 only; DNS validation and pinned connection on every attempt; no redirects or environment proxy.'
paths:
  /api/customer/v1/balance:
    get:
      x-required-scopes: ["wallet:read"]
      tags: [Account]
      summary: Wallet balance for the key's tenant
      description: Requires the scopes listed in x-required-scopes. Returns the same wallet DTO the portal uses.
      responses:
        '200':
          description: Wallet balance
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Wallet'}
        default: {$ref: '#/components/responses/Error'}
  /api/customer/v1/catalog:
    get:
      x-required-scopes: ["catalog:read"]
      tags: [Catalog]
      summary: Effective offers for the key's tenant
      description: |
        Requires the scopes listed in x-required-scopes. Exactly the tenant-effective type ->
        country -> location price resolution the portal uses, with exact terms and
        prices. Internal pricing provenance is not exposed.
      responses:
        '200':
          description: Effective offer list
          content:
            application/json:
              schema: {$ref: '#/components/schemas/CatalogPage'}
        default: {$ref: '#/components/responses/Error'}
  /api/customer/v1/quotes:
    post:
      x-required-scopes: ["orders:write"]
      tags: [Catalog]
      summary: Create an immutable purchase quote
      description: |
        Write permission required. Freezes the effective unit and total price for
        the requested quantity and protocol. The response never
        contains the simulator scenario or any supplier field. Unknown body
        fields (including `tenant_id` and `scenario`) are rejected with 422.
        The price_id must identify a named-location offer. Country/product rules
        and country-only synthesized IDs return 422 LOCATION_REQUIRED.
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/QuoteRequest'}
      responses:
        '200':
          description: Frozen quote
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Quote'}
        default: {$ref: '#/components/responses/Error'}
  /api/customer/v1/orders:
    post:
      x-required-scopes: ["orders:write", "wallet:spend"]
      tags: [Orders]
      summary: Confirm a quote and place the order
      description: |
        Write permission required. Charges the wallet and queues exactly one
        fulfillment operation, in one transaction. Requires `Idempotency-Key`;
        an exact replay returns the original order and operation without another
        debit, and a concurrent duplicate is collapsed to the same order.
        Quotes without a named location return 422 LOCATION_REQUIRED before debit.
      parameters: [{$ref: '#/components/parameters/Idempotency'}]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [quote_id]
              properties:
                quote_id: {type: string, minLength: 1, maxLength: 128}
      responses:
        '202':
          description: Accepted (or replayed) order with its queued operation
          content:
            application/json:
              schema: {$ref: '#/components/schemas/OrderAccepted'}
        default: {$ref: '#/components/responses/Error'}
    get:
      x-required-scopes: ["orders:read"]
      tags: [Orders]
      summary: List the tenant's orders
      description: Requires the scopes listed in x-required-scopes. Newest first, bounded pagination.
      parameters:
        - {$ref: '#/components/parameters/Page'}
        - {$ref: '#/components/parameters/PageSize'}
      responses:
        '200':
          description: Order page
          content:
            application/json:
              schema: {$ref: '#/components/schemas/OrderPage'}
        default: {$ref: '#/components/responses/Error'}
  /api/customer/v1/orders/{id}:
    get:
      x-required-scopes: ["orders:read"]
      tags: [Orders]
      summary: Read one order with item, operation and service trace
      description: Requires the scopes listed in x-required-scopes. A foreign or unknown order id is 404; no other tenant's record is visible.
      parameters: [{$ref: '#/components/parameters/ID'}]
      responses:
        '200':
          description: Order detail
          content:
            application/json:
              schema: {$ref: '#/components/schemas/OrderDetail'}
        default: {$ref: '#/components/responses/Error'}
  /api/customer/v1/operations/{id}:
    get:
      x-required-scopes: ["orders:read"]
      tags: [Orders]
      summary: Poll one fulfillment operation
      description: |
        Requires the scopes listed in x-required-scopes. Bounded status only: id, kind, state,
        attempt count and timestamps. Supplier identifiers, leases, modes and raw
        error codes are never exposed; any internal failure is reported as
        `OPERATION_NEEDS_ATTENTION`.
      parameters: [{$ref: '#/components/parameters/ID'}]
      responses:
        '200':
          description: Operation status
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Operation'}
        default: {$ref: '#/components/responses/Error'}
  /api/customer/v1/proxies:
    get:
      x-required-scopes: ["proxies:read"]
      tags: [Proxies]
      summary: List static instance summaries with renewal/expiry status
      description: |
        Requires the scopes listed in x-required-scopes. Bounded pagination; optional product and
        term-state filters, a literal free-text search (which may also match
        notes) and an exact IP filter. Each row carries the embedded lifecycle
        guard and its instance settings (note, optimistic version and auto-renew
        state), which a legacy row without settings reads as empty note, version
        1 and auto-renew off.
      parameters:
        - {$ref: '#/components/parameters/Page'}
        - {$ref: '#/components/parameters/PageSize'}
        - {name: product, in: query, schema: {enum: [static_residential, static_datacenter]}}
        - {name: term_state, in: query, schema: {enum: [active, expired]}, description: Recorded dates only; not a live cutoff claim}
        - {name: q, in: query, schema: {type: string, maxLength: 128}, description: Literal case-insensitive free-text search; may also match instance notes}
        - {name: ips, in: query, schema: {type: string, maxLength: 4096}, description: Comma-separated normalized exact IPv4/IPv6 addresses, at most 100, no empty or invalid tokens; intersects the other filters}
      responses:
        '200':
          description: Instance page
          content:
            application/json:
              schema: {$ref: '#/components/schemas/ProxyPage'}
        default: {$ref: '#/components/responses/Error'}
  /api/customer/v1/proxies/export:
    get:
      x-required-scopes: ["proxies:read", "credentials:read"]
      tags: [Proxies]
      summary: Export currently usable plaintext connections
      description: |
        Requires the scopes listed in x-required-scopes. Returns `text/plain`: one LF-terminated
        `host:port:username:password` tuple per line for a single explicit
        `protocol`, at most `page_size` lines. An IPv6 host is bracketed. Only
        connections that are usable right now are exported: an expired or
        otherwise credential-free instance contributes no line, so an empty
        export is a 200 with an empty body, never JSON. A genuine read failure
        fails the whole request instead of silently dropping an instance.
        Pagination metadata rides on the response headers, not the body. Never
        log the returned values. access_mode defaults to preferred. Explicit direct or
        optimized selection never falls back to the other route. The exact ips
        filter intersects product, term and search filters.
      parameters:
        - {name: access_mode, in: query, schema: {type: string, enum: [preferred, direct, optimized], default: preferred}, description: Explicit route selection; unavailable routes produce no line}
        - {name: ips, in: query, schema: {type: string}, description: Comma-separated exact IPv4/IPv6 values; at most 100 normalized unique addresses. Empty/invalid tokens and repeated ips parameters are rejected.}
        - {name: protocol, in: query, required: true, schema: {enum: [http, socks5]}, description: HTTP maps to the stored HTTPS/CONNECT representation}
        - {$ref: '#/components/parameters/Page'}
        - {$ref: '#/components/parameters/PageSize'}
        - {name: product, in: query, schema: {enum: [static_residential, static_datacenter]}}
        - {name: term_state, in: query, schema: {enum: [active, expired]}}
        - {name: q, in: query, schema: {type: string, maxLength: 128}}
      responses:
        '200':
          description: |
            One bounded page of plaintext tuples, one per line. The body is
            empty when no instance in the page has a usable connection for the
            requested protocol.
          headers:
            X-Total-Count: {schema: {type: integer, minimum: 0}, description: Total matching instances across all pages}
            X-Page: {schema: {type: integer, minimum: 1}, description: Current page number}
            X-Page-Size: {schema: {type: integer, minimum: 1, maximum: 100}, description: Requested page size}
          content:
            text/plain:
              schema: {type: string, description: 'LF-terminated host:port:username:password lines; sensitive, never log'}
              example: "203.0.113.10:8443:acme-user:secret\n"
        default: {$ref: '#/components/responses/Error'}
  /api/customer/v1/proxies/{id}:
    get:
      x-required-scopes: ["proxies:read", "credentials:read"]
      tags: [Proxies]
      summary: Read one instance with its permitted connection credentials
      description: |
        Requires the scopes listed in x-required-scopes. Returns the instance plus the per-protocol
        connection tuples the backend permits. An expired instance exposes no
        usable connection. A foreign or unknown id is 404.
      parameters: [{$ref: '#/components/parameters/ID'}]
      responses:
        '200':
          description: Instance detail
          content:
            application/json:
              schema: {$ref: '#/components/schemas/ProxyDetail'}
        default: {$ref: '#/components/responses/Error'}
  /api/customer/v1/proxies/{id}/settings:
    patch:
      x-required-scopes: ["proxies:write"]
      tags: [Proxies]
      summary: Update one instance's non-financial settings
      description: |
        Write permission required. Applies one optimistic-concurrency settings
        update: an expected version plus the optional note and/or auto-renew
        opt-in; omitted fields stay unchanged. Bounded note validation (at most
        200 Unicode characters). Opt-in requires a verified tenant and an active,
        unexpired, unrefunded instance with renewal support and an original purchase
        duration. Disabling and note-only edits do not require renewal eligibility.
        Never a financial command: enabling auto-renew
        has no wallet effect until the scheduler actually charges, which needs a
        current active/verified tenant and an active authorizing membership.
        A stale version is a 409 conflict; a foreign or unknown id is 404.
      parameters: [{$ref: '#/components/parameters/ID'}]
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/SettingsRequest'}
      responses:
        '200':
          description: Updated instance detail (credentials omitted from a settings write)
          content:
            application/json:
              schema: {$ref: '#/components/schemas/ProxyDetail'}
        default: {$ref: '#/components/responses/Error'}
  /api/customer/v1/proxies/{id}/renewal-quotes:
    post:
      x-required-scopes: ["orders:write"]
      tags: [Proxies]
      summary: Create an immutable renewal quote
      description: |
        Write permission required. Binds the service revision, effective price
        version and the exact extension arithmetic from the
        recorded expiry. Read-only: no debit and no provider mutation. An expired,
        unsupported or manual non-renewable instance returns the existing guard
        error and no quote.
      parameters: [{$ref: '#/components/parameters/ID'}]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [price_id]
              properties:
                price_id: {type: string, minLength: 1, maxLength: 128}
      responses:
        '201':
          description: Renewal quote snapshot
          content:
            application/json:
              schema: {$ref: '#/components/schemas/RenewalQuote'}
        default: {$ref: '#/components/responses/Error'}
  /api/customer/v1/proxies/{id}/renewals:
    post:
      x-required-scopes: ["orders:write", "wallet:spend"]
      tags: [Proxies]
      summary: Pay for a renewal
      description: |
        Write permission required. Debits exactly once with the required
        `Idempotency-Key`: wallet debit and renewal row share one transaction and
        the quote stays bound to the paying actor. An exact replay returns the
        original renewal; a used quote or changed body conflicts without a debit.
        The provider mutation is queued for the worker, never performed inline.
      parameters: [{$ref: '#/components/parameters/ID'}, {$ref: '#/components/parameters/Idempotency'}]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [quote_id]
              properties:
                quote_id: {type: string, minLength: 1, maxLength: 128}
      responses:
        '202':
          description: Accepted or replayed renewal
          content:
            application/json:
              schema: {$ref: '#/components/schemas/RenewalWrap'}
        default: {$ref: '#/components/responses/Error'}
  /api/customer/v1/proxies/{id}/renewals/{renewal}:
    get:
      x-required-scopes: ["orders:read"]
      tags: [Proxies]
      summary: Read one renewal status
      description: Requires the scopes listed in x-required-scopes. Only the key tenant's own renewal is visible.
      parameters: [{$ref: '#/components/parameters/ID'}, {name: renewal, in: path, required: true, schema: {type: string}}]
      responses:
        '200':
          description: Renewal status
          content:
            application/json:
              schema: {$ref: '#/components/schemas/RenewalWrap'}
        default: {$ref: '#/components/responses/Error'}
  /api/v1/tenants/{tenant}/api-keys:
    parameters: [{$ref: '#/components/parameters/Tenant'}]
    get:
      tags: [Account]
      summary: List tenant API keys (portal session only)
      description: |
        Customer cookie session of an active admin of an active, non-removed
        tenant. Any `Authorization` header (development bearer token or API key)
        is rejected with 403; this surface is never reachable with a key. Returns
        secret-free summaries only.
      security: [{portalSession: []}]
      parameters: [{$ref: '#/components/parameters/Origin'}, {$ref: '#/components/parameters/CSRFHeader'}]
      responses:
        '200':
          description: Key summaries
          content:
            application/json:
              schema: {$ref: '#/components/schemas/APIKeyList'}
        default: {$ref: '#/components/responses/Error'}
    post:
      tags: [Account]
      summary: Create one tenant API key (portal session only)
      description: |
        Customer cookie session of an active admin, with the exact Origin and the
        session CSRF token. The raw secret is returned exactly once, here, and is
        never listed or recoverable. At most 10 active keys per tenant; a racing
        create that would exceed the ceiling is refused under a lock.
        Omitted/null scopes create explicit defaults: all 18 for write; read-only
        scopes excluding credentials:read for read. An empty list is rejected.
        Expiry must be in the future; CIDRs are canonicalized. Source IP follows
        configured trusted proxies, never untrusted caller headers.
      security: [{portalSession: []}]
      parameters: [{$ref: '#/components/parameters/Origin'}, {$ref: '#/components/parameters/CSRFHeader'}]
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/APIKeyCreateInput'}
      responses:
        '201':
          description: Created key and its one-time secret
          content:
            application/json:
              schema: {$ref: '#/components/schemas/APIKeyCreated'}
        default: {$ref: '#/components/responses/Error'}
  /api/v1/tenants/{tenant}/api-keys/{id}:
    parameters: [{$ref: '#/components/parameters/Tenant'}, {$ref: '#/components/parameters/ID'}]
    patch:
      tags: [Account]
      summary: Update API key access policy (portal session only)
      description: |
        Cookie session plus Origin and CSRF required. Supply expected_version;
        stale policy returns VERSION_CONFLICT (409). Name may change; permission
        may stay unchanged or narrow write to read, never widen read to write.
        Omitted fields remain unchanged. Null clears expiry/spend limit; empty
        allowed_cidrs clears IP restrictions. Scopes must be a nonempty explicit
        array; null cannot restore legacy unrestricted access. The cap cannot
        fall below spent_fen (SPEND_LIMIT_BELOW_SPENT). Revoked keys cannot change.
      security: [{portalSession: []}]
      parameters: [{$ref: '#/components/parameters/Origin'}, {$ref: '#/components/parameters/CSRFHeader'}]
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/APIKeyPolicyInput'}
      responses:
        '200':
          description: Updated secret-free key policy
          content:
            application/json:
              schema: {$ref: '#/components/schemas/APIKeyRevoked'}
        default: {$ref: '#/components/responses/Error'}
    delete:
      tags: [Account]
      summary: Revoke one tenant API key (portal session only)
      description: |
        Customer cookie session of an active admin. Revocation takes effect on the
        next request. Repeated revocation is harmless and returns the same
        summary. A key of another tenant is 404.
      security: [{portalSession: []}]
      parameters: [{$ref: '#/components/parameters/Origin'}, {$ref: '#/components/parameters/CSRFHeader'}]
      responses:
        '200':
          description: Revoked key summary
          content:
            application/json:
              schema: {$ref: '#/components/schemas/APIKeyRevoked'}
        default: {$ref: '#/components/responses/Error'}
  "/api/customer/v1/proxies/{id}/optimization-quotes":
    "post":
      "tags": ["Proxies"]
      "summary": "Quote optimization for an existing static IP"
      "description": "Requires write permission and orders:write. Confirm through POST /orders. Existing service expiry is unchanged. Coupons are not supported on upgrades (COUPON_UNAVAILABLE). Unknown body fields are refused."
      "responses":
        "200":
          "description": "Quote optimization for an existing static IP"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Quote"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/ID"}]
      "requestBody":
        "required": false
        "content":
          "application/json":
            "schema":
              "type": "object"
              "additionalProperties": false
              "properties":
                "coupon_code":
                  "type": "string"
              "required": []
      "x-required-scopes": ["orders:write"]
  "/api/customer/v1/proxies/batch-renewal-quotes":
    "post":
      "tags": ["Proxies"]
      "summary": "Quote an atomic batch renewal"
      "description": "Requires write permission and orders:write. Accepts 1–100 unique owned service IDs and their exact renewal price IDs. Any ineligible item rejects the entire quote."
      "responses":
        "201":
          "description": "Quote an atomic batch renewal"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/BatchQuote"
        "default":
          "$ref": "#/components/responses/Error"
      "requestBody":
        "required": true
        "content":
          "application/json":
            "schema":
              "$ref": "#/components/schemas/BatchQuoteInput"
      "x-required-scopes": ["orders:write"]
  "/api/customer/v1/proxies/batch-renewals":
    "post":
      "tags": ["Proxies"]
      "summary": "Confirm a batch renewal"
      "description": "Requires write permission and orders:write, wallet:spend. One atomic wallet debit; each IP subsequently has its own fulfillment outcome. Reuse the Idempotency-Key on an exact retry."
      "responses":
        "202":
          "description": "Confirm a batch renewal"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/BatchRenewal"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/Idempotency"}]
      "requestBody":
        "required": true
        "content":
          "application/json":
            "schema":
              "type": "object"
              "additionalProperties": false
              "properties":
                "quote_id":
                  "type": "string"
              "required": ["quote_id"]
      "x-required-scopes": ["orders:write", "wallet:spend"]
  "/api/customer/v1/proxies/batch-renewals/{batch}":
    "get":
      "tags": ["Proxies"]
      "summary": "Read a batch renewal"
      "description": "Requires orders:read. Foreign and unknown IDs return 404. Internal failure codes are returned as OPERATION_NEEDS_ATTENTION."
      "responses":
        "200":
          "description": "Read a batch renewal"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/BatchRenewal"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"name": "batch", "in": "path", "required": true, "schema": {"type": "string"}}]
      "x-required-scopes": ["orders:read"]
  "/api/customer/v1/wallet/summary":
    "get":
      "tags": ["Wallet"]
      "summary": "Read wallet totals"
      "description": "Requires wallet:read. Posted balance, current-month spending and latest recharge use the same consistent snapshot as the portal."
      "responses":
        "200":
          "description": "Read wallet totals"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/WalletSummary"
        "default":
          "$ref": "#/components/responses/Error"
      "x-required-scopes": ["wallet:read"]
  "/api/customer/v1/wallet/ledger":
    "get":
      "tags": ["Wallet"]
      "summary": "List posted wallet movements"
      "description": "Requires wallet:read. Pending payment intents are listed separately under recharges."
      "responses":
        "200":
          "description": "List posted wallet movements"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/LedgerPage"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/Page"}, {"$ref": "#/components/parameters/PageSize"}, {"name": "q", "in": "query", "schema": {"type": "string"}}, {"name": "kind", "in": "query", "schema": {"type": "string"}}, {"name": "from", "in": "query", "schema": {"type": "string"}, "description": "Inclusive RFC3339 or YYYY-MM-DD UTC lower bound."}, {"name": "to", "in": "query", "schema": {"type": "string"}, "description": "Exclusive RFC3339 or YYYY-MM-DD UTC upper bound. Must be later than from."}]
      "x-required-scopes": ["wallet:read"]
  "/api/customer/v1/order-history":
    "get":
      "tags": ["Orders"]
      "summary": "List unified order history"
      "description": "Requires orders:read. Includes static orders, optimization upgrades, renewals and dynamic traffic purchases."
      "responses":
        "200":
          "description": "List unified order history"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/OrderHistoryPage"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/Page"}, {"$ref": "#/components/parameters/PageSize"}, {"name": "q", "in": "query", "schema": {"type": "string"}}, {"name": "status", "in": "query", "schema": {"type": "string"}}, {"name": "product", "in": "query", "schema": {"type": "string", "enum": ["static_residential", "static_datacenter", "dynamic_residential"]}}, {"name": "from", "in": "query", "schema": {"type": "string"}, "description": "Inclusive RFC3339 or YYYY-MM-DD UTC lower bound."}, {"name": "to", "in": "query", "schema": {"type": "string"}, "description": "Exclusive RFC3339 or YYYY-MM-DD UTC upper bound. Must be later than from."}]
      "x-required-scopes": ["orders:read"]
  "/api/customer/v1/recharges/config":
    "get":
      "tags": ["Recharges"]
      "summary": "Read available recharge methods"
      "description": "Requires recharges:read. Availability and exact fees follow deployment payment policy. A method being listed does not imply it is enabled."
      "responses":
        "200":
          "description": "Read available recharge methods"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/RechargeConfig"
        "default":
          "$ref": "#/components/responses/Error"
      "x-required-scopes": ["recharges:read"]
  "/api/customer/v1/recharges":
    "get":
      "tags": ["Recharges"]
      "summary": "List payment intents"
      "description": "Requires recharges:read. Only limit, offset and state are accepted, each once. Lists omit actionable payment instructions."
      "responses":
        "200":
          "description": "List payment intents"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/RechargeIntentPage"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"name": "limit", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 100, "default": 20}}, {"name": "offset", "in": "query", "schema": {"type": "integer", "minimum": 0, "maximum": 1000000, "default": 0}}, {"name": "state", "in": "query", "schema": {"type": "string", "enum": ["queued", "running", "waiting", "needs_attention", "succeeded"]}}]
      "x-required-scopes": ["recharges:read"]
    "post":
      "tags": ["Recharges"]
      "summary": "Create a recharge payment intent"
      "description": "Requires write permission and recharges:write. New payments require enabled wallet top-up policy. Creating an intent does not credit the wallet; credit follows verified settlement. Repeat an exact command with the same Idempotency-Key. Legacy asset_units input is accepted only for an existing USDT receipt replay; new USDT commands remain disabled."
      "responses":
        "202":
          "description": "Create a recharge payment intent"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/RechargeIntent"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/Idempotency"}]
      "requestBody":
        "required": true
        "content":
          "application/json":
            "schema":
              "oneOf": [{"$ref": "#/components/schemas/RechargeCreateInput"}, {"$ref": "#/components/schemas/LegacyRechargeReplayInput"}]
      "x-required-scopes": ["recharges:write"]
  "/api/customer/v1/recharges/{id}":
    "get":
      "tags": ["Recharges"]
      "summary": "Read payment intent and instructions"
      "description": "Requires recharges:read. Payment instructions, including a card client_secret when applicable, appear only for the owning actor while the intent is actionable. These are sensitive single-payment instructions, not confirmation of payment."
      "responses":
        "200":
          "description": "Read payment intent and instructions"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/RechargeIntent"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/ID"}]
      "x-required-scopes": ["recharges:read"]
  "/api/customer/v1/recharge-history":
    "get":
      "tags": ["Recharges"]
      "summary": "List recharge and manual adjustment history"
      "description": "Requires recharges:read. Expired uncredited payments have customer display state closed; later verified settlement may still succeed."
      "responses":
        "200":
          "description": "List recharge and manual adjustment history"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/RechargeHistoryPage"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/Page"}, {"$ref": "#/components/parameters/PageSize"}, {"name": "q", "in": "query", "schema": {"type": "string"}}, {"name": "state", "in": "query", "schema": {"type": "string"}}, {"name": "channel", "in": "query", "schema": {"type": "string"}}]
      "x-required-scopes": ["recharges:read"]
  "/api/customer/v1/observability/metrics":
    "get":
      "tags": ["Observability"]
      "summary": "Read workspace metrics"
      "description": "Requires metrics:read. Reports only traffic observed by optimized forwarders. Empty, disabled, stale and unavailable states are distinct from zero traffic. Time windows and sample counts are bounded. Foreign IP IDs return 404 before querying telemetry."
      "responses":
        "200":
          "description": "Read workspace metrics"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/ObservabilityMetricsResponse"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/ObservabilityStart"}, {"$ref": "#/components/parameters/ObservabilityEnd"}, {"$ref": "#/components/parameters/ObservabilityStepSeconds"}]
      "x-required-scopes": ["metrics:read"]
  "/api/customer/v1/observability/logs":
    "get":
      "tags": ["Observability"]
      "summary": "Read workspace logs"
      "description": "Requires metrics:read. Reports only traffic observed by optimized forwarders. Empty, disabled, stale and unavailable states are distinct from zero traffic. Time windows and sample counts are bounded. Foreign IP IDs return 404 before querying telemetry."
      "responses":
        "200":
          "description": "Read workspace logs"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/ObservabilityLogResponse"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/ObservabilityStart"}, {"$ref": "#/components/parameters/ObservabilityEnd"}, {"$ref": "#/components/parameters/ObservabilityLimit"}, {"$ref": "#/components/parameters/ObservabilityCursor"}, {"$ref": "#/components/parameters/ObservabilityProtocol"}, {"$ref": "#/components/parameters/ObservabilityTransport"}, {"$ref": "#/components/parameters/ObservabilityOutcome"}, {"$ref": "#/components/parameters/ObservabilitySourceIP"}, {"$ref": "#/components/parameters/ObservabilityDestinationHost"}]
      "x-required-scopes": ["metrics:read"]
  "/api/customer/v1/proxies/{id}/observability/metrics":
    "get":
      "tags": ["Observability"]
      "summary": "Read one IP’s metrics"
      "description": "Requires metrics:read. Reports only traffic observed by optimized forwarders. Empty, disabled, stale and unavailable states are distinct from zero traffic. Time windows and sample counts are bounded. Foreign IP IDs return 404 before querying telemetry."
      "responses":
        "200":
          "description": "Read one IP’s metrics"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/ObservabilityMetricsResponse"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/ID"}, {"$ref": "#/components/parameters/ObservabilityStart"}, {"$ref": "#/components/parameters/ObservabilityEnd"}, {"$ref": "#/components/parameters/ObservabilityStepSeconds"}]
      "x-required-scopes": ["metrics:read"]
  "/api/customer/v1/proxies/{id}/observability/logs":
    "get":
      "tags": ["Observability"]
      "summary": "Read one IP’s logs"
      "description": "Requires metrics:read. Reports only traffic observed by optimized forwarders. Empty, disabled, stale and unavailable states are distinct from zero traffic. Time windows and sample counts are bounded. Foreign IP IDs return 404 before querying telemetry."
      "responses":
        "200":
          "description": "Read one IP’s logs"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/ObservabilityLogResponse"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/ID"}, {"$ref": "#/components/parameters/ObservabilityStart"}, {"$ref": "#/components/parameters/ObservabilityEnd"}, {"$ref": "#/components/parameters/ObservabilityLimit"}, {"$ref": "#/components/parameters/ObservabilityCursor"}, {"$ref": "#/components/parameters/ObservabilityProtocol"}, {"$ref": "#/components/parameters/ObservabilityTransport"}, {"$ref": "#/components/parameters/ObservabilityOutcome"}, {"$ref": "#/components/parameters/ObservabilitySourceIP"}, {"$ref": "#/components/parameters/ObservabilityDestinationHost"}]
      "x-required-scopes": ["metrics:read"]
  "/api/customer/v1/referrals":
    "get":
      "tags": ["Referrals"]
      "summary": "Read referral summary"
      "description": "Requires referrals:read. Only the workspace’s own referral program and masked invitee information are exposed."
      "responses":
        "200":
          "description": "Read referral summary"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/ReferralSummary"
        "default":
          "$ref": "#/components/responses/Error"
      "x-required-scopes": ["referrals:read"]
  "/api/customer/v1/referrals/link":
    "post":
      "tags": ["Referrals"]
      "summary": "Create, rotate or disable referral link"
      "description": "Requires write permission and referrals:write. Uses an optimistic version and a durable Idempotency-Key. Signup must be enabled to create or rotate a link; disabling remains available."
      "responses":
        "200":
          "description": "Create, rotate or disable referral link"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/ReferralSummary"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/Idempotency"}]
      "requestBody":
        "required": true
        "content":
          "application/json":
            "schema":
              "type": "object"
              "additionalProperties": false
              "properties":
                "action":
                  "type": "string"
                  "enum": ["create", "rotate", "disable"]
                "expected_version":
                  "type": "integer"
                  "minimum": 0
              "required": ["action", "expected_version"]
      "x-required-scopes": ["referrals:write"]
  "/api/customer/v1/referrals/invitees":
    "get":
      "tags": ["Referrals"]
      "summary": "List referral invitees"
      "description": "Requires referrals:read. Server controls attribution, identity masking and reward amounts."
      "responses":
        "200":
          "description": "List referral invitees"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/ReferralInviteePage"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/Page"}, {"$ref": "#/components/parameters/PageSize"}]
      "x-required-scopes": ["referrals:read"]
  "/api/customer/v1/referrals/rewards":
    "get":
      "tags": ["Referrals"]
      "summary": "List referral rewards"
      "description": "Requires referrals:read. Server controls attribution, identity masking and reward amounts."
      "responses":
        "200":
          "description": "List referral rewards"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/ReferralRewardPage"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/Page"}, {"$ref": "#/components/parameters/PageSize"}]
      "x-required-scopes": ["referrals:read"]
  "/api/customer/v1/dynamic/config":
    "get":
      "tags": ["Dynamic"]
      "summary": "Read dynamic capabilities and traffic packs"
      "description": "Requires dynamic:read. Countries, protocols and sticky durations reflect configured support. Disabled products return no sellable packs. Select a pack by its kind: standard or optimization."
      "responses":
        "200":
          "description": "Read dynamic capabilities and traffic packs"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/CustomerDynamicConfig"
        "default":
          "$ref": "#/components/responses/Error"
      "x-required-scopes": ["dynamic:read"]
  "/api/customer/v1/dynamic/account":
    "get":
      "tags": ["Dynamic"]
      "summary": "Read standard and optimization traffic balances"
      "description": "Requires dynamic:read. Residential/Usage supplies the standard total/used/remaining snapshot. Reading this data never changes the wallet or traffic allowance. Do not use windowed analytics as cumulative billing usage."
      "responses":
        "200":
          "description": "Read standard and optimization traffic balances"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/CustomerDynamicAccount"
        "default":
          "$ref": "#/components/responses/Error"
      "x-required-scopes": ["dynamic:read"]
  "/api/customer/v1/dynamic/usage":
    "get":
      "tags": ["Dynamic"]
      "summary": "Read windowed dynamic usage analytics"
      "description": "Requires dynamic:read. Defaults to the last 24 hours, ending at the current UTC minute. Maximum window 30 days, or a lower capability limit. Missing geographic attribution is country unknown, not zero usage. A reporting cooldown returns 429 DYNAMIC_USAGE_RATE_LIMITED with Retry-After."
      "responses":
        "200":
          "description": "Read windowed dynamic usage analytics"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/CustomerDynamicUsage"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"name": "from", "in": "query", "schema": {"type": "string", "format": "date-time"}, "description": "Inclusive lower bound."}, {"name": "to", "in": "query", "schema": {"type": "string", "format": "date-time"}, "description": "Exclusive upper bound; at most one minute in the future."}, {"name": "breakdown", "in": "query", "schema": {"enum": ["overview", "trend", "countries", "hosts"], "default": "overview"}}, {"name": "interval", "in": "query", "schema": {"enum": ["hour", "day"], "default": "hour"}}, {"name": "limit", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 100, "default": 20}, "description": "Maximum ranked host rows."}]
      "x-required-scopes": ["dynamic:read"]
  "/api/customer/v1/dynamic/quotes":
    "post":
      "tags": ["Dynamic"]
      "summary": "Quote a dynamic traffic pack"
      "description": "Requires write permission and dynamic:write. Uses the workspace-effective pack price and optional eligible coupon."
      "responses":
        "200":
          "description": "Quote a dynamic traffic pack"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/CustomerDynamicQuote"
        "default":
          "$ref": "#/components/responses/Error"
      "requestBody":
        "required": true
        "content":
          "application/json":
            "schema":
              "type": "object"
              "additionalProperties": false
              "properties":
                "pack_id":
                  "type": "string"
                "coupon_code":
                  "type": "string"
                  "maxLength": 64
              "required": ["pack_id"]
      "x-required-scopes": ["dynamic:write"]
  "/api/customer/v1/dynamic/purchases":
    "post":
      "tags": ["Dynamic"]
      "summary": "Purchase a traffic pack"
      "description": "Requires write permission and dynamic:write, wallet:spend. Send pack_id, price_version and expected_total_fen from config, or include quote_id and echo the quote’s exact pack/version/total. Prefer a quote to freeze price and use coupons. Exact retries return the same receipt without another debit or supplier grant. A 502 grant result includes the purchase for status tracking; poll it or contact support rather than creating another purchase."
      "responses":
        "202":
          "description": "Purchase a traffic pack"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/CustomerDynamicPurchaseResult"
        "default":
          "$ref": "#/components/responses/Error"
        "502":
          "description": "Supplier grant was rejected or not confirmed. The committed receipt remains available; no automatic retry."
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/DynamicPurchaseGrantError"
      "parameters": [{"$ref": "#/components/parameters/Idempotency"}]
      "requestBody":
        "required": true
        "content":
          "application/json":
            "schema":
              "type": "object"
              "additionalProperties": false
              "properties":
                "pack_id":
                  "type": "string"
                "price_version":
                  "type": "integer"
                  "minimum": 1
                "expected_total_fen":
                  "$ref": "#/components/schemas/Fen"
                "quote_id":
                  "type": "string"
              "required": ["pack_id", "price_version", "expected_total_fen"]
      "x-required-scopes": ["dynamic:write", "wallet:spend"]
    "get":
      "tags": ["Dynamic"]
      "summary": "List dynamic traffic purchases"
      "description": "Requires dynamic:read. Includes both traffic kinds unless filtered."
      "responses":
        "200":
          "description": "List dynamic traffic purchases"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/CustomerDynamicPurchasePage"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/Page"}, {"$ref": "#/components/parameters/PageSize"}, {"name": "kind", "in": "query", "schema": {"type": "string", "enum": ["standard", "optimization"]}}]
      "x-required-scopes": ["dynamic:read"]
  "/api/customer/v1/dynamic/purchases/{id}":
    "get":
      "tags": ["Dynamic"]
      "summary": "Read dynamic traffic purchase status"
      "description": "Requires dynamic:read. Unknown and foreign IDs return 404. Provisioning retry, cancellation and wallet refunds remain operator-only."
      "responses":
        "200":
          "description": "Read dynamic traffic purchase status"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/CustomerDynamicPurchase"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/ID"}]
      "x-required-scopes": ["dynamic:read"]
  "/api/customer/v1/dynamic/profiles":
    "get":
      "tags": ["Dynamic"]
      "summary": "List saved dynamic connections"
      "description": "Requires dynamic:read. Newest first across direct and optimized profiles. The list is bounded by saved-profile limits and always omits credentials (connection is null)."
      "responses":
        "200":
          "description": "List saved dynamic connections"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/CustomerDynamicProfileList"
        "default":
          "$ref": "#/components/responses/Error"
      "x-required-scopes": ["dynamic:read"]
    "post":
      "tags": ["Dynamic"]
      "summary": "Generate or reuse a dynamic connection"
      "description": "Requires write permission and dynamic:write, credentials:read. Use country * for all available countries when advertised by config; country is required. Select rotating or sticky mode and a supported sticky duration. The same effective configuration reuses its saved profile, including under a new idempotency key. Optimized access requires available allowance, gateway and a healthy assigned node. Credential-bearing responses are sensitive."
      "responses":
        "201":
          "description": "Generate or reuse a dynamic connection"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/CustomerDynamicProfileResult"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/Idempotency"}]
      "requestBody":
        "required": true
        "content":
          "application/json":
            "schema":
              "type": "object"
              "additionalProperties": false
              "properties":
                "country":
                  "type": "string"
                  "description": "ISO country code from config, or * for all available countries."
                "protocol":
                  "enum": ["http", "socks5"]
                  "default": "http"
                "mode":
                  "type": "string"
                  "enum": ["rotating", "sticky"]
                "duration_minutes":
                  "type": "integer"
                  "minimum": 1
                "access_optimized":
                  "type": "boolean"
                  "default": false
              "required": ["country", "mode"]
      "x-required-scopes": ["dynamic:write", "credentials:read"]
  "/api/customer/v1/dynamic/profiles/{id}":
    "get":
      "tags": ["Dynamic"]
      "summary": "Read one dynamic connection"
      "description": "Requires dynamic:read, credentials:read. Connection credentials are returned only for a ready, authorized and entitled profile. Unknown and foreign IDs return 404."
      "responses":
        "200":
          "description": "Read one dynamic connection"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/CustomerDynamicProfileResult"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/ID"}]
      "x-required-scopes": ["dynamic:read", "credentials:read"]
    "delete":
      "tags": ["Dynamic"]
      "summary": "Remove a saved dynamic connection"
      "description": "Requires write permission and dynamic:write. Removal is idempotent. Deleting a direct profile removes the saved configuration; it does not rotate shared credentials that were already copied, or refund traffic."
      "responses":
        "200":
          "description": "Remove a saved dynamic connection"
          "content":
            "application/json":
              "schema":
                "type": "object"
                "additionalProperties": false
                "properties":
                  "revoked":
                    "type": "boolean"
                    "const": true
                "required": ["revoked"]
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/ID"}]
      "x-required-scopes": ["dynamic:write"]
  "/api/customer/v1/webhooks":
    "get":
      "tags": ["Webhooks"]
      "summary": "List webhook subscriptions"
      "description": "Requires webhooks:read. Includes disabled/deleted subscriptions for delivery history. Signing secrets are omitted."
      "responses":
        "200":
          "description": "List webhook subscriptions"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/WebhookPage"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/Page"}, {"$ref": "#/components/parameters/PageSize"}]
      "x-required-scopes": ["webhooks:read"]
    "post":
      "tags": ["Webhooks"]
      "summary": "Create a webhook subscription"
      "description": "Requires write permission and webhooks:write. Maximum 10 enabled subscriptions per workspace. Idempotency-Key is required; exact replay returns the same subscription. Copy the secret securely. Only later committed changes are captured; no historical backfill."
      "responses":
        "201":
          "description": "Create a webhook subscription"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/WebhookSecretReply"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/Idempotency"}]
      "requestBody":
        "required": true
        "content":
          "application/json":
            "schema":
              "$ref": "#/components/schemas/WebhookCreate"
      "x-required-scopes": ["webhooks:write"]
  "/api/customer/v1/webhooks/{id}":
    "get":
      "tags": ["Webhooks"]
      "summary": "Read a webhook subscription"
      "description": "Requires webhooks:read. Foreign IDs return 404."
      "responses":
        "200":
          "description": "Read a webhook subscription"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/WebhookReply"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/ID"}]
      "x-required-scopes": ["webhooks:read"]
    "patch":
      "tags": ["Webhooks"]
      "summary": "Update a webhook subscription"
      "description": "Requires write permission and webhooks:write. Supply current version; stale version returns 409. Omitted fields are unchanged. Remove wallet.low_balance and set its threshold to null together. Deleted subscriptions cannot be re-enabled."
      "responses":
        "200":
          "description": "Update a webhook subscription"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/WebhookReply"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/ID"}]
      "requestBody":
        "required": true
        "content":
          "application/json":
            "schema":
              "$ref": "#/components/schemas/WebhookPatch"
      "x-required-scopes": ["webhooks:write"]
    "delete":
      "tags": ["Webhooks"]
      "summary": "Delete a webhook subscription"
      "description": "Requires write permission and webhooks:write. Soft deletion retains history and cancels pending deliveries. Repeated deletion is harmless. Already dispatched network requests may still arrive."
      "responses":
        "200":
          "description": "Delete a webhook subscription"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/WebhookReply"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/ID"}]
      "x-required-scopes": ["webhooks:write"]
  "/api/customer/v1/webhooks/{id}/rotate-secret":
    "post":
      "tags": ["Webhooks"]
      "summary": "Rotate webhook signing secret"
      "description": "Requires write permission and webhooks:write. Returns a fresh secret, replacing the previous one for subsequent attempts. This command is not idempotent: do not automatically repeat an uncertain response. An in-flight delivery can still carry the prior signature. If the response is lost, explicitly rotate again and install that new secret."
      "responses":
        "200":
          "description": "Rotate webhook signing secret"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/WebhookSecretReply"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/ID"}]
      "x-required-scopes": ["webhooks:write"]
  "/api/customer/v1/webhooks/{id}/deliveries":
    "get":
      "tags": ["Webhooks"]
      "summary": "List delivery history"
      "description": "Requires webhooks:read. Events are delivered at least once and may arrive out of order. Deduplicate using event_id. Failed deliveries stop automatically after 8 attempts per round; up to 3 manual retry rounds are allowed."
      "responses":
        "200":
          "description": "List delivery history"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/WebhookDeliveryPage"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/ID"}, {"$ref": "#/components/parameters/Page"}, {"$ref": "#/components/parameters/PageSize"}]
      "x-required-scopes": ["webhooks:read"]
  "/api/customer/v1/webhooks/{id}/deliveries/{delivery}/retry":
    "post":
      "tags": ["Webhooks"]
      "summary": "Retry a failed delivery"
      "description": "Requires write permission and webhooks:write. Only failed deliveries on enabled subscriptions can be retried. Required Idempotency-Key makes exact replay a no-op. Creates one additional bounded retry round, retaining the same event ID."
      "responses":
        "200":
          "description": "Retry a failed delivery"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/WebhookDeliveryReply"
        "default":
          "$ref": "#/components/responses/Error"
      "parameters": [{"$ref": "#/components/parameters/ID"}, {"name": "delivery", "in": "path", "required": true, "schema": {"type": "string"}}, {"$ref": "#/components/parameters/Idempotency"}]
      "x-required-scopes": ["webhooks:write"]
  "/api/customer/v1/account":
    "get":
      "tags": ["Account"]
      "summary": "Identify the authenticated workspace"
      "description": "Requires account:read. Returns the workspace and issuer derived from the API key, its base permission and explicit scopes (null for a legacy key). No customer-selected identity parameters or secrets."
      "responses":
        "200":
          "description": "Identify the authenticated workspace"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/CustomerAccount"
        "default":
          "$ref": "#/components/responses/Error"
      "x-required-scopes": ["account:read"]
components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: |
        Tenant API key, `ipk_` prefixed, sent as `Authorization: Bearer <key>`.
        Scoped to one tenant and one permission (read/write). The key is usable
        only while its issuer is still an active admin of an active, non-removed
        tenant; a revocation, demotion, removal or suspension takes effect on the
        next request. Never log or persist the raw key.
    portalSession:
      type: apiKey
      in: cookie
      name: ipjojo_session
      description: Customer portal cookie session. Requires the exact Origin and the session CSRF token on unsafe methods.
  parameters:
    Page: {name: page, in: query, schema: {type: integer, minimum: 1, maximum: 100000, default: 1}}
    PageSize: {name: page_size, in: query, schema: {type: integer, minimum: 1, maximum: 100, default: 20}}
    ID: {name: id, in: path, required: true, schema: {type: string}}
    Tenant: {name: tenant, in: path, required: true, schema: {type: string}, description: Tenant id; membership and admin role are enforced server-side}
    Idempotency:
      name: Idempotency-Key
      in: header
      required: true
      schema: {type: string, minLength: 8, maxLength: 128}
      description: Exact replay returns the original result without spending twice; a changed body conflicts.
    Origin:
      name: Origin
      in: header
      required: true
      schema: {type: string}
      description: Exact configured customer origin for portal key management
    CSRFHeader:
      name: X-CSRF-Token
      in: header
      required: true
      schema: {type: string}
      description: Stable session-bound token returned by the customer session endpoint; keep in memory
    ObservabilityCursor: {name: cursor, in: query, schema: {type: string, maxLength: 4096}, description: Opaque cursor bound to the original scope, window and filters; a tampered, re-targeted or expired cursor is refused with 422 INVALID_CURSOR}
    ObservabilityDestinationHost: {name: destination_host, in: query, schema: {type: string, maxLength: 255}, description: Exact-match destination host filter, never a wildcard pattern}
    ObservabilityEnd: {name: end, in: query, schema: {type: string, format: date-time}, description: RFC3339 UTC upper bound, defaulting to now; a genuinely future value is refused rather than clamped}
    ObservabilityLimit: {name: limit, in: query, schema: {type: integer, minimum: 1, maximum: 100, default: 50}, description: Maximum number of events returned on one page}
    ObservabilityOutcome: {name: outcome, in: query, schema: {type: string, enum: [success, error, cancelled, incomplete]}, description: Exact-match outcome filter}
    ObservabilityProtocol: {name: protocol, in: query, schema: {type: string, enum: [http, connect, socks5]}, description: Exact-match protocol filter; no wildcard or pattern syntax}
    ObservabilitySourceIP: {name: source_ip, in: query, schema: {type: string, maxLength: 45}, description: Exact-match client source address that must parse as IPv4 or IPv6}
    ObservabilityStart: {name: start, in: query, schema: {type: string, format: date-time}, description: RFC3339 UTC lower bound, defaulting to one hour before end; a window beyond six months for metrics or 30 days for one logs query is refused}
    ObservabilityStepSeconds: {name: step_seconds, in: query, schema: {type: integer, minimum: 1, maximum: 2592000}, description: Requested sample interval in seconds; adapted upward so the graph returns at most 720 points}
    ObservabilityTransport: {name: transport, in: query, schema: {type: string, enum: [tcp, udp]}, description: Exact-match transport filter. tcp also selects legacy records stored before the field existed (all prior data plane traffic was TCP-only); udp matches only records that explicitly carry udp, never an absent field}
  responses:
    Error:
      description: |
        Shared failure envelope. Codes by status:
        - 401 UNAUTHORIZED - missing, wrong, revoked or disabled-issuer key.
        - 403 FORBIDDEN / TENANT_SUSPENDED - read key on a write route, or a
          suspended/removed tenant or non-admin portal actor.
        - 404 NOT_FOUND - unknown id or another tenant's record.
        - 409 conflict - LIFECYCLE_UNSUPPORTED, QUOTE_USED, IDEMPOTENCY_CONFLICT,
          KEY_LIMIT_REACHED, or the renewal guard.
        - 422 INVALID_REQUEST / INVALID_PROTOCOL / INVALID_PAGINATION /
          INVALID_PRICE / INSUFFICIENT_FUNDS / QUOTE_EXPIRED - validation.
        - 429 RATE_LIMITED - per-key ceiling; includes `Retry-After`.
        - 500 internal error.
        Every response is `Cache-Control: no-store`. Surface `request_id` for support.
      headers:
        Retry-After: {schema: {type: integer}, description: Seconds to wait; present on 429 only}
      content:
        application/json:
          schema:
            type: object
            required: [code, request_id]
            properties:
              code: {type: string}
              request_id: {type: string}
  schemas:
    Fen: {type: string, pattern: '^[0-9]+$', description: Integer CNY fen; no binary floating-point money}
    Wallet:
      type: object
      additionalProperties: false
      required: [tenant_id, balance_fen]
      properties:
        tenant_id: {type: string}
        balance_fen: {$ref: '#/components/schemas/Fen'}
    CatalogOffer:
      type: object
      additionalProperties: false
      required: [id, product, country, location_id, location_kind, location_name, location_subdivision, period, days, base_fen, optimization_fen, optimization_enabled, optimization_supported, supported_protocols, supply_mode]
      properties:
        location_kind: {type: string, enum: [city, state, province, territory, region]}
        id: {type: string, description: Identical across the portal and the API}
        product: {enum: [static_residential, static_datacenter]}
        country: {type: string}
        location_id: {type: string, minLength: 1, description: Required named location; country/product defaults are not purchase offers}
        location_name: {type: string, description: Canonical location name; empty string when no location is selected}
        location_subdivision: {type: string, description: Optional subdivision label disambiguating identically named locations}
        period: {type: string, description: "Calendar-month terms use month for 1 month or Nmonths for 2–121 months; days is nominal metadata (30 × N), not elapsed-day expiry. Preserve the exact catalog tuple."}
        days: {type: integer, minimum: 1}
        base_fen: {$ref: '#/components/schemas/Fen'}
        optimization_fen: {$ref: '#/components/schemas/Fen'}
        optimization_enabled: {type: boolean, description: True when the winning price rule offers the independently priced per-IP access-optimization add-on}
        optimization_supported: {type: boolean, description: True when the add-on is offered and at least one healthy, capability-compatible node has free capacity; purchase rechecks capacity transactionally}
        supported_protocols: {type: array, minItems: 1, uniqueItems: true, items: {enum: [http, socks5]}, description: Protocols actually supported; http is the HTTPS/CONNECT representation}
        supply_mode: {type: string}
    CatalogPage:
      type: object
      additionalProperties: false
      required: [items, total]
      properties:
        items: {type: array, items: {$ref: '#/components/schemas/CatalogOffer'}}
        total: {type: integer, minimum: 0}
    QuoteRequest:
      type: object
      additionalProperties: false
      required: [price_id, quantity]
      description: Unknown fields, including `tenant_id` and `scenario`, are rejected with 422.
      properties:
        price_id: {type: string, minLength: 1, maxLength: 128}
        quantity: {type: integer, minimum: 1, maximum: 20}
        protocol: {enum: [http, socks5], default: http, description: A connection-format preference, never a pricing or availability dimension; omitted defaults to the compatibility HTTPS/CONNECT representation}
        access_optimized: {type: boolean, default: false, description: Select the independently priced per-IP access-optimization add-on; rejected when the effective offer does not support it}
        note: {type: string, maxLength: 200, default: '', description: Optional plain-text instance note of at most 200 Unicode characters; a multi-IP purchase suffixes each instance by its durable order-item ordinal, and an empty note stays empty}
        auto_renew_enabled: {type: boolean, default: false, description: Opt the purchase into wallet auto-renew; non-financial until the scheduler charges, which needs a current active/verified tenant and an active authorizing membership}
        coupon_code: {type: string, minLength: 3, maxLength: 64, description: Optional one-per-purchase percentage coupon code. Trimmed and upper-cased server-side; an unknown, disabled, expired, exhausted or ineligible code returns 422 COUPON_UNAVAILABLE without leaking restriction targets. Nested referral attribution/rates are never supplied by the caller}
    Quote:
      type: object
      additionalProperties: false
      required: [id, tenant_id, price_id, product, country, location_id, location_name, location_subdivision, period, days, quantity, protocol, access_optimized, optimization_fen, unit_fen, subtotal_fen, discount_fen, total_fen, coupon, created_at, expires_at, note, auto_renew_enabled]
      properties:
        purchase_kind: {enum: [purchase, optimization], description: Purchase creates new IPs; optimization adds access optimization to an existing service.}
        service_id: {type: string, description: Existing instance targeted by an optimization order.}
        service_expires_at: {type: string, format: date-time, description: Unchanged instance expiry frozen by an optimization quote.}
        id: {type: string}
        tenant_id: {type: string}
        price_id: {type: string}
        product: {enum: [static_residential, static_datacenter]}
        country: {type: string}
        location_id: {type: string}
        location_name: {type: string}
        location_subdivision: {type: string}
        period: {type: string, description: "Calendar-month terms use month for 1 month or Nmonths for 2–121 months; days is nominal metadata (30 × N), not elapsed-day expiry. Preserve the exact catalog tuple."}
        days: {type: integer, minimum: 1}
        quantity: {type: integer, minimum: 1}
        protocol: {enum: [http, socks5]}
        access_optimized: {type: boolean}
        optimization_fen: {$ref: '#/components/schemas/Fen'}
        unit_fen: {$ref: '#/components/schemas/Fen'}
        subtotal_fen: {$ref: '#/components/schemas/Fen'}
        discount_fen: {$ref: '#/components/schemas/Fen'}
        total_fen: {$ref: '#/components/schemas/Fen'}
        coupon: {oneOf: [{$ref: '#/components/schemas/Coupon'}, {type: 'null'}], description: Applied coupon snapshot, or null. unit_fen stays the pre-discount retail unit price; subtotal_fen - discount_fen = total_fen}
        created_at: {type: string, format: date-time}
        expires_at: {type: string, format: date-time}
        note: {type: string, maxLength: 200, description: Snapshot of the requested instance note; checkout replay retains it and provisioning initializes the instance settings}
        auto_renew_enabled: {type: boolean, description: Snapshot of the requested auto-renew opt-in; checkout replay retains it}
    Order:
      type: object
      additionalProperties: false
      required: [id, tenant_id, quote_id, subtotal_fen, discount_fen, total_fen, coupon, status, created_at]
      properties:
        purchase_kind: {enum: [purchase, optimization], description: Purchase creates new IPs; optimization adds access optimization to an existing service.}
        service_id: {type: string, description: Existing instance targeted by an optimization order.}
        id: {type: string}
        tenant_id: {type: string}
        quote_id: {type: string}
        subtotal_fen: {$ref: '#/components/schemas/Fen'}
        discount_fen: {$ref: '#/components/schemas/Fen'}
        total_fen: {$ref: '#/components/schemas/Fen'}
        coupon: {oneOf: [{$ref: '#/components/schemas/Coupon'}, {type: 'null'}], description: Coupon snapshot frozen on the accepted order, or null}
        status: {enum: [provisioning, partial, needs_attention, completed, cancelled]}
        created_at: {type: string, format: date-time}
    Coupon:
      type: object
      additionalProperties: false
      required: [id, code, name, discount_bps]
      description: Buyer-facing coupon snapshot. Never carries restriction targets, attribution or rates.
      properties:
        id: {type: string}
        code: {type: string, pattern: '^[A-Z0-9_-]{3,64}$'}
        name: {type: string}
        discount_bps: {type: integer, minimum: 1, maximum: 10000}
    OrderItem:
      type: object
      additionalProperties: false
      required: [id, ordinal, amount_fen]
      properties:
        id: {type: string}
        ordinal: {type: integer, minimum: 1}
        amount_fen: {$ref: '#/components/schemas/Fen'}
    Operation:
      type: object
      additionalProperties: false
      required: [id, order_id, kind, state, error_code, attempt_count, created_at, updated_at]
      description: Bounded fulfillment status; supplier identifiers, leases, modes and raw error codes are never exposed.
      properties:
        id: {type: string}
        order_id: {type: string}
        kind: {type: string}
        state: {enum: [queued, running, verifying, needs_attention, succeeded, cancelled, paused]}
        error_code: {type: string, description: Empty on success; otherwise the stable OPERATION_NEEDS_ATTENTION signal}
        attempt_count: {type: integer, minimum: 0}
        created_at: {type: string, format: date-time}
        updated_at: {type: string, format: date-time}
    OrderAccepted:
      type: object
      additionalProperties: false
      required: [order, operations]
      properties:
        order: {$ref: '#/components/schemas/Order'}
        operations: {type: array, items: {$ref: '#/components/schemas/Operation'}}
    OrderDetail:
      type: object
      additionalProperties: false
      required: [order, quote, items, operations, services]
      properties:
        order: {$ref: '#/components/schemas/Order'}
        quote: {$ref: '#/components/schemas/Quote'}
        items: {type: array, items: {$ref: '#/components/schemas/OrderItem'}}
        operations: {type: array, items: {$ref: '#/components/schemas/Operation'}}
        services: {type: array, items: {$ref: '#/components/schemas/ProxySummary'}}
    OrderPage:
      type: object
      additionalProperties: false
      required: [items, total, page, page_size]
      properties:
        items: {type: array, items: {$ref: '#/components/schemas/Order'}}
        total: {type: integer, minimum: 0}
        page: {type: integer, minimum: 1}
        page_size: {type: integer, minimum: 1, maximum: 100}
    ProxySummary:
      type: object
      additionalProperties: false
      required: [id, order_id, operation_id, ip, hostname, product, country, location_id, location_name, location_subdivision, refunded_at, protocol, activated_at, expires_at, term_state, connection_available, direct_connection_available, optimized_connection_available, supported_protocols, renewable, simulated, provenance, optimization, lifecycle, note, settings_version, auto_renew_enabled, auto_renew_period, auto_renew_state, auto_renew_next_attempt_at]
      properties:
        id: {type: string, description: Identical across the portal and the API}
        order_id: {type: string}
        operation_id: {type: string}
        ip: {type: string}
        hostname: {type: string}
        product: {enum: [static_residential, static_datacenter]}
        country: {type: string}
        location_id: {type: string, description: Purchased location scope id snapshot; empty string for a product/country-scope resource}
        location_name: {type: string, description: Purchased location display name snapshot}
        location_subdivision: {type: string, description: Optional subdivision label that disambiguated the purchased location; empty string when absent}
        refunded_at: {oneOf: [{type: string, format: date-time}, {type: 'null'}], description: Set once an operator refund settles; the instance stays readable as a historical record and is terminal for renewal and connection use}
        protocol: {enum: [http, socks5], description: Original/default protocol}
        activated_at: {type: string, format: date-time}
        expires_at: {type: string, format: date-time}
        term_state: {enum: [active, expired], description: Recorded dates only; not a live cutoff claim}
        connection_available: {type: boolean, description: Availability of the legacy preferred route; optimized when selected, direct otherwise. No implicit fallback.}
        direct_connection_available: {type: boolean, description: Whether the instance supplier credentials may be displayed; independent of optimized route readiness.}
        optimized_connection_available: {type: boolean, description: Whether a ready optimized route may be displayed. Both routes are unavailable after expiry or refund and when access is denied.}
        supported_protocols: {type: array, minItems: 1, uniqueItems: true, items: {enum: [http, socks5]}, description: Complete delivered protocol capability, independent of whether the add-on credentials are currently releasable}
        renewable: {type: boolean}
        simulated: {type: boolean, description: True when the upstream connection evidence is an explicit fixture}
        provenance: {enum: [provider, manual]}
        optimization:
          oneOf: [{$ref: '#/components/schemas/Optimization'}, {type: 'null'}]
          description: Present when the instance carries a per-IP access-optimization add-on; null otherwise
        lifecycle:
          oneOf: [{$ref: '#/components/schemas/LifecycleStatus'}, {type: 'null'}]
          description: Present once a lifecycle row exists; null for a manual instance with none.
        note: {type: string, maxLength: 200, description: Optional plain-text instance note; empty when unset. A legacy row without settings reads as empty}
        settings_version: {type: integer, minimum: 1, description: Optimistic-concurrency version; send the current value as expected_version when patching. A legacy row without settings reads as 1}
        auto_renew_enabled: {type: boolean, description: Whether the instance is opted into wallet auto-renew; a legacy row without settings reads as false}
        auto_renew_period: {type: string, description: Original purchase period the auto-renew renews for; empty string when unsupported}
        auto_renew_state: {enum: [off, scheduled, insufficient_balance, unavailable, needs_attention]}
        auto_renew_next_attempt_at: {oneOf: [{type: string, format: date-time}, {type: 'null'}], description: Next bounded retry time after insufficient balance; null otherwise}
    SettingsRequest:
      type: object
      additionalProperties: false
      required: [expected_version]
      description: Optimistic-concurrency instance settings update. Omitted note/auto_renew_enabled stay unchanged; a stale expected_version conflicts.
      properties:
        expected_version: {type: integer, minimum: 1, description: The settings_version last read for the instance}
        note: {type: string, maxLength: 200, description: Replacement note, at most 200 Unicode characters}
        auto_renew_enabled: {type: boolean, description: Replacement auto-renew opt-in; non-financial until the scheduler charges}
    Optimization:
      type: object
      additionalProperties: false
      required: [selected, state, bandwidth]
      description: Sanitized access-optimization add-on state. Never carries a node id, control identity or internal error. A pending or revoked add-on exposes no direct supplier credentials.
      properties:
        selected: {type: boolean}
        state: {enum: [pending, ready, needs_attention, revoked]}
        bandwidth:
          oneOf: [{$ref: '#/components/schemas/OptimizationBandwidth'}, {type: 'null'}]
    OptimizationBandwidth:
      type: object
      additionalProperties: false
      required: [upload, download]
      properties:
        upload: {$ref: '#/components/schemas/OptimizationBucket'}
        download: {$ref: '#/components/schemas/OptimizationBucket'}
    OptimizationBucket:
      type: object
      additionalProperties: false
      required: [bytes_per_second, burst_bytes]
      description: One direction's resolved token bucket as canonical nonnegative integer decimal strings over the full unsigned 64-bit range; both zero means unlimited.
      properties:
        bytes_per_second: {type: string, pattern: '^[0-9]+$'}
        burst_bytes: {type: string, pattern: '^[0-9]+$'}
    LifecycleStatus:
      type: object
      additionalProperties: false
      required: [supported, renewal_supported, revoke_supported, access_state, expires_at, can_renew, renewal_block_reason, latest_renewal, prices]
      description: Embedded renewal and expiry guard. Application denial does not establish supplier traffic cutoff.
      properties:
        supported: {type: boolean}
        renewal_supported: {type: boolean}
        revoke_supported: {type: boolean}
        access_state: {type: string}
        expires_at: {type: string, format: date-time}
        can_renew: {type: boolean}
        renewal_block_reason: {type: string}
        latest_renewal:
          oneOf: [{$ref: '#/components/schemas/Renewal'}, {type: 'null'}]
        prices:
          type: array
          items: {$ref: '#/components/schemas/RenewalOffer'}
          description: Exact renewal prices for the instance's already-fixed supply; no new-sale capability or supply-mode fields.
    RenewalOffer:
      type: object
      additionalProperties: false
      description: |
        One renewing instance's exact location, term and price pair. Deliberately
        narrower than a new-sale CatalogOffer: an existing instance's supply mode
        and negotiated capabilities are already fixed.
      required: [id, product, country, location_id, location_name, location_subdivision, period, days, base_fen]
      properties:
        id: {type: string, description: Buyable offer identity, identical to the value passed as the renewal quote price_id}
        product: {enum: [static_residential, static_datacenter]}
        country: {type: string}
        location_id: {type: string}
        location_name: {type: string}
        location_subdivision: {type: string}
        period: {type: string, description: "Calendar-month terms use month for 1 month or Nmonths for 2–121 months; days is nominal metadata (30 × N), not elapsed-day expiry. Preserve the exact catalog tuple."}
        days: {type: integer, minimum: 1}
        base_fen: {$ref: '#/components/schemas/Fen'}
    Connection:
      type: object
      additionalProperties: false
      required: [protocol, host, port, username, password, simulated]
      description: Persisted connection evidence, released only after authorization. Never log these values.
      properties:
        protocol: {enum: [http, socks5]}
        host: {type: string, maxLength: 253}
        port: {type: integer, minimum: 1, maximum: 65535}
        username: {type: string, maxLength: 256}
        password: {type: string, maxLength: 1024}
        simulated: {type: boolean}
    ProxyDetail:
      type: object
      additionalProperties: false
      required: [service, connections, direct_connections, optimized_connections]
      properties:
        service: {$ref: '#/components/schemas/ProxySummary'}
        connections:
          type: array
          description: Legacy preferred route; optimized when selected with no implicit direct fallback.
          items: {$ref: '#/components/schemas/Connection'}
        direct_connections:
          type: array
          description: Explicit supplier route, available independently of optimized route readiness while access is permitted.
          items: {$ref: '#/components/schemas/Connection'}
        optimized_connections:
          type: array
          description: Explicit forwarder route; empty until ready or when access is not permitted.
          items: {$ref: '#/components/schemas/Connection'}
    ProxyPage:
      type: object
      additionalProperties: false
      required: [items, total, page, page_size]
      properties:
        items: {type: array, items: {$ref: '#/components/schemas/ProxySummary'}}
        total: {type: integer, minimum: 0}
        page: {type: integer, minimum: 1}
        page_size: {type: integer, minimum: 1, maximum: 100}
    RenewalQuote:
      type: object
      additionalProperties: false
      required: [id, service_id, branch, price_id, product, country, location_id, location_name, location_subdivision, period, days, optimization_selected, base_fen, optimization_fen, total_fen, old_expires_at, proposed_expires_at, created_at, expires_at]
      description: Immutable renewal price snapshot, bindable by its paying actor only.
      properties:
        id: {type: string}
        service_id: {type: string}
        branch: {type: string, enum: [extend]}
        price_id: {type: string}
        product: {enum: [static_residential, static_datacenter]}
        country: {type: string}
        location_id: {type: string}
        location_name: {type: string}
        location_subdivision: {type: string}
        period: {type: string, description: "Calendar-month terms use month for 1 month or Nmonths for 2–121 months; days is nominal metadata (30 × N), not elapsed-day expiry. Preserve the exact catalog tuple."}
        days: {type: integer, minimum: 1}
        optimization_selected: {type: boolean, description: True when the instance's purchased add-on is preserved and repriced into this renewal quote}
        base_fen: {$ref: '#/components/schemas/Fen'}
        optimization_fen: {$ref: '#/components/schemas/Fen'}
        total_fen: {$ref: '#/components/schemas/Fen'}
        old_expires_at: {type: string, format: date-time}
        proposed_expires_at: {type: [string, 'null'], format: date-time}
        created_at: {type: string, format: date-time}
        expires_at: {type: string, format: date-time}
    Renewal:
      type: object
      additionalProperties: false
      required: [id, service_id, quote_id, branch, amount_fen, state, error_code, term_start, expires_at, created_at, updated_at]
      properties:
        id: {type: string}
        service_id: {type: string}
        quote_id: {type: string}
        branch: {type: string, enum: [extend]}
        amount_fen: {$ref: '#/components/schemas/Fen'}
        state: {type: string, enum: [queued, running, succeeded, needs_attention, unknown]}
        error_code: {type: string, description: Empty on success; otherwise the stable OPERATION_NEEDS_ATTENTION signal}
        term_start: {type: [string, 'null'], format: date-time}
        expires_at: {type: [string, 'null'], format: date-time}
        created_at: {type: string, format: date-time}
        updated_at: {type: string, format: date-time}
    RenewalWrap:
      type: object
      additionalProperties: false
      required: [renewal]
      properties:
        renewal: {$ref: '#/components/schemas/Renewal'}
    APIKeySummary:
      type: object
      additionalProperties: false
      required: [id, name, prefix, permission, created_at, last_used_at, revoked_at, scopes, expires_at, allowed_cidrs, spend_limit_fen, spent_fen, version]
      properties:
        id: {type: string}
        name: {type: string, maxLength: 80}
        prefix: {type: string, description: Non-secret display prefix; enough to identify, never enough to authenticate}
        permission: {enum: [read, write]}
        created_at: {type: string, format: date-time}
        last_used_at: {type: [string, 'null'], format: date-time}
        revoked_at: {type: [string, 'null'], format: date-time}
        scopes: {"type": ["array", "null"], "minItems": 1, "maxItems": 18, "uniqueItems": true, "items": {"type": "string", "enum": ["account:read", "catalog:read", "orders:read", "orders:write", "proxies:read", "proxies:write", "credentials:read", "dynamic:read", "dynamic:write", "wallet:read", "wallet:spend", "recharges:read", "recharges:write", "metrics:read", "referrals:read", "referrals:write", "webhooks:read", "webhooks:write"]}, "description": "Null only for pre-policy legacy keys, retaining their original base permission."}
        expires_at: {"type": ["string", "null"], "format": "date-time"}
        allowed_cidrs: {"type": "array", "maxItems": 20, "items": {"type": "string"}}
        spend_limit_fen: {"type": ["string", "null"], "pattern": "^[0-9]+$", "description": "Nonnegative integer CNY fen <=9223372036854775807. Gross lifetime API wallet spending; null is unlimited. Refunds do not restore this cap."}
        spent_fen: {"$ref": "#/components/schemas/Fen"}
        version: {"type": "integer", "minimum": 1}
    APIKeyList:
      type: object
      additionalProperties: false
      required: [items]
      properties:
        items: {type: array, items: {$ref: '#/components/schemas/APIKeySummary'}}
    APIKeyCreated:
      type: object
      additionalProperties: false
      required: [key, api_key]
      properties:
        key: {$ref: '#/components/schemas/APIKeySummary'}
        api_key: {type: string, description: The raw secret, shown exactly once here and never listed or recoverable. Copy it now; it cannot be retrieved again}
    APIKeyRevoked:
      type: object
      additionalProperties: false
      required: [key]
      properties:
        key: {$ref: '#/components/schemas/APIKeySummary'}

    "BatchQuoteInput":
      "type": "object"
      "additionalProperties": false
      "properties":
        "items":
          "type": "array"
          "minItems": 1
          "maxItems": 100
          "items":
            "type": "object"
            "additionalProperties": false
            "properties":
              "service_id":
                "type": "string"
              "price_id":
                "type": "string"
            "required": ["service_id", "price_id"]
      "required": ["items"]
    "BatchQuote":
      "type": "object"
      "additionalProperties": false
      "properties":
        "id":
          "type": "string"
        "tenant_id":
          "type": "string"
        "actor_id":
          "type": "string"
        "total_fen":
          "$ref": "#/components/schemas/Fen"
        "created_at":
          "type": "string"
          "format": "date-time"
        "expires_at":
          "type": "string"
          "format": "date-time"
        "quotes":
          "type": "array"
          "items":
            "$ref": "#/components/schemas/RenewalQuote"
      "required": ["id", "tenant_id", "actor_id", "total_fen", "created_at", "expires_at", "quotes"]
    "BatchRenewal":
      "type": "object"
      "additionalProperties": false
      "properties":
        "id":
          "type": "string"
        "tenant_id":
          "type": "string"
        "quote_id":
          "type": "string"
        "total_fen":
          "$ref": "#/components/schemas/Fen"
        "created_at":
          "type": "string"
          "format": "date-time"
        "renewals":
          "type": "array"
          "items":
            "$ref": "#/components/schemas/Renewal"
      "required": ["id", "tenant_id", "quote_id", "total_fen", "created_at", "renewals"]
    "LedgerPage":
      "type": "object"
      "additionalProperties": false
      "properties":
        "items":
          "type": "array"
          "items":
            "$ref": "#/components/schemas/LedgerItem"
        "total":
          "type": "integer"
          "minimum": 0
        "page":
          "type": "integer"
          "minimum": 1
        "page_size":
          "type": "integer"
          "minimum": 1
          "maximum": 100
      "required": ["items", "total", "page", "page_size"]
    "OrderHistoryPage":
      "type": "object"
      "additionalProperties": false
      "properties":
        "items":
          "type": "array"
          "items":
            "$ref": "#/components/schemas/OrderHistoryItem"
        "total":
          "type": "integer"
          "minimum": 0
        "page":
          "type": "integer"
          "minimum": 1
        "page_size":
          "type": "integer"
          "minimum": 1
          "maximum": 100
      "required": ["items", "total", "page", "page_size"]
    "RechargeHistoryPage":
      "type": "object"
      "additionalProperties": false
      "properties":
        "items":
          "type": "array"
          "items":
            "$ref": "#/components/schemas/RechargeHistoryItem"
        "total":
          "type": "integer"
          "minimum": 0
        "page":
          "type": "integer"
          "minimum": 1
        "page_size":
          "type": "integer"
          "minimum": 1
          "maximum": 100
      "required": ["items", "total", "page", "page_size"]
    "ReferralInviteePage":
      "type": "object"
      "additionalProperties": false
      "properties":
        "items":
          "type": "array"
          "items":
            "$ref": "#/components/schemas/ReferralInvitee"
        "total":
          "type": "integer"
          "minimum": 0
        "page":
          "type": "integer"
          "minimum": 1
        "page_size":
          "type": "integer"
          "minimum": 1
          "maximum": 100
      "required": ["items", "total", "page", "page_size"]
    "ReferralRewardPage":
      "type": "object"
      "additionalProperties": false
      "properties":
        "items":
          "type": "array"
          "items":
            "$ref": "#/components/schemas/ReferralReward"
        "total":
          "type": "integer"
          "minimum": 0
        "page":
          "type": "integer"
          "minimum": 1
        "page_size":
          "type": "integer"
          "minimum": 1
          "maximum": 100
      "required": ["items", "total", "page", "page_size"]
    "DynamicUsageCapabilities":
      "type": "object"
      "additionalProperties": false
      "properties":
        "breakdowns":
          "type": "array"
          "items":
            "type": "string"
            "enum": ["overview", "trend", "countries", "hosts"]
        "intervals":
          "type": "array"
          "items":
            "type": "string"
            "enum": ["hour", "day"]
        "max_window_days":
          "type": "integer"
          "minimum": 0
        "bucket_timezone":
          "type": "string"
        "balance_reporting":
          "type": "boolean"
      "required": ["breakdowns", "intervals", "max_window_days", "bucket_timezone", "balance_reporting"]
    "DynamicCapabilities":
      "type": "object"
      "additionalProperties": false
      "properties":
        "usage_analytics":
          "oneOf": [{"$ref": "#/components/schemas/DynamicUsageCapabilities"}, {"type": "null"}]
        "supported_countries":
          "type": "array"
          "items":
            "type": "string"
        "protocols":
          "type": "array"
          "items":
            "type": "string"
            "enum": ["http", "socks5"]
        "modes":
          "type": "array"
          "items":
            "type": "string"
            "enum": ["rotating", "sticky"]
        "sticky_session_minutes":
          "type": "array"
          "items":
            "type": "integer"
            "minimum": 1
        "sticky_default_minutes":
          "type": "integer"
          "minimum": 0
      "required": ["usage_analytics", "supported_countries", "protocols", "modes", "sticky_session_minutes", "sticky_default_minutes"]
    "DynamicPack":
      "type": "object"
      "additionalProperties": false
      "properties":
        "id":
          "type": "string"
        "label":
          "type": "string"
        "kind":
          "type": "string"
          "enum": ["standard", "optimization"]
        "bytes":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)$"
        "price_fen":
          "$ref": "#/components/schemas/Fen"
        "version":
          "type": "integer"
          "minimum": 1
      "required": ["id", "label", "kind", "bytes", "price_fen", "version"]
    "CustomerDynamicConfig":
      "type": "object"
      "additionalProperties": false
      "properties":
        "tenant_id":
          "type": "string"
        "ready":
          "type": "boolean"
        "ready_reason":
          "type": "string"
        "capabilities":
          "$ref": "#/components/schemas/DynamicCapabilities"
        "packs":
          "type": "array"
          "items":
            "$ref": "#/components/schemas/DynamicPack"
      "required": ["tenant_id", "ready", "ready_reason", "capabilities", "packs"]
    "CustomerDynamicAccount":
      "type": "object"
      "additionalProperties": false
      "properties":
        "tenant_id":
          "type": "string"
        "usage_status":
          "enum": ["reported", "unavailable"]
        "purchased_bytes":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)$"
        "adjustment_bytes":
          "type": "string"
          "pattern": "^(0|-?[1-9][0-9]*)$"
        "balance_status":
          "enum": ["reported", "unavailable"]
        "balance_source":
          "enum": ["local", "provider", "unavailable"]
        "balance_observed_at":
          "oneOf": [{"type": "string", "format": "date-time"}, {"type": "null"}]
        "total_bytes":
          "oneOf": [{"type": "string", "pattern": "^(0|[1-9][0-9]*)(\\.[0-9]+)?$"}, {"type": "null"}]
        "used_bytes":
          "oneOf": [{"type": "string", "pattern": "^(0|[1-9][0-9]*)(\\.[0-9]+)?$"}, {"type": "null"}]
        "remaining_bytes":
          "oneOf": [{"type": "string", "pattern": "^(0|[1-9][0-9]*)(\\.[0-9]+)?$"}, {"type": "null"}]
        "status":
          "enum": ["none", "active", "exhausted"]
        "last_usage_at":
          "oneOf": [{"type": "string", "format": "date-time"}, {"type": "null"}]
        "optimization":
          "oneOf": [{"$ref": "#/components/schemas/DynamicOptimizationBalance"}, {"type": "null"}]
      "required": ["tenant_id", "usage_status", "purchased_bytes", "adjustment_bytes", "balance_status", "balance_source", "balance_observed_at", "total_bytes", "used_bytes", "remaining_bytes", "status", "last_usage_at", "optimization"]
      "description": "The total/used/remaining triple is a read-only snapshot. PrivatePool values come from Residential/Usage, with remaining=max(total-used,0). Unavailable is null, not zero. Local purchased/adjustment amounts and optimization allowance have separate accounting."
    "DynamicUsageMetrics":
      "type": "object"
      "additionalProperties": false
      "properties":
        "request_count":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)$"
        "limited_count":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)$"
        "error_count":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)$"
        "upload_bytes":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)(\\.[0-9]+)?$"
        "download_bytes":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)(\\.[0-9]+)?$"
        "total_bytes":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)(\\.[0-9]+)?$"
      "required": ["request_count", "limited_count", "error_count", "upload_bytes", "download_bytes", "total_bytes"]
    "DynamicUsageRow":
      "type": "object"
      "additionalProperties": false
      "properties":
        "request_count":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)$"
        "limited_count":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)$"
        "error_count":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)$"
        "upload_bytes":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)(\\.[0-9]+)?$"
        "download_bytes":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)(\\.[0-9]+)?$"
        "total_bytes":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)(\\.[0-9]+)?$"
        "bucket_start":
          "type": "string"
          "format": "date-time"
        "country":
          "type": "string"
        "host":
          "type": "string"
      "required": ["request_count", "limited_count", "error_count", "upload_bytes", "download_bytes", "total_bytes"]
    "CustomerDynamicUsage":
      "type": "object"
      "additionalProperties": false
      "properties":
        "tenant_id":
          "type": "string"
        "status":
          "enum": ["reported", "unavailable"]
        "from":
          "type": "string"
          "format": "date-time"
        "to":
          "type": "string"
          "format": "date-time"
        "breakdown":
          "enum": ["overview", "trend", "countries", "hosts"]
        "interval":
          "enum": ["hour", "day"]
        "bucket_timezone":
          "type": "string"
        "observed_at":
          "type": "string"
          "format": "date-time"
        "totals":
          "oneOf": [{"$ref": "#/components/schemas/DynamicUsageMetrics"}, {"type": "null"}]
        "rows":
          "type": "array"
          "items":
            "$ref": "#/components/schemas/DynamicUsageRow"
        "capabilities":
          "$ref": "#/components/schemas/DynamicUsageCapabilities"
      "required": ["tenant_id", "status", "from", "to", "breakdown", "interval", "bucket_timezone", "observed_at", "totals", "rows", "capabilities"]
    "CustomerDynamicQuote":
      "type": "object"
      "additionalProperties": false
      "properties":
        "id":
          "type": "string"
        "tenant_id":
          "type": "string"
        "pack_id":
          "type": "string"
        "pack_label":
          "type": "string"
        "kind":
          "type": "string"
          "enum": ["standard", "optimization"]
        "bytes":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)$"
        "price_version":
          "type": "integer"
          "minimum": 1
        "subtotal_fen":
          "$ref": "#/components/schemas/Fen"
        "discount_fen":
          "$ref": "#/components/schemas/Fen"
        "total_fen":
          "$ref": "#/components/schemas/Fen"
        "coupon":
          "oneOf": [{"$ref": "#/components/schemas/Coupon"}, {"type": "null"}]
        "created_at":
          "type": "string"
          "format": "date-time"
        "expires_at":
          "type": "string"
          "format": "date-time"
      "required": ["id", "tenant_id", "pack_id", "pack_label", "kind", "bytes", "price_version", "subtotal_fen", "discount_fen", "total_fen", "coupon", "created_at", "expires_at"]
    "CustomerDynamicPurchase":
      "type": "object"
      "additionalProperties": false
      "properties":
        "id":
          "type": "string"
        "tenant_id":
          "type": "string"
        "pack_id":
          "type": "string"
        "pack_label":
          "type": "string"
        "quote_id":
          "type": "string"
        "kind":
          "type": "string"
          "enum": ["standard", "optimization"]
        "bytes":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)$"
        "price_fen":
          "$ref": "#/components/schemas/Fen"
        "subtotal_fen":
          "$ref": "#/components/schemas/Fen"
        "discount_fen":
          "$ref": "#/components/schemas/Fen"
        "coupon":
          "oneOf": [{"$ref": "#/components/schemas/Coupon"}, {"type": "null"}]
        "price_version":
          "type": "integer"
          "minimum": 1
        "status":
          "type": "string"
        "created_at":
          "type": "string"
          "format": "date-time"
      "required": ["id", "tenant_id", "pack_id", "pack_label", "quote_id", "kind", "bytes", "price_fen", "subtotal_fen", "discount_fen", "coupon", "price_version", "status", "created_at"]
    "CustomerDynamicPurchasePage":
      "type": "object"
      "additionalProperties": false
      "properties":
        "items":
          "type": "array"
          "items":
            "$ref": "#/components/schemas/CustomerDynamicPurchase"
        "total":
          "type": "integer"
          "minimum": 0
        "page":
          "type": "integer"
          "minimum": 1
        "page_size":
          "type": "integer"
          "minimum": 1
          "maximum": 100
      "required": ["items", "total", "page", "page_size"]
    "CustomerDynamicPurchaseResult":
      "type": "object"
      "additionalProperties": false
      "properties":
        "purchase":
          "$ref": "#/components/schemas/CustomerDynamicPurchase"
      "required": ["purchase"]
    "DynamicPurchaseGrantError":
      "type": "object"
      "additionalProperties": false
      "properties":
        "code":
          "enum": ["DYNAMIC_GRANT_UNKNOWN", "DYNAMIC_GRANT_FAILED"]
        "request_id":
          "type": "string"
        "purchase":
          "$ref": "#/components/schemas/CustomerDynamicPurchase"
      "required": ["code", "request_id", "purchase"]
    "DynamicEndpointPublic":
      "type": "object"
      "additionalProperties": false
      "properties":
        "host":
          "type": "string"
        "port":
          "type": "integer"
          "minimum": 1
          "maximum": 65535
      "required": ["host", "port"]
    "DynamicConnectionPublic":
      "type": "object"
      "additionalProperties": false
      "properties":
        "username":
          "type": "string"
        "password":
          "type": "string"
        "endpoints":
          "type": "object"
          "additionalProperties": false
          "minProperties": 1
          "properties":
            "http":
              "$ref": "#/components/schemas/DynamicEndpointPublic"
            "socks5":
              "$ref": "#/components/schemas/DynamicEndpointPublic"
      "required": ["username", "password", "endpoints"]
    "CustomerDynamicProfile":
      "type": "object"
      "additionalProperties": false
      "properties":
        "id":
          "type": "string"
        "tenant_id":
          "type": "string"
        "country":
          "type": "string"
        "mode":
          "type": "string"
          "enum": ["rotating", "sticky"]
        "duration_minutes":
          "oneOf": [{"type": "integer", "minimum": 1}, {"type": "null"}]
        "state":
          "type": "string"
        "version":
          "type": "integer"
          "minimum": 1
        "created_at":
          "type": "string"
          "format": "date-time"
        "access_optimized":
          "type": "boolean"
        "connection":
          "oneOf": [{"$ref": "#/components/schemas/DynamicConnectionPublic"}, {"type": "null"}]
      "required": ["id", "tenant_id", "country", "mode", "duration_minutes", "state", "version", "created_at", "access_optimized", "connection"]
    "CustomerDynamicProfileResult":
      "type": "object"
      "additionalProperties": false
      "properties":
        "profile":
          "$ref": "#/components/schemas/CustomerDynamicProfile"
      "required": ["profile"]
    "CustomerDynamicProfileList":
      "type": "object"
      "additionalProperties": false
      "properties":
        "tenant_id":
          "type": "string"
        "optimization_available":
          "type": "boolean"
        "items":
          "type": "array"
          "items":
            "$ref": "#/components/schemas/CustomerDynamicProfile"
      "required": ["tenant_id", "optimization_available", "items"]
    "WebhookCreate":
      "type": "object"
      "additionalProperties": false
      "properties":
        "name":
          "type": "string"
          "minLength": 1
          "maxLength": 80
        "url":
          "type": "string"
          "format": "uri"
          "maxLength": 2048
          "description": "HTTPS, port 443 only, no credentials or fragment. Each delivery resolves and pins a public address; private, special and mixed public/private DNS answers are refused."
        "event_types":
          "type": "array"
          "minItems": 1
          "uniqueItems": true
          "items":
            "type": "string"
            "enum": ["fulfillment.updated", "renewal.updated", "refund.completed", "wallet.low_balance", "dynamic.purchase.updated"]
        "enabled":
          "type": "boolean"
        "wallet_low_balance_threshold_fen":
          "type": ["string", "null"]
          "pattern": "^(0|[1-9][0-9]*)$"
          "description": "Exact CNY fen; required when wallet.low_balance is selected, null otherwise. Maximum 9007199254740992."
      "required": ["name", "url", "event_types", "enabled"]
    "WebhookPatch":
      "type": "object"
      "additionalProperties": false
      "properties":
        "name":
          "type": "string"
          "minLength": 1
          "maxLength": 80
        "url":
          "type": "string"
          "format": "uri"
          "maxLength": 2048
          "description": "HTTPS, port 443 only, no credentials or fragment. Each delivery resolves and pins a public address; private, special and mixed public/private DNS answers are refused."
        "event_types":
          "type": "array"
          "minItems": 1
          "uniqueItems": true
          "items":
            "type": "string"
            "enum": ["fulfillment.updated", "renewal.updated", "refund.completed", "wallet.low_balance", "dynamic.purchase.updated"]
        "enabled":
          "type": "boolean"
        "wallet_low_balance_threshold_fen":
          "type": ["string", "null"]
          "pattern": "^(0|[1-9][0-9]*)$"
          "description": "Exact CNY fen; required when wallet.low_balance is selected, null otherwise. Maximum 9007199254740992."
        "version":
          "type": "integer"
          "minimum": 1
      "required": ["version"]
    "Webhook":
      "type": "object"
      "additionalProperties": false
      "properties":
        "name":
          "type": "string"
          "minLength": 1
          "maxLength": 80
        "url":
          "type": "string"
          "format": "uri"
          "maxLength": 2048
          "description": "HTTPS, port 443 only, no credentials or fragment. Each delivery resolves and pins a public address; private, special and mixed public/private DNS answers are refused."
        "event_types":
          "type": "array"
          "minItems": 1
          "uniqueItems": true
          "items":
            "type": "string"
            "enum": ["fulfillment.updated", "renewal.updated", "refund.completed", "wallet.low_balance", "dynamic.purchase.updated"]
        "enabled":
          "type": "boolean"
        "wallet_low_balance_threshold_fen":
          "type": ["string", "null"]
          "pattern": "^(0|[1-9][0-9]*)$"
          "description": "Exact CNY fen; required when wallet.low_balance is selected, null otherwise. Maximum 9007199254740992."
        "id":
          "type": "string"
        "version":
          "type": "integer"
          "minimum": 1
        "created_at":
          "type": "string"
          "format": "date-time"
        "updated_at":
          "type": "string"
          "format": "date-time"
        "disabled_at":
          "type": ["string", "null"]
          "format": "date-time"
      "required": ["name", "url", "event_types", "enabled", "wallet_low_balance_threshold_fen", "id", "version", "created_at", "updated_at", "disabled_at"]
    "WebhookPage":
      "type": "object"
      "additionalProperties": false
      "properties":
        "items":
          "type": "array"
          "items":
            "$ref": "#/components/schemas/Webhook"
        "total":
          "type": "integer"
          "minimum": 0
        "page":
          "type": "integer"
          "minimum": 1
        "page_size":
          "type": "integer"
          "minimum": 1
          "maximum": 100
      "required": ["items", "total", "page", "page_size"]
    "WebhookReply":
      "type": "object"
      "additionalProperties": false
      "properties":
        "webhook":
          "$ref": "#/components/schemas/Webhook"
      "required": ["webhook"]
    "WebhookSecretReply":
      "type": "object"
      "additionalProperties": false
      "properties":
        "webhook":
          "$ref": "#/components/schemas/Webhook"
        "secret":
          "type": "string"
          "description": "Signing secret returned on creation or explicit rotation. Exact create replay can recover it only before the subscription has changed; later replay returns an empty secret. Never returned by list/detail."
      "required": ["webhook", "secret"]
    "WebhookFulfilledData":
      "type": "object"
      "additionalProperties": false
      "properties":
        "service_id":
          "type": "string"
        "status":
          "const": "fulfilled"
        "expires_at":
          "type": ["string", "null"]
          "format": "date-time"
        "activated_at":
          "type": "string"
          "format": "date-time"
        "order_id":
          "type": ["string", "null"]
      "required": ["service_id", "status", "expires_at", "activated_at", "order_id"]
    "WebhookAccessData":
      "type": "object"
      "additionalProperties": false
      "properties":
        "service_id":
          "type": "string"
        "status":
          "type": "string"
          "enum": ["active", "stop_pending", "released", "unknown"]
        "expires_at":
          "type": ["string", "null"]
          "format": "date-time"
      "required": ["service_id", "status", "expires_at"]
    "WebhookProvisioningData":
      "type": "object"
      "additionalProperties": false
      "properties":
        "operation_id":
          "type": "string"
        "order_id":
          "type": ["string", "null"]
        "status":
          "type": "string"
          "enum": ["needs_attention", "cancelled", "paused"]
      "required": ["operation_id", "order_id", "status"]
    "WebhookOptimizationData":
      "type": "object"
      "additionalProperties": false
      "properties":
        "operation_id":
          "type": "string"
        "order_id":
          "type": "string"
        "service_id":
          "type": "string"
        "purchase_kind":
          "const": "optimization"
        "status":
          "const": "fulfilled"
      "required": ["operation_id", "order_id", "service_id", "purchase_kind", "status"]
    "WebhookFulfillmentData":
      "oneOf": [{"$ref": "#/components/schemas/WebhookFulfilledData"}, {"$ref": "#/components/schemas/WebhookAccessData"}, {"$ref": "#/components/schemas/WebhookProvisioningData"}, {"$ref": "#/components/schemas/WebhookOptimizationData"}]
    "WebhookRenewalData":
      "type": "object"
      "additionalProperties": false
      "properties":
        "renewal_id":
          "type": "string"
        "service_id":
          "type": "string"
        "state":
          "type": "string"
        "amount_fen":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)$"
        "term_start":
          "type": ["string", "null"]
          "format": "date-time"
        "expires_at":
          "type": ["string", "null"]
          "format": "date-time"
      "required": ["renewal_id", "service_id", "state", "amount_fen", "term_start", "expires_at"]
    "WebhookStaticRefundData":
      "type": "object"
      "additionalProperties": false
      "properties":
        "refund_id":
          "type": "string"
        "service_id":
          "type": "string"
        "amount_fen":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)$"
        "currency":
          "const": "CNY"
        "state":
          "const": "succeeded"
      "required": ["refund_id", "service_id", "amount_fen", "currency", "state"]
    "WebhookDynamicRefundData":
      "type": "object"
      "additionalProperties": false
      "properties":
        "purchase_id":
          "type": "string"
        "amount_fen":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)$"
        "currency":
          "const": "CNY"
        "status":
          "const": "refunded"
        "kind":
          "type": "string"
          "enum": ["standard", "optimization"]
      "required": ["purchase_id", "amount_fen", "currency", "status", "kind"]
    "WebhookRefundData":
      "oneOf": [{"$ref": "#/components/schemas/WebhookStaticRefundData"}, {"$ref": "#/components/schemas/WebhookDynamicRefundData"}]
    "WebhookWalletData":
      "type": "object"
      "additionalProperties": false
      "properties":
        "balance_fen":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)$"
        "previous_balance_fen":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)$"
        "threshold_fen":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)$"
      "required": ["balance_fen", "previous_balance_fen", "threshold_fen"]
    "WebhookPurchaseData":
      "type": "object"
      "additionalProperties": false
      "properties":
        "purchase_id":
          "type": "string"
        "status":
          "type": "string"
          "enum": ["granted", "granted_unknown", "refunded"]
        "provisioning_state":
          "type": "string"
          "enum": ["processing", "succeeded", "failed", "unknown", "cancelled"]
        "kind":
          "type": "string"
          "enum": ["standard", "optimization"]
        "bytes":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)$"
        "amount_fen":
          "type": "string"
          "pattern": "^(0|[1-9][0-9]*)$"
      "required": ["purchase_id", "status", "provisioning_state", "kind", "bytes", "amount_fen"]
    "WebhookEvent":
      "oneOf": [{"type": "object", "additionalProperties": false, "properties": {"id": {"type": "string"}, "type": {"const": "fulfillment.updated"}, "created_at": {"type": "string", "format": "date-time"}, "data": {"$ref": "#/components/schemas/WebhookFulfillmentData"}}, "required": ["id", "type", "created_at", "data"]}, {"type": "object", "additionalProperties": false, "properties": {"id": {"type": "string"}, "type": {"const": "renewal.updated"}, "created_at": {"type": "string", "format": "date-time"}, "data": {"$ref": "#/components/schemas/WebhookRenewalData"}}, "required": ["id", "type", "created_at", "data"]}, {"type": "object", "additionalProperties": false, "properties": {"id": {"type": "string"}, "type": {"const": "refund.completed"}, "created_at": {"type": "string", "format": "date-time"}, "data": {"$ref": "#/components/schemas/WebhookRefundData"}}, "required": ["id", "type", "created_at", "data"]}, {"type": "object", "additionalProperties": false, "properties": {"id": {"type": "string"}, "type": {"const": "wallet.low_balance"}, "created_at": {"type": "string", "format": "date-time"}, "data": {"$ref": "#/components/schemas/WebhookWalletData"}}, "required": ["id", "type", "created_at", "data"]}, {"type": "object", "additionalProperties": false, "properties": {"id": {"type": "string"}, "type": {"const": "dynamic.purchase.updated"}, "created_at": {"type": "string", "format": "date-time"}, "data": {"$ref": "#/components/schemas/WebhookPurchaseData"}}, "required": ["id", "type", "created_at", "data"]}]
      "description": "Immutable signed envelope. Exact money and traffic amounts are decimal strings. No proxy credentials, internal provider references or raw failures."
    "WebhookDelivery":
      "type": "object"
      "additionalProperties": false
      "properties":
        "id":
          "type": "string"
        "event_id":
          "type": "string"
        "event_type":
          "type": "string"
          "enum": ["fulfillment.updated", "renewal.updated", "refund.completed", "wallet.low_balance", "dynamic.purchase.updated"]
        "state":
          "type": "string"
          "enum": ["pending", "delivering", "succeeded", "failed", "cancelled"]
        "attempts":
          "type": "integer"
        "max_attempts":
          "type": "integer"
        "manual_retries":
          "type": "integer"
          "minimum": 0
          "maximum": 3
        "attempt_budget":
          "type": "integer"
        "next_attempt_at":
          "type": ["string", "null"]
          "format": "date-time"
        "last_attempt_at":
          "type": ["string", "null"]
          "format": "date-time"
        "last_status_code":
          "type": ["integer", "null"]
        "last_error_code":
          "type": "string"
        "delivered_at":
          "type": ["string", "null"]
          "format": "date-time"
        "created_at":
          "type": "string"
          "format": "date-time"
        "payload":
          "oneOf": [{"$ref": "#/components/schemas/WebhookFulfillmentData"}, {"$ref": "#/components/schemas/WebhookRenewalData"}, {"$ref": "#/components/schemas/WebhookRefundData"}, {"$ref": "#/components/schemas/WebhookWalletData"}, {"$ref": "#/components/schemas/WebhookPurchaseData"}]
          "description": "The event data object without the signed envelope; event_type selects its schema."
      "required": ["id", "event_id", "event_type", "state", "attempts", "max_attempts", "manual_retries", "attempt_budget", "next_attempt_at", "last_attempt_at", "last_status_code", "last_error_code", "delivered_at", "created_at", "payload"]
    "WebhookDeliveryPage":
      "type": "object"
      "additionalProperties": false
      "properties":
        "items":
          "type": "array"
          "items":
            "$ref": "#/components/schemas/WebhookDelivery"
        "total":
          "type": "integer"
          "minimum": 0
        "page":
          "type": "integer"
          "minimum": 1
        "page_size":
          "type": "integer"
          "minimum": 1
          "maximum": 100
      "required": ["items", "total", "page", "page_size"]
    "WebhookDeliveryReply":
      "type": "object"
      "additionalProperties": false
      "properties":
        "delivery":
          "$ref": "#/components/schemas/WebhookDelivery"
      "required": ["delivery"]
    "APIKeyCreateInput":
      "type": "object"
      "additionalProperties": false
      "properties":
        "name":
          "type": "string"
          "minLength": 1
          "maxLength": 80
        "permission":
          "type": "string"
          "enum": ["read", "write"]
        "scopes":
          "type": ["array", "null"]
          "minItems": 1
          "maxItems": 18
          "uniqueItems": true
          "items":
            "type": "string"
            "enum": ["account:read", "catalog:read", "orders:read", "orders:write", "proxies:read", "proxies:write", "credentials:read", "dynamic:read", "dynamic:write", "wallet:read", "wallet:spend", "recharges:read", "recharges:write", "metrics:read", "referrals:read", "referrals:write", "webhooks:read", "webhooks:write"]
        "expires_at":
          "type": ["string", "null"]
          "format": "date-time"
        "allowed_cidrs":
          "type": "array"
          "maxItems": 20
          "items":
            "type": "string"
        "spend_limit_fen":
          "type": ["string", "null"]
          "pattern": "^[0-9]+$"
          "description": "Nonnegative integer CNY fen <=9223372036854775807. Gross lifetime API wallet spending; null is unlimited. Refunds do not restore this cap."
      "required": ["name", "permission"]
    "APIKeyPolicyInput":
      "type": "object"
      "additionalProperties": false
      "properties":
        "name":
          "type": "string"
          "minLength": 1
          "maxLength": 80
        "permission":
          "type": "string"
          "enum": ["read", "write"]
        "scopes":
          "type": "array"
          "minItems": 1
          "maxItems": 18
          "uniqueItems": true
          "items":
            "type": "string"
            "enum": ["account:read", "catalog:read", "orders:read", "orders:write", "proxies:read", "proxies:write", "credentials:read", "dynamic:read", "dynamic:write", "wallet:read", "wallet:spend", "recharges:read", "recharges:write", "metrics:read", "referrals:read", "referrals:write", "webhooks:read", "webhooks:write"]
        "expires_at":
          "type": ["string", "null"]
          "format": "date-time"
        "allowed_cidrs":
          "type": "array"
          "maxItems": 20
          "items":
            "type": "string"
        "spend_limit_fen":
          "type": ["string", "null"]
          "pattern": "^[0-9]+$"
          "description": "Nonnegative integer CNY fen <=9223372036854775807. Gross lifetime API wallet spending; null is unlimited. Refunds do not restore this cap."
        "expected_version":
          "type": "integer"
          "minimum": 1
      "required": ["expected_version"]
    "CustomerAccount":
      "type": "object"
      "additionalProperties": false
      "properties":
        "tenant_id":
          "type": "string"
        "user_id":
          "type": "string"
        "permission":
          "type": "string"
          "enum": ["read", "write"]
        "scopes":
          "type": ["array", "null"]
          "minItems": 1
          "maxItems": 18
          "uniqueItems": true
          "items":
            "type": "string"
            "enum": ["account:read", "catalog:read", "orders:read", "orders:write", "proxies:read", "proxies:write", "credentials:read", "dynamic:read", "dynamic:write", "wallet:read", "wallet:spend", "recharges:read", "recharges:write", "metrics:read", "referrals:read", "referrals:write", "webhooks:read", "webhooks:write"]
      "required": ["tenant_id", "user_id", "permission", "scopes"]
    CnyRechargeCreateInput:
      type: object
      additionalProperties: false
      required: [channel, amount_fen, policy_revision, expected_fee_fen, expected_credit_fen]
      description: Exact confirmed consent. Server independently recomputes fresh pricing. Existing exact replay uses its immutable original terms.
      properties:
        channel: { $ref: '#/components/schemas/RechargeChannel' }
        amount_fen: { $ref: '#/components/schemas/RechargePositiveFen' }
        policy_revision: { $ref: '#/components/schemas/RechargePolicyRevision' }
        expected_fee_fen: { $ref: '#/components/schemas/RechargeNonnegativeFen' }
        expected_credit_fen: { $ref: '#/components/schemas/RechargePositiveFen' }
    CnyRechargeIntent:
      type: object
      additionalProperties: false
      required: [id, tenant_id, actor_id, state, version, credited, mock, channel, currency, policy_revision, amount_fen, gross_fen, fee_fen, credit_fen, flat_fee_fen, fee_bps, fee_rounding, expires_at, created_at]
      description: Immutable issued terms. amount_fen equals gross_fen; fee is flat_fee_fen plus ceil(amount_fen * fee_bps / 10000); gross minus fee equals positive credit. Acknowledged instructions are not payment success. Prior credit remains visible during discrepancies.
      properties:
        id: { type: string }
        tenant_id: { type: string }
        actor_id: { type: string }
        state: { type: string, enum: [queued, running, waiting, needs_attention, succeeded, closed], description: "closed is a customer-only display state for expired uncredited requests. Later verified payment may become succeeded. Staff retain the operational state." }
        version: { type: integer, minimum: 1, maximum: 9007199254740991 }
        credited: { type: boolean }
        mock: { type: boolean }
        channel: { $ref: '#/components/schemas/RechargeChannel' }
        currency: { type: string, const: CNY }
        policy_revision: { $ref: '#/components/schemas/RechargePolicyRevision' }
        amount_fen: { $ref: '#/components/schemas/RechargePositiveFen' }
        gross_fen: { $ref: '#/components/schemas/RechargePositiveFen' }
        fee_fen: { $ref: '#/components/schemas/RechargeNonnegativeFen' }
        credit_fen: { $ref: '#/components/schemas/RechargePositiveFen' }
        flat_fee_fen: { $ref: '#/components/schemas/RechargeNonnegativeFen' }
        fee_bps: { $ref: '#/components/schemas/RechargeFeeBps' }
        fee_rounding: { type: string, const: up }
        instructions:
          $ref: '#/components/schemas/RechargeInstructions'
        expires_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }
    DynamicExactInteger:
      type: string
      pattern: '^[0-9]+$'
    DynamicOptimizationBalance:
      type: object
      additionalProperties: false
      description: 'The LOCAL optimization balance. available_bytes is UNALLOCATED workspace credit, NOT the customer spendable total: credit already authorized to the assigned node is still usable. remaining_bytes is ALL certain credit and may include an offline old node''s outstanding hold, so it is not by itself "can generate now". usable_bytes is the "can generate now" amount (current enabled assigned node only); pending_node_bytes is certain credit held by any other (e.g. offline old) node. uncertain_bytes is crash-ambiguous evidence that is never auto-billed, refunded or reused. remaining_bytes = usable_bytes + pending_node_bytes.'
      required: [purchased_bytes, consumed_bytes, reserved_bytes, uncertain_bytes, available_bytes, remaining_bytes, usable_bytes, pending_node_bytes, status, availability_reason]
      properties:
        purchased_bytes: {$ref: '#/components/schemas/DynamicExactInteger', description: Gross purchased traffic; immutable purchase receipts are unchanged by adjustments.}
        adjustment_bytes: {type: string, pattern: '^(0|-?[1-9][0-9]*)$', description: Signed net applied manual adjustment in bytes. Effective allowance equals purchased_bytes plus adjustment_bytes. Absent on legacy responses.}
        consumed_bytes: {$ref: '#/components/schemas/DynamicExactInteger'}
        reserved_bytes: {$ref: '#/components/schemas/DynamicExactInteger', description: 'Sum over OPEN allocations of (authorized - consumed); it INCLUDES uncertain bytes.'}
        uncertain_bytes: {$ref: '#/components/schemas/DynamicExactInteger'}
        available_bytes: {$ref: '#/components/schemas/DynamicExactInteger', description: 'purchased + adjustment - consumed - reserved: unallocated workspace credit, not the spendable total.'}
        remaining_bytes: {$ref: '#/components/schemas/DynamicExactInteger', description: 'purchased + adjustment - consumed - uncertain: all certain credit, may include an offline old node''s hold.'}
        usable_bytes: {$ref: '#/components/schemas/DynamicExactInteger', description: 'available + unspent certain credit on the CURRENT enabled assigned binding/node. This is the amount generation can use now.'}
        pending_node_bytes: {$ref: '#/components/schemas/DynamicExactInteger', description: 'Certain credit held by nodes that are not the current enabled assignment; pending/unavailable, never consumed.'}
        status: {enum: [none, active, unavailable], description: 'none when nothing was purchased; active when usable_bytes > 0; unavailable when there is no usable credit yet, refined by availability_reason.'}
        availability_reason:
          enum: [none, usable, reconciliation_pending, assignment_pending, exhausted]
          description: 'Exact blockage for an unavailable status: reconciliation_pending means uncertain_bytes > 0 while nothing is usable (manual reconciliation, never an automatic refund); assignment_pending means certain credit sits on another node; exhausted means no remaining and no uncertain credit.'
    LastRecharge:
      type: object
      additionalProperties: false
      required: [id, amount_fen, channel, created_at]
      properties:
        id: {type: string}
        amount_fen: {$ref: '#/components/schemas/Fen'}
        channel: {enum: [manual, card, alipay, wechat_pay, usdt]}
        created_at: {type: string, format: date-time}
    LedgerItem:
      type: object
      additionalProperties: false
      required: [id, tenant_id, kind, channel, direction, amount_fen, wallet_delta_fen, reason, reference_id, created_at]
      properties:
        id: {type: string}
        tenant_id: {type: string}
        kind: {enum: [recharge, purchase, renewal, refund, manual_credit, manual_debit, referral_bonus, referral_bonus_reversal, payment_refund, payment_refund_reversal, seed]}
        channel: {enum: [manual, card, alipay, wechat_pay, usdt, wallet]}
        direction: {enum: [credit, debit]}
        amount_fen: {$ref: '#/components/schemas/Fen'}
        wallet_delta_fen: {type: string, pattern: '^-?[0-9]+$', description: Signed fen applied to the wallet}
        reason: {type: string}
        reference_id: {type: string}
        created_at: {type: string, format: date-time}
    LegacyRechargeIntent:
      type: object
      additionalProperties: false
      required: [id, tenant_id, actor_id, state, version, credited, mock, asset, network, asset_precision, expected_asset_units, gross_fen, fee_fen, credit_fen, expires_at, created_at]
      properties:
        id: { type: string }
        tenant_id: { type: string }
        actor_id: { type: string }
        state: { type: string, enum: [queued, running, waiting, needs_attention, succeeded, closed], description: "closed is a customer-only display state for expired uncredited requests. Later verified payment may become succeeded. Staff retain the operational state." }
        version: { type: integer, minimum: 1, maximum: 9007199254740991 }
        credited: { type: boolean }
        mock: { type: boolean }
        asset: { type: string }
        network: { type: string }
        asset_precision: { type: integer, minimum: 0, maximum: 18 }
        expected_asset_units: { type: string, pattern: '^[1-9][0-9]{0,77}$' }
        gross_fen: { type: string, pattern: '^[1-9][0-9]{0,18}$', description: Positive CNY fen no greater than 9223372036854775807 }
        fee_fen: { type: string, pattern: '^(0|[1-9][0-9]{0,18})$', description: Nonnegative CNY fen no greater than 9223372036854775807 }
        credit_fen: { type: string, pattern: '^[1-9][0-9]{0,18}$', description: Positive CNY fen no greater than 9223372036854775807 }
        destination: { type: string }
        expires_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }
    LegacyRechargeReplayInput:
      type: object
      additionalProperties: false
      required: [asset_units]
      description: Existing exact USDT receipt replay only. New USDT commands are disabled.
      properties:
        asset_units: { type: string, pattern: '^[1-9][0-9]{0,77}$' }
    ObservabilityAccessEventV1:
      type: object
      additionalProperties: false
      required: [schema_version, event_id, timestamp, event_type, transport]
      properties:
        schema_version: {const: 1}
        event_id: {type: string, description: Stable identity; an at-least-once retry carrying the same id is emitted once}
        timestamp: {type: string, format: date-time}
        event_type: {type: string, enum: [connection_open, connection_close, request_complete, authentication_failed]}
        node_id: {type: string}
        connection_id: {type: string}
        request_id: {type: string}
        tenant_id: {type: string, description: Present only after successful authentication}
        instance_id: {type: string, description: Present only after successful authentication}
        instance_ip: {type: string, description: Current exit IP resolved from the owning platform instance; omitted if unavailable}
        instance_note: {type: string, description: Current instance note; omitted if not set}
        assignment_id: {type: string, description: Observed route generation, not the PostgreSQL assignment primary key}
        source_ip: {type: string}
        source_port: {type: integer}
        destination_host: {type: string}
        destination_ip: {type: string, description: Present only when actually known}
        destination_port: {type: integer}
        upstream_host: {type: string}
        upstream_port: {type: integer}
        protocol: {type: string, enum: [http, connect, socks5]}
        transport: {type: string, enum: [tcp, udp], description: "Additive access-event v1 carrier fact. Always present: a record stored before the field existed is normalized to tcp, because the whole prior data plane was TCP-only. udp appears only for the SOCKS5 UDP associate; a record carrying an invalid or empty explicit value is never returned."}
        upload_bytes: {type: integer}
        download_bytes: {type: integer}
        duration_ms: {type: integer}
        outcome: {type: string, enum: [success, error, cancelled, incomplete]}
        failure_stage: {type: string, description: Bounded category when applicable}
        close_reason: {type: string, description: Bounded category when applicable}
        http_method: {type: string, description: Present only when visible}
        http_status: {type: integer}
    ObservabilityLogResponse:
      type: object
      additionalProperties: false
      required: [status, observed_at, items, next_cursor, coverage]
      properties:
        status: {type: string, enum: [ready, empty, unavailable, disabled]}
        reason: {type: string, description: Stable optional code; never raw storage error text}
        observed_at: {oneOf: [{type: string, format: date-time}, {type: 'null'}]}
        items: {type: array, maxItems: 100, items: {$ref: '#/components/schemas/ObservabilityAccessEventV1'}}
        next_cursor: {oneOf: [{type: string}, {type: 'null'}], description: Opaque continuation bound to this scope, window and filters; null when the page is the last}
        coverage: {type: string, enum: [forwarder_only]}
    ObservabilityMetricPoint:
      type: object
      additionalProperties: false
      required: [timestamp, upload_bps, download_bps, active_connections]
      properties:
        timestamp: {type: string, format: date-time}
        upload_bps: {oneOf: [{type: number}, {type: 'null'}], description: Bits per second observed by forwarders; a null sample is a gap and is never rendered as zero}
        download_bps: {oneOf: [{type: number}, {type: 'null'}]}
        active_connections: {oneOf: [{type: number}, {type: 'null'}]}
    ObservabilityMetricTotals:
      type: object
      additionalProperties: false
      required: [upload_bytes, download_bytes]
      properties:
        upload_bytes: {type: integer, description: Range increase in bytes, not a rate}
        download_bytes: {type: integer}
    ObservabilityMetricsResponse:
      type: object
      additionalProperties: false
      required: [status, observed_at, start, end, step_seconds, coverage, points, totals]
      properties:
        status: {type: string, enum: [ready, empty, unavailable, disabled, not_observed, stale], description: ready has samples; empty is a valid zero-window; unavailable/disabled are infrastructure states; not_observed is an instance with no forwarder data; stale is old relative to the selected period}
        reason: {type: string, description: Stable optional code explaining a non-ready status; never raw storage error text}
        observed_at: {oneOf: [{type: string, format: date-time}, {type: 'null'}], description: Freshest source sample time, distinct from query wall-clock time}
        start: {type: string, format: date-time}
        end: {type: string, format: date-time}
        step_seconds: {type: integer, minimum: 1}
        coverage: {type: string, enum: [forwarder_only], description: Only traffic observed by our forwarders is shown; direct upstream traffic is never presented as a measured zero}
        points: {type: array, maxItems: 720, items: {$ref: '#/components/schemas/ObservabilityMetricPoint'}}
        totals: {$ref: '#/components/schemas/ObservabilityMetricTotals'}
    OrderHistoryItem:
      type: object
      additionalProperties: false
      required: [id, tenant_id, kind, product, country, location_name, period, days, quantity, traffic_bytes, total_fen, status, created_at]
      properties:
        id: {type: string}
        tenant_id: {type: string}
        kind: {enum: [static, dynamic]}
        purchase_kind: {enum: [purchase, optimization], description: 'Mirrors the paid order discriminator: purchase (the default) buys a new IP or dynamic traffic; optimization is a paid access-optimization upgrade of the existing service named by service_id.'}
        service_id: {type: string, description: 'Upgrade target service id when purchase_kind=optimization; empty otherwise.'}
        product: {enum: [static_residential, static_datacenter, dynamic_residential], description: Stable product; a dynamic purchase is always dynamic_residential and its pack label is searchable only through q}
        country: {type: string}
        location_name: {type: string}
        period: {type: string}
        days: {type: integer, minimum: 0}
        quantity: {type: integer, minimum: 0}
        traffic_bytes: {oneOf: [{type: string, pattern: '^[0-9]+$'}, {type: 'null'}]}
        total_fen: {$ref: '#/components/schemas/Fen'}
        status: {enum: [provisioning, partial, needs_attention, completed, cancelled]}
        created_at: {type: string, format: date-time}
    RechargeChannel:
      type: string
      enum: [card, alipay, wechat_pay]
    RechargeChannelPolicy:
      type: object
      additionalProperties: false
      required: [channel, enabled, policy_revision, flat_fee_fen, fee_bps, fee_rounding, min_amount_fen, max_amount_fen, validity_seconds]
      properties:
        channel: { $ref: '#/components/schemas/RechargeChannel' }
        enabled: { type: boolean }
        policy_revision: { $ref: '#/components/schemas/RechargePolicyRevision' }
        flat_fee_fen: { $ref: '#/components/schemas/RechargeNonnegativeFen' }
        fee_bps: { $ref: '#/components/schemas/RechargeFeeBps' }
        fee_rounding: { type: string, const: up }
        min_amount_fen: { $ref: '#/components/schemas/RechargePositiveFen' }
        max_amount_fen: { $ref: '#/components/schemas/RechargePositiveFen' }
        validity_seconds: { type: integer, minimum: 1, maximum: 9007199254740991 }
    RechargeConfig:
      type: object
      additionalProperties: false
      required: [tenant_id, currency, mock, allow_unverified, can_create, eligibility_reason, channels, disabled_channels]
      properties:
        tenant_id: { type: string }
        currency: { type: string, enum: [CNY] }
        mock: { type: boolean }
        allow_unverified: { type: boolean, enum: [false] }
        can_create: { type: boolean }
        eligibility_reason: { type: string, enum: ["", read_only, verification_required, tenant_inactive, channel_unavailable] }
        channels:
          type: array
          minItems: 1
          maxItems: 3
          uniqueItems: true
          items: { $ref: '#/components/schemas/RechargeChannelPolicy' }
          description: A supported nonempty subset of the published CNY channel set, each channel at most once.
        disabled_channels:
          type: array
          minItems: 1
          maxItems: 1
          items: { type: string, const: usdt }
    RechargeCreateInput:
      oneOf:
        - { $ref: '#/components/schemas/CnyRechargeCreateInput' }
        - { $ref: '#/components/schemas/LegacyRechargeReplayInput' }
    RechargeFeeBps:
      type: string
      pattern: '^(0|[1-9][0-9]{0,3}|10000)$'
    RechargeHistoryItem:
      type: object
      additionalProperties: false
      required: [id, tenant_id, kind, channel, direction, amount_fen, credit_fen, state, reason, created_at]
      properties:
        id: {type: string}
        tenant_id: {type: string}
        kind: {enum: [gateway, manual]}
        channel: {enum: [manual, card, alipay, wechat_pay, usdt]}
        direction: {enum: [credit, debit]}
        amount_fen: {oneOf: [{$ref: '#/components/schemas/Fen'}, {type: 'null'}], description: Null only for a legacy USDT intent whose CNY amount is unknown}
        credit_fen: {$ref: '#/components/schemas/Fen'}
        state: {enum: [queued, running, waiting, needs_attention, succeeded, closed], description: "Customer reads show expired uncredited gateway requests as closed; staff retain the operational state."}
        reason: {type: string}
        created_at: {type: string, format: date-time}
    RechargeInstructions:
      description: Provider-displayed directions scoped to the owning authorized customer and served no-store. Present only while the intent is actionable (acknowledged, unpaid, unexpired) and only for the owning customer; staff reads, lists and terminal/credited intents omit them. None of these fields is payment evidence; only the backend ledger result renders success. A development mock instruction is a fictional reference plus a same-origin simulated gateway link.
      oneOf:
        - $ref: '#/components/schemas/RechargeMockInstructions'
        - $ref: '#/components/schemas/StripeCardInstructions'
        - $ref: '#/components/schemas/StripeWeChatInstructions'
        - $ref: '#/components/schemas/StripeAlipayInstructions'
    RechargeIntent:
      oneOf:
        - { $ref: '#/components/schemas/CnyRechargeIntent' }
        - { $ref: '#/components/schemas/LegacyRechargeIntent' }
    RechargeIntentPage:
      type: object
      additionalProperties: false
      required: [items, total, limit, offset]
      properties:
        items:
          type: array
          items: { $ref: '#/components/schemas/RechargeIntent' }
        total: { type: integer, minimum: 0 }
        limit: { type: integer, minimum: 1, maximum: 100 }
        offset: { type: integer, minimum: 0, maximum: 1000000 }
    RechargeMockInstructions:
      type: object
      additionalProperties: false
      required: [kind, reference]
      properties:
        kind: { type: string, const: mock }
        reference: { type: string, minLength: 1, maxLength: 256, description: Fictional opaque reference; no private provider handle. }
        payment_url: { type: string, pattern: "^/api/v1/mock/payments/", description: Same-origin simulated gateway. Opening this page does not make a payment. }
    RechargeNonnegativeFen:
      type: string
      pattern: '^(0|[1-9][0-9]{0,18})$'
      description: Canonical nonnegative CNY fen no greater than 9223372036854775807; validated with exact integer arithmetic.
    RechargePolicyRevision:
      type: string
      minLength: 1
      maxLength: 128
      description: Public immutable pricing revision; not a provider configuration revision.
    RechargePositiveFen:
      type: string
      pattern: '^[1-9][0-9]{0,18}$'
      description: Canonical positive CNY fen no greater than 9223372036854775807; validated with exact integer arithmetic.
    ReferralInvitee:
      type: object
      additionalProperties: false
      required: [id, display_label, registered_at, created_workspace_count, qualifying_orders, earned_fen, reversed_fen]
      properties:
        id: {type: string}
        display_label: {type: string, description: Masked; never a full phone or email}
        registered_at: {type: string, format: date-time}
        created_workspace_count: {type: integer, minimum: 0}
        qualifying_orders: {type: integer, minimum: 0}
        earned_fen: {$ref: '#/components/schemas/Fen'}
        reversed_fen: {$ref: '#/components/schemas/Fen'}
    ReferralLink:
      type: object
      additionalProperties: false
      required: [token, enabled, version, created_at]
      properties:
        token: {type: string, description: 'Opaque share token; the customer builds its own /app?mode=register&ref=<token> origin URL and no inviter identity is exposed'}
        enabled: {type: boolean}
        version: {type: integer, minimum: 1}
        created_at: {type: string, format: date-time}
    ReferralReward:
      type: object
      additionalProperties: false
      required: [id, invitee_label, source_kind, source_reference, attribution_source, rate_bps, eligible_paid_fen, earned_fen, reversed_fen, outstanding_recovery_fen, created_at, updated_at]
      properties:
        id: {type: string}
        invitee_label: {type: string}
        source_kind: {enum: [static_purchase, dynamic_purchase, renewal, static_optimization]}
        source_reference: {type: string, description: Opaque; no buyer workspace/resource/order contents are exposed}
        attribution_source: {enum: [workspace_creator, buyer]}
        rate_bps: {type: integer, minimum: 0, maximum: 10000}
        eligible_paid_fen: {$ref: '#/components/schemas/Fen'}
        earned_fen: {$ref: '#/components/schemas/Fen'}
        reversed_fen: {$ref: '#/components/schemas/Fen'}
        outstanding_recovery_fen: {$ref: '#/components/schemas/Fen'}
        created_at: {type: string, format: date-time}
        updated_at: {type: string, format: date-time}
    ReferralSummary:
      type: object
      additionalProperties: false
      required: [tenant_id, program_enabled, can_generate_link, link, first_order_bps, subsequent_order_bps, invited_users, qualifying_orders, earned_fen, reversed_fen, outstanding_recovery_fen]
      properties:
        tenant_id: {type: string}
        program_enabled: {type: boolean}
        can_generate_link: {type: boolean}
        link: {oneOf: [{$ref: '#/components/schemas/ReferralLink'}, {type: 'null'}]}
        first_order_bps: {type: integer}
        subsequent_order_bps: {type: integer}
        invited_users: {type: integer, minimum: 0}
        qualifying_orders: {type: integer, minimum: 0}
        earned_fen: {$ref: '#/components/schemas/Fen'}
        reversed_fen: {$ref: '#/components/schemas/Fen'}
        outstanding_recovery_fen: {$ref: '#/components/schemas/Fen'}
    SignedFen: {type: string, pattern: '^(0|-?[1-9][0-9]*)$', description: Signed integer CNY fen; a negative wallet balance is debt}
    StripeAlipayInstructions:
      type: object
      additionalProperties: false
      required: [kind, reference, payment_url]
      properties:
        kind: { type: string, const: stripe_alipay_redirect }
        reference: { type: string, minLength: 1, maxLength: 128 }
        payment_url: { type: string, pattern: '^https://(hooks|pm-redirects)\.stripe\.com/', description: Verified Stripe Alipay redirect; exact HTTPS host only, no userinfo and no alternate port. }
    StripeCardInstructions:
      type: object
      additionalProperties: false
      required: [kind, reference, publishable_key, client_secret, return_url]
      description: Card confirmation material for one unconfirmed PaymentIntent, confirmed client-side with the current Payment Element. The client secret is sensitive and scoped to this single owned intent; it is never logged, listed for another user, persisted in client storage or carried in a query string.
      properties:
        kind: { type: string, const: stripe_card }
        reference: { type: string, minLength: 1, maxLength: 128, description: Stripe PaymentIntent id; not a secret. }
        publishable_key: { type: string, pattern: "^pk_(test|live)_[A-Za-z0-9]+$" }
        client_secret: { type: string, minLength: 1, maxLength: 512 }
        return_url: { type: string, format: uri, description: Server-owned return target derived from the trusted customer origin. }
    StripeWeChatInstructions:
      type: object
      additionalProperties: false
      required: [kind, reference, qr_data]
      properties:
        kind: { type: string, const: stripe_wechat_qr }
        reference: { type: string, minLength: 1, maxLength: 128 }
        qr_data: { type: string, minLength: 1, maxLength: 4096, description: Raw bounded text rendered as a QR code; never HTML and never a navigation target. }
    WalletSummary:
      type: object
      additionalProperties: false
      required: [tenant_id, balance_fen, spent_month_fen, month_start, month_end, last_recharge]
      properties:
        tenant_id: {type: string}
        balance_fen: {$ref: '#/components/schemas/SignedFen'}
        spent_month_fen: {$ref: '#/components/schemas/Fen'}
        month_start: {type: string, format: date-time}
        month_end: {type: string, format: date-time}
        last_recharge: {oneOf: [{$ref: '#/components/schemas/LastRecharge'}, {type: 'null'}]}
