openapi: 3.1.0
info:
  title: Phylex Reseller API
  version: 1.9.0
  description: Complete tenant-scoped control plane for approved Phylex reseller workspaces and white-label customer panels. The default shared tenant quota is 1,200 read RPM and 300 write RPM and can be raised by staff per reseller. Inspect RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset on every response; a 429 also includes Retry-After.
  license:
    name: Proprietary
    url: https://phylex.net/terms
servers:
  - url: https://phylex.net/api/proxy/reseller-api/v1
security:
  - bearerAuth: []
paths:
  /me:
    get:
      operationId: getResellerWorkspace
      summary: Read the reseller workspace
      x-required-scope: account:read
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
  /plans:
    get:
      operationId: listResellerPlans
      summary: List the enabled reseller catalog
      x-required-scope: catalog:read
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
  /payment-methods:
    get:
      operationId: listResellerPaymentMethods
      summary: List payment methods enabled for this reseller
      description: Returns public method identifiers and display data only; gateway credentials and internal configuration are never exposed.
      x-required-scope: payments:read
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
  /workspace/summary:
    get:
      operationId: getResellerWorkspaceSummary
      summary: Read control-plane counters and payout availability
      x-required-scope: workspace:read
      responses: { "200": { $ref: "#/components/responses/Success" } }
  /workspace/catalog:
    get:
      operationId: getResellerWorkspaceCatalog
      summary: Read tenant catalog overrides
      x-required-scope: catalog:read
      responses: { "200": { $ref: "#/components/responses/Success" } }
    put:
      operationId: updateResellerWorkspaceCatalog
      summary: Create or replace a tenant catalog override
      x-required-scope: catalog:write
      responses: { "200": { $ref: "#/components/responses/Success" } }
    delete:
      operationId: deleteResellerWorkspaceCatalogOverride
      summary: Remove one tenant catalog override
      x-required-scope: catalog:write
      parameters:
        - { name: plan_ref, in: query, required: true, schema: { type: string, pattern: "^PHX-RPLAN-[0-9A-F]+$" } }
      responses: { "200": { $ref: "#/components/responses/Success" } }
  /workspace/storefront:
    get:
      operationId: getResellerStorefront
      summary: Read white-label storefront configuration
      x-required-scope: workspace:read
      responses: { "200": { $ref: "#/components/responses/Success" } }
    patch:
      operationId: updateResellerStorefront
      summary: Replace white-label storefront configuration
      x-required-scope: workspace:write
      responses: { "200": { $ref: "#/components/responses/Success" } }
  /workspace/payment-methods:
    get:
      operationId: getResellerWorkspacePaymentMethods
      summary: Read platform eligibility and tenant payment-method state
      x-required-scope: payments:read
      responses: { "200": { $ref: "#/components/responses/Success" } }
    put:
      operationId: setResellerWorkspacePaymentMethod
      summary: Enable or disable an eligible hosted payment method
      x-required-scope: workspace:write
      responses: { "200": { $ref: "#/components/responses/Success" } }
  /workspace/domains:
    get:
      operationId: listResellerDomains
      summary: List white-label domains and verification state
      x-required-scope: domains:read
      responses: { "200": { $ref: "#/components/responses/Success" } }
    post:
      operationId: createResellerDomain
      summary: Add a white-label domain
      x-required-scope: domains:write
      responses: { "201": { $ref: "#/components/responses/Success" } }
  /workspace/domains/{domain_ref}:
    parameters: [ { $ref: "#/components/parameters/DomainRefPath" } ]
    delete:
      operationId: deleteResellerDomain
      summary: Delete a tenant domain
      x-required-scope: domains:write
      responses: { "200": { $ref: "#/components/responses/Success" } }
  /workspace/domains/{domain_ref}/{verification}:
    parameters:
      - $ref: "#/components/parameters/DomainRefPath"
      - { name: verification, in: path, required: true, schema: { type: string, enum: [verify, verify-routing] } }
    post:
      operationId: verifyResellerDomain
      summary: Verify domain ownership or routing
      x-required-scope: domains:write
      responses: { "200": { $ref: "#/components/responses/Success" } }
  /workspace/analytics:
    get:
      operationId: getResellerAnalytics
      summary: Read tenant revenue and customer analytics
      x-required-scope: analytics:read
      responses: { "200": { $ref: "#/components/responses/Success" } }
  /workspace/ledger:
    get:
      operationId: getResellerLedger
      summary: Read tenant ledger entries
      x-required-scope: ledger:read
      responses: { "200": { $ref: "#/components/responses/Success" } }
  /workspace/payout-options:
    get:
      operationId: getResellerPayoutOptions
      summary: Read payout availability, verified-destination state, and rail limits
      x-required-scope: payouts:read
      responses: { "200": { $ref: "#/components/responses/Success" } }
  /workspace/payouts:
    get:
      operationId: listResellerPayouts
      summary: List tenant payout requests and available payout balance
      x-required-scope: payouts:read
      responses: { "200": { $ref: "#/components/responses/Success" } }
    post:
      operationId: createResellerPayout
      summary: Request a payout to the owner's previously verified destination
      description: Requires Idempotency-Key. Destination credentials cannot be supplied through the API and are never returned.
      x-required-scope: payouts:write
      parameters: [ { $ref: "#/components/parameters/IdempotencyKey" } ]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [amount]
              properties:
                amount: { type: number, exclusiveMinimum: 0 }
                currency: { type: string, minLength: 3, maxLength: 8 }
                method: { type: string, maxLength: 64 }
              additionalProperties: false
      responses:
        "201": { $ref: "#/components/responses/Success" }
        "409": { $ref: "#/components/responses/Error" }
  /workspace/api-keys:
    get:
      operationId: listResellerAPIKeys
      summary: List tenant API keys without secrets
      x-required-scope: keys:read
      responses: { "200": { $ref: "#/components/responses/Success" } }
    post:
      operationId: createResellerAPIKey
      summary: Create a child key whose scopes cannot exceed the caller
      description: The secret is returned once.
      x-required-scope: keys:write
      responses: { "201": { $ref: "#/components/responses/Success" } }
  /workspace/api-keys/{api_key_ref}:
    delete:
      operationId: revokeResellerAPIKey
      summary: Revoke another tenant key; a key cannot revoke itself
      x-required-scope: keys:write
      parameters:
        - { name: api_key_ref, in: path, required: true, schema: { type: string, pattern: "^PHX-RAK-[0-9A-F]+$" } }
      responses: { "200": { $ref: "#/components/responses/Success" } }
  /workspace/webhooks:
    get:
      operationId: listResellerWebhooks
      summary: List webhook endpoints and supported events
      x-required-scope: webhooks:read
      responses: { "200": { $ref: "#/components/responses/Success" } }
    post:
      operationId: createResellerWebhook
      summary: Create a signed public-HTTPS webhook endpoint
      x-required-scope: webhooks:write
      responses: { "201": { $ref: "#/components/responses/Success" } }
  /workspace/webhooks/{webhook_ref}:
    parameters: [ { $ref: "#/components/parameters/WebhookRefPath" } ]
    patch:
      operationId: updateResellerWebhook
      summary: Update URL, event subscriptions, or status
      x-required-scope: webhooks:write
      responses: { "200": { $ref: "#/components/responses/Success" } }
    delete:
      operationId: deleteResellerWebhook
      summary: Delete an endpoint and delivery history
      x-required-scope: webhooks:write
      responses: { "200": { $ref: "#/components/responses/Success" } }
  /workspace/webhooks/{webhook_ref}/deliveries:
    parameters: [ { $ref: "#/components/parameters/WebhookRefPath" } ]
    get:
      operationId: listResellerWebhookDeliveries
      summary: List the last 100 deliveries
      x-required-scope: webhooks:read
      responses: { "200": { $ref: "#/components/responses/Success" } }
  /workspace/webhooks/{webhook_ref}/{action}:
    parameters:
      - $ref: "#/components/parameters/WebhookRefPath"
      - { name: action, in: path, required: true, schema: { type: string, enum: [test, replay, rotate-secret] } }
    post:
      operationId: actOnResellerWebhook
      summary: Test, replay, or rotate a signing secret
      x-required-scope: webhooks:write
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "202": { $ref: "#/components/responses/Success" }
  /customers:
    get:
      operationId: listResellerCustomers
      summary: List reseller customers
      x-required-scope: customers:read
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
    post:
      operationId: createResellerCustomer
      summary: Create or link a reseller customer
      x-required-scope: customers:write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CustomerCreate"
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "201": { $ref: "#/components/responses/Success" }
        "400": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
  /customers/invite:
    post:
      operationId: inviteResellerCustomer
      summary: Send a customer invitation
      description: Use the stable customer_ref returned by customer creation or listing.
      x-required-scope: customers:write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [customer_ref]
              properties:
                customer_ref:
                  $ref: "#/components/schemas/CustomerRef"
              additionalProperties: false
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "400": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
    get:
      operationId: getResellerCustomer
      summary: Read one tenant-scoped customer
      x-required-scope: customers:read
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "404": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}/profile:
    parameters: [ { $ref: "#/components/parameters/CustomerRefPath" } ]
    get:
      operationId: getResellerCustomerProfile
      summary: Read customer identity and audited history
      x-required-scope: customers:read
      responses: { "200": { $ref: "#/components/responses/Success" } }
    patch:
      operationId: updateResellerCustomerProfile
      summary: Update names or reseller external reference
      x-required-scope: customers:write
      responses: { "200": { $ref: "#/components/responses/Success" } }
  /customers/{customer_ref}/payer-profile:
    parameters: [ { $ref: "#/components/parameters/CustomerRefPath" } ]
    get:
      operationId: getResellerCustomerPayerProfile
      summary: Read current payer details and reseller audit history
      x-required-scope: customers:read
      responses: { "200": { $ref: "#/components/responses/Success" } }
    put:
      operationId: replaceResellerCustomerPayerProfile
      summary: Replace payer details for future financial records
      description: Requires a reason and exact customer_ref confirmation. Existing invoice and transaction snapshots remain immutable.
      x-required-scope: customers:write
      responses: { "200": { $ref: "#/components/responses/Success" } }
  /customers/{customer_ref}/balance:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
    get:
      operationId: getResellerCustomerBalance
      summary: Read the authoritative tenant wallet including holds
      x-required-scope: finance:read
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "404": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}/invoices:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
    get:
      operationId: listResellerCustomerInvoices
      summary: Read up to 200 tenant invoices
      x-required-scope: finance:read
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "404": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}/invoices/{invoice_ref}:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
      - { name: invoice_ref, in: path, required: true, schema: { type: string, pattern: "^PHX-INV-[0-9A-F]+$" } }
    get:
      operationId: getResellerCustomerInvoice
      summary: Read one invoice and its immutable line-item snapshot
      x-required-scope: finance:read
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "404": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}/transactions:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
    get:
      operationId: listResellerCustomerTransactions
      summary: Read up to 200 tenant transactions
      x-required-scope: finance:read
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "404": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}/transactions/{transaction_ref}:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
      - { name: transaction_ref, in: path, required: true, schema: { type: string, pattern: "^PHX-TXN-[0-9A-F]+$" } }
    get:
      operationId: getResellerCustomerTransaction
      summary: Read one tenant-scoped customer transaction
      x-required-scope: finance:read
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "404": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}/services:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
    get:
      operationId: listResellerCustomerServices
      summary: List every service owned by one reseller customer
      x-required-scope: services:read
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "404": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}/services/{service_ref}:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
      - $ref: "#/components/parameters/ServiceRefPath"
    get:
      operationId: getResellerCustomerService
      summary: Read one service after both customer and service tenant checks
      x-required-scope: services:read
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "404": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}/services/{service_ref}/auto-renew:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
      - $ref: "#/components/parameters/ServiceRefPath"
    put:
      operationId: setResellerCustomerServiceAutoRenew
      summary: Set automatic renewal for an eligible customer service
      x-required-scope: services:write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [enabled]
              properties:
                enabled: { type: boolean }
              additionalProperties: false
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "404": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}/services/{service_ref}/metrics:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
      - $ref: "#/components/parameters/ServiceRefPath"
    get:
      operationId: getResellerCustomerServiceMetrics
      summary: Read bounded customer-visible service metrics
      description: Returns stored CPU, memory, disk, network-rate and traffic series without provider or internal numeric identifiers.
      x-required-scope: services:read
      parameters:
        - name: preset
          in: query
          schema: { type: string, enum: [hour, day, week, month] }
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "404": { $ref: "#/components/responses/Error" }
        "503": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}/services/{service_ref}/power:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
      - $ref: "#/components/parameters/ServiceRefPath"
    post:
      operationId: powerResellerCustomerService
      summary: Start, stop, or reboot a supported customer KVM
      description: Requires a stable Idempotency-Key. Reusing the same key with a different action is rejected, and an uncertain processing result must not be retried with a new key.
      x-required-scope: services:power
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          schema: { type: string, minLength: 8, maxLength: 128 }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [action]
              properties:
                action: { type: string, enum: [start, stop, reboot] }
              additionalProperties: false
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "400": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
        "428": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}/services/{service_ref}/manager:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
      - $ref: "#/components/parameters/ServiceRefPath"
    get:
      operationId: getResellerCustomerServiceManager
      summary: Read the native customer manager payload
      description: Resolves the customer and service inside the authenticated reseller tenant, then uses the same provider-safe handler as Atlas. Supports KVM, game, Storage Box, S3, dedicated, IP subnet and DDoS protection services.
      x-required-scope: services:read
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "404": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}/services/{service_ref}/manager/{resource}:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
      - $ref: "#/components/parameters/ServiceRefPath"
      - $ref: "#/components/parameters/ManagerResourcePath"
    get:
      operationId: readResellerCustomerServiceManagerResource
      summary: Read a native manager resource
      description: Read-only details use services:read. Console, reinstall, network, files, backups and credentials use their dedicated scopes.
      x-required-scope: resource-specific
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
    post:
      operationId: mutateResellerCustomerServiceManagerResource
      summary: Run a native manager action
      description: Native request bodies and validation match the Atlas customer manager. General mutations require services:manage; sensitive resource families require their dedicated scope.
      x-required-scope: resource-specific
      requestBody:
        content:
          application/json:
            schema: { type: object, additionalProperties: true }
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "202": { $ref: "#/components/responses/Success" }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
    patch:
      operationId: patchResellerCustomerServiceManagerResource
      summary: Patch a native manager resource
      x-required-scope: resource-specific
      requestBody:
        content:
          application/json:
            schema: { type: object, additionalProperties: true }
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
    put:
      operationId: putResellerCustomerServiceManagerResource
      summary: Replace or upload a native manager resource
      x-required-scope: resource-specific
      requestBody:
        content:
          application/json:
            schema: { type: object, additionalProperties: true }
          application/octet-stream:
            schema: { type: string, format: binary }
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "202": { $ref: "#/components/responses/Success" }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
    delete:
      operationId: deleteResellerCustomerServiceManagerResource
      summary: Delete a native manager resource
      x-required-scope: resource-specific
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "204": { description: Deleted }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}/services/{service_ref}/manager/{resource}/{operation}:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
      - $ref: "#/components/parameters/ServiceRefPath"
      - $ref: "#/components/parameters/ManagerResourcePath"
      - $ref: "#/components/parameters/ManagerOperationPath"
    get:
      operationId: readResellerCustomerServiceManagerOperation
      summary: Read a nested native manager operation
      description: Used for bounded metrics variants. Raw console websocket proxying is rejected; connect directly to the URL returned by the console session operation.
      x-required-scope: resource-specific
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
    post:
      operationId: mutateResellerCustomerServiceManagerOperation
      summary: Run a nested native manager operation
      x-required-scope: resource-specific
      requestBody:
        content:
          application/json:
            schema: { type: object, additionalProperties: true }
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "202": { $ref: "#/components/responses/Success" }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}/services/{service_ref}/access:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
      - $ref: "#/components/parameters/ServiceRefPath"
    get:
      operationId: listResellerCustomerServiceAccess
      summary: List available capabilities, accepted members, and pending invitations
      x-required-scope: services:read
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "404": { $ref: "#/components/responses/Error" }
    post:
      operationId: inviteResellerCustomerServiceMember
      summary: Invite a same-tenant identity to access the customer service
      description: The email must already belong to a user in this reseller tenant. Billing and ownership remain with the customer; unknown and cross-reseller identities return the same conflict.
      x-required-scope: services:write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ServiceAccessMutation"
      responses:
        "201": { $ref: "#/components/responses/Success" }
        "400": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}/services/{service_ref}/access/members/{member_ref}:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
      - $ref: "#/components/parameters/ServiceRefPath"
      - $ref: "#/components/parameters/MemberRefPath"
    patch:
      operationId: updateResellerCustomerServiceMember
      summary: Update one delegated member
      x-required-scope: services:write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ServiceAccessMutation"
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "404": { $ref: "#/components/responses/Error" }
    delete:
      operationId: revokeResellerCustomerServiceMember
      summary: Revoke an accepted member
      x-required-scope: services:write
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "404": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}/services/{service_ref}/access/invitations/{invitation_ref}:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
      - $ref: "#/components/parameters/ServiceRefPath"
      - $ref: "#/components/parameters/InvitationRefPath"
    delete:
      operationId: revokeResellerCustomerServiceInvitation
      summary: Revoke a pending invitation
      x-required-scope: services:write
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "404": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}/services/{service_ref}/transfer:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
      - $ref: "#/components/parameters/ServiceRefPath"
    post:
      operationId: transferResellerCustomerService
      summary: Transfer a service to another customer in the same reseller tenant
      description: Requires Idempotency-Key and confirm_transfer=true. Linked add-ons move atomically and immutable financial evidence is copied for the recipient.
      x-required-scope: services:transfer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [recipient_customer_ref, confirm_transfer]
              properties:
                recipient_customer_ref: { type: string, pattern: "^PHX-RCU-[0-9A-F]+$" }
                confirm_transfer: { type: boolean, const: true }
                comment: { type: string, maxLength: 1000 }
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "404": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
        "428": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}/orders:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
    get:
      operationId: listResellerCustomerOrders
      summary: Read up to 200 tenant service orders
      x-required-scope: finance:read
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "404": { $ref: "#/components/responses/Error" }
    post:
      operationId: createResellerCustomerOrder
      summary: Buy an enabled reseller plan from the customer's authoritative balance
      description: Requires Idempotency-Key. The API never creates or adjusts balance; insufficient funds fails without provisioning.
      x-required-scope: orders:write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [type, plan_code]
              additionalProperties: true
              properties:
                type: { type: string }
                plan_code: { type: string }
                period_ref: { type: string }
                region_ref: { type: string }
                os_image_ref: { type: string }
                quote_version: { type: string }
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "402": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
        "428": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}/orders/{order_ref}:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
      - { name: order_ref, in: path, required: true, schema: { type: string, pattern: "^PHX-ORD-[0-9A-F]+$" } }
    get:
      operationId: getResellerCustomerOrder
      summary: Read one tenant-scoped service order
      x-required-scope: finance:read
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "404": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}/payment-links:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
    get:
      operationId: listResellerCustomerPaymentLinks
      summary: List payment-link status without exposing tokens
      x-required-scope: finance:read
      responses:
        "200": { $ref: "#/components/responses/Success" }
    post:
      operationId: createResellerCustomerPaymentLink
      summary: Create a customer-bound single-use storefront payment link
      description: The customer must authenticate before redemption. Lifetime is bounded to 300-86400 seconds and the selected method is revalidated at checkout. Reusing the same Idempotency-Key with an identical payload returns the original link; changed payloads are rejected.
      x-required-scope: payments:write
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [amount]
              additionalProperties: false
              properties:
                amount: { type: number, minimum: 1, maximum: 100000 }
                currency: { type: string, pattern: "^[A-Z]{3}$" }
                payment_method: { type: string }
                expires_in_seconds: { type: integer, minimum: 300, maximum: 86400, default: 3600 }
      responses:
        "201": { $ref: "#/components/responses/Success" }
        "400": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
  /customers/{customer_ref}/payment-links/{payment_link_ref}:
    parameters:
      - $ref: "#/components/parameters/CustomerRefPath"
      - { name: payment_link_ref, in: path, required: true, schema: { type: string, pattern: "^PHX-RPL-[0-9A-F]+$" } }
    get:
      operationId: getResellerCustomerPaymentLink
      summary: Read payment-link status without exposing its token
      x-required-scope: finance:read
      responses: { "200": { $ref: "#/components/responses/Success" } }
    delete:
      operationId: revokeResellerCustomerPaymentLink
      summary: Revoke an unused payment link
      x-required-scope: payments:write
      responses: { "200": { $ref: "#/components/responses/Success" } }
  /tickets:
    get:
      operationId: listResellerTickets
      summary: Search reseller tickets
      x-required-scope: tickets:read
      parameters:
        - name: q
          in: query
          schema: { type: string, maxLength: 120 }
        - name: status
          in: query
          schema: { type: string, maxLength: 40 }
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
    post:
      operationId: createResellerTicket
      summary: Open a reseller support ticket
      x-required-scope: tickets:write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TicketCreate"
      responses:
        "201": { $ref: "#/components/responses/Success" }
        "400": { $ref: "#/components/responses/Error" }
  /tickets/messages:
    parameters:
      - $ref: "#/components/parameters/TicketRef"
    get:
      operationId: listResellerTicketMessages
      summary: Read messages from an owned reseller ticket
      x-required-scope: tickets:read
      responses:
        "200": { $ref: "#/components/responses/Success" }
        "400": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
    post:
      operationId: createResellerTicketMessage
      summary: Reply to an owned reseller ticket
      x-required-scope: tickets:write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [body]
              properties:
                body: { type: string, minLength: 1 }
              additionalProperties: false
      responses:
        "201": { $ref: "#/components/responses/Success" }
        "400": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
  /tickets/{ticket_ref}:
    parameters:
      - { name: ticket_ref, in: path, required: true, schema: { type: string, pattern: "^PHX-RT-[0-9A-F]+$" } }
    get:
      operationId: getResellerTicket
      summary: Read one ticket with messages
      x-required-scope: tickets:read
      responses: { "200": { $ref: "#/components/responses/Success" } }
    patch:
      operationId: updateResellerTicket
      summary: Close or reopen a ticket
      x-required-scope: tickets:write
      responses: { "200": { $ref: "#/components/responses/Success" } }
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: phxrk
      description: Reseller API key. Alternatively use X-Reseller-API-Key, but never send both headers.
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema: { type: string, minLength: 1, maxLength: 128 }
    Limit:
      name: limit
      in: query
      schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
    Offset:
      name: offset
      in: query
      schema: { type: integer, minimum: 0, default: 0 }
    TicketRef:
      name: ticket_ref
      in: query
      required: true
      schema:
        type: string
        pattern: "^PHX-RT-[0-9A-F]+$"
    CustomerRefPath:
      name: customer_ref
      in: path
      required: true
      schema:
        $ref: "#/components/schemas/CustomerRef"
    ServiceRefPath:
      name: service_ref
      in: path
      required: true
      schema:
        type: string
        pattern: "^PHX-SVC-[0-9A-F]+$"
    ManagerResourcePath:
      name: resource
      in: path
      required: true
      schema:
        type: string
        pattern: "^[a-z0-9][a-z0-9_-]{0,31}$"
      description: Native manager resource such as metrics, console, files, backups, network, os, reinstall, settings or action.
    ManagerOperationPath:
      name: operation
      in: path
      required: true
      schema:
        type: string
        pattern: "^[a-z0-9][a-z0-9_-]{0,31}$"
    MemberRefPath:
      name: member_ref
      in: path
      required: true
      schema:
        type: string
        pattern: "^PHX-SAM-[0-9A-F]+$"
    InvitationRefPath:
      name: invitation_ref
      in: path
      required: true
      schema:
        type: string
        pattern: "^PHX-SAI-[0-9A-F]+$"
    DomainRefPath:
      name: domain_ref
      in: path
      required: true
      schema: { type: string, pattern: "^PHX-RDOM-[0-9A-F]+$" }
    WebhookRefPath:
      name: webhook_ref
      in: path
      required: true
      schema: { type: string, format: uuid }
  schemas:
    CustomerRef:
      type: string
      pattern: "^PHX-RCU-[0-9A-F]+$"
    CustomerCreate:
      type: object
      required: [email]
      properties:
        email: { type: string, format: email }
        password: { type: string, minLength: 8, maxLength: 72, writeOnly: true }
        first_name: { type: string, maxLength: 120 }
        last_name: { type: string, maxLength: 120 }
        name: { type: string, maxLength: 240 }
        country_code: { type: string, pattern: "^[A-Za-z]{2}$" }
        external_ref: { type: string, maxLength: 128 }
      additionalProperties: false
    ServiceAccessMutation:
      type: object
      properties:
        email: { type: string, format: email, maxLength: 320 }
        permission: { type: string, enum: [read, manage] }
        capabilities:
          type: array
          uniqueItems: true
          items: { type: string }
      additionalProperties: false
    TicketCreate:
      type: object
      required: [subject]
      properties:
        subject: { type: string, minLength: 1 }
        body: { type: string }
        customer_ref: { $ref: "#/components/schemas/CustomerRef" }
        category_ref: { type: string, pattern: "^PHX-CAT-[0-9A-F]+$" }
        level: { type: string }
        service_type: { type: string }
      additionalProperties: false
    Error:
      type: object
      required: [error]
      properties:
        error: { type: string }
      additionalProperties: true
  responses:
    Success:
      description: Successful tenant-scoped response.
      content:
        application/json:
          schema:
            type: object
            additionalProperties: true
    Error:
      description: Stable machine-readable API error.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
