openapi: 3.1.0
info:
  title: ARKZ Human API
  version: 1.1.0
  summary: Access, query, and invoke any ARK (a human knowledge twin) over HTTP.
  description: |
    The **Human API** turns any ARK on ARKZ into something a third party, a
    machine, or an AI agent can call: read its public profile, discover what it
    knows, and ask it questions answered in the creator's voice. Every call is
    authenticated with a scoped, revocable credential and metered to the ARK
    **owner's** wallet.

    ## Authentication
    Send a credential in the Bearer `Authorization` header (or the `x-ark-api-key` header):
      * **API key**: `ark_live_...`, created at Settings → API & Connections.
      * **OAuth 2.1 access credential**: `arka_...`, issued by the Authorization
        Server (see `https://arkz.ai/.well-known/oauth-authorization-server`).

    Both are scoped (`oauth_scope[]`) and bound to a single ARK + owner.

    ## Billing
    `ask_ark` charges `platform_config.api_call_cost_cents` (default 5¢) to the
    ARK owner's wallet, atomically and idempotently. Reads are free.

    ## Shape
    This is a single-endpoint action gateway: POST a JSON body whose `action`
    field selects the operation. This keeps the surface identical across REST and
    the MCP connector, which exposes the same three actions as MCP tools.
  contact:
    name: ARKZ
    url: https://arkz.ai
  license:
    name: Proprietary
servers:
  - url: https://arkz.ai/api/human
    description: Production (branded)
  - url: https://ukajvdczurtjoktmrbfw.supabase.co/functions/v1/ark-api
    description: Production (direct edge function)

security:
  - ApiKeyAuth: []
  - BearerAuth: []

paths:
  /:
    post:
      operationId: invokeArk
      summary: Invoke an ARK action
      description: |
        Dispatches on the `action` discriminator. Valid actions: `health`,
        `get_ark_profile`, `search_ark_topics`, `ask_ark`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/HealthRequest'
                - $ref: '#/components/schemas/GetArkProfileRequest'
                - $ref: '#/components/schemas/SearchArkTopicsRequest'
                - $ref: '#/components/schemas/AskArkRequest'
              discriminator:
                propertyName: action
            examples:
              ask_ark:
                summary: Ask the ARK a question
                value: { action: ask_ark, content: "In one sentence, what do you help with?" }
              get_ark_profile:
                summary: Read the public profile
                value: { action: get_ark_profile }
              search_ark_topics:
                summary: Discover topic coverage
                value: { action: search_ark_topics, query: scaling }
      responses:
        '200':
          description: Success. The body shape depends on the action.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/HealthResponse'
                  - $ref: '#/components/schemas/ArkProfileResponse'
                  - $ref: '#/components/schemas/ArkTopicsResponse'
                  - $ref: '#/components/schemas/AskArkResponse'
        '400':
          description: Missing/invalid parameters or unknown action.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '401':
          description: Missing, malformed, unknown, revoked, or expired credential.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '403':
          description: The credential lacks the scope required for this action.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '429':
          description: Per-minute, per-day, or per-month rate limit exceeded.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '502':
          description: Generation upstream failure (ask_ark only).
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

  # The Human API over x402: no credential; each question is paid in USDC. These paths are on www.arkz.ai. A request to
  # /ask/{derivativeId} that is not a JSON POST reaches the Ask page, not the door.
  /ask/{derivativeId}:
    servers:
      - url: https://www.arkz.ai
        description: The x402 door
    post:
      operationId: askOverX402
      summary: Ask a derivative one question and pay for it in USDC over x402
      description: |
        No credential. Without `X-PAYMENT` the door answers 402 with the payment
        requirement. The retry carries `X-PAYMENT`: base64 JSON holding an EIP-3009
        `transferWithAuthorization` signed by the paying wallet, produced by an x402
        client library from the 402 body. One settled payment buys one answer; a
        payment already used is refused. Send `Content-Type: application/json`.
        After a 503 `answer_unavailable` that carries `credit_ref`, the credit retry
        sends the same question with that `credit_ref` in the body and no
        `X-PAYMENT`; the 200 receipt then carries `credit_retry: true`, no
        `X-PAYMENT-RESPONSE` is sent and nothing is paid twice. A credit is used
        once; the third failed answer ends it and queues the refund.
      security: []
      parameters:
        - name: derivativeId
          in: path
          required: true
          schema: { type: string, format: uuid }
        - name: X-PAYMENT
          in: header
          required: false
          description: Base64 JSON of the signed payment. Absent on the first request and on a credit retry.
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/X402Question' }
      responses:
        '200':
          description: The answer and its receipt.
          headers:
            X-PAYMENT-RESPONSE:
              description: Base64 JSON of the settlement (success, transaction, network, payer). Absent on a credit retry.
              schema: { type: string }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/X402Answer' }
        '400':
          description: A refused payment (the 402 body with the refusal code in error), or a body the door cannot take (error only, before any payment).
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/X402PaymentRefused'
                  - $ref: '#/components/schemas/X402RequestRefused'
        '402':
          description: "Payment required (payment_required); no_credit (a credit_ref that is wrong, used, past its window or for another question); a payment the facilitator failed, its reason in error; or payer_mismatch (the facilitator named another payer)."
          content:
            application/json:
              schema: { $ref: '#/components/schemas/X402PaymentRequired' }
        '404':
          description: not_available (the lane sells nothing right now; code, when present, names why), not_found (no such derivative or person) or lane_off (the lane is off).
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '413':
          description: request_too_large. The body is over 16,384 bytes.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '415':
          description: json_required. A body sent without Content-Type application/json.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '429':
          description: too_many_requests. Too many requests from one address in a short time.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '502':
          description: facilitator_unavailable. The payment facilitator did not answer.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '503':
          description: "answer_unavailable: the payment settled but the answer failed (the credit with its credit_ref, the credit ended with its refund queued, or credit_unrecorded), with X-PAYMENT-RESPONSE when a payment settled in this request. Or, error only: origin_waiting (the door's public address is not set yet: it reads nothing and takes no payment), terms_unavailable, facilitator_waiting, payment_record_unavailable or credit_unavailable (the credit could not be checked; no payment is taken)."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/X402AnswerUnavailable'
                  - $ref: '#/components/schemas/Error'
  /ask/{derivativeId}/human:
    servers:
      - url: https://www.arkz.ai
        description: The x402 door
    post:
      operationId: askThePersonOverX402
      summary: Put a question to the person behind a derivative and pay for it in USDC over x402
      description: |
        The human lane. The 402 carries the person's own question price. The paid
        retry answers 202 and the question waits in the person's queue until the
        deadline the person set; poll it at poll_url, the question's full address.
      security: []
      parameters:
        - name: derivativeId
          in: path
          required: true
          schema: { type: string, format: uuid }
        - name: X-PAYMENT
          in: header
          required: false
          description: Base64 JSON of the signed payment. Absent on the first request.
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/X402HumanQuestion' }
      responses:
        '202':
          description: Paid and queued for the person.
          headers:
            X-PAYMENT-RESPONSE:
              description: Base64 JSON of the settlement (success, transaction, network, payer).
              schema: { type: string }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/HumanQuestionAccepted' }
        '400':
          description: A refused payment (the 402 body with the refusal code in error), or a body the door cannot take (error only, before any payment).
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/X402PaymentRefused'
                  - $ref: '#/components/schemas/X402RequestRefused'
        '402':
          description: "Payment required (payment_required); a payment the facilitator failed, its reason in error; or payer_mismatch (the facilitator named another payer)."
          content:
            application/json:
              schema: { $ref: '#/components/schemas/X402PaymentRequired' }
        '404':
          description: not_available (the lane sells nothing right now; code, when present, names why), not_found (no such derivative or person) or lane_off (the lane is off).
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '413':
          description: request_too_large. The body is over 16,384 bytes.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '415':
          description: json_required. A body sent without Content-Type application/json.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '429':
          description: too_many_requests. Too many requests from one address in a short time.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '502':
          description: facilitator_unavailable. The payment facilitator did not answer.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '503':
          description: "origin_waiting (the door's public address is not set yet: it reads nothing and takes no payment), terms_unavailable, facilitator_waiting or payment_record_unavailable."
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
  /ask/person/{passportId}:
    servers:
      - url: https://www.arkz.ai
        description: The x402 door
    post:
      operationId: askAPersonOverX402
      summary: Put a question to a person without a published derivative, by Passport, and pay for it in USDC over x402
      description: The human lane by Passport number. Answers as /ask/{derivativeId}/human does.
      security: []
      parameters:
        - name: passportId
          in: path
          required: true
          schema: { type: string, pattern: '^[1-9][0-9]*$' }
        - name: X-PAYMENT
          in: header
          required: false
          description: Base64 JSON of the signed payment. Absent on the first request.
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/X402HumanQuestion' }
      responses:
        '202':
          description: Paid and queued for the person.
          headers:
            X-PAYMENT-RESPONSE:
              description: Base64 JSON of the settlement (success, transaction, network, payer).
              schema: { type: string }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/HumanQuestionAccepted' }
        '400':
          description: A refused payment (the 402 body with the refusal code in error), or a body the door cannot take (error only, before any payment).
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/X402PaymentRefused'
                  - $ref: '#/components/schemas/X402RequestRefused'
        '402':
          description: "Payment required (payment_required); a payment the facilitator failed, its reason in error; or payer_mismatch (the facilitator named another payer)."
          content:
            application/json:
              schema: { $ref: '#/components/schemas/X402PaymentRequired' }
        '404':
          description: not_available (the lane sells nothing right now; code, when present, names why), not_found (no such derivative or person) or lane_off (the lane is off).
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '413':
          description: request_too_large. The body is over 16,384 bytes.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '415':
          description: json_required. A body sent without Content-Type application/json.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '429':
          description: too_many_requests. Too many requests from one address in a short time.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '502':
          description: facilitator_unavailable. The payment facilitator did not answer.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '503':
          description: "origin_waiting (the door's public address is not set yet: it reads nothing and takes no payment), terms_unavailable, facilitator_waiting or payment_record_unavailable."
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
  /ask/human/{questionId}:
    servers:
      - url: https://www.arkz.ai
        description: The x402 door
    get:
      operationId: pollHumanQuestion
      summary: Poll a paid question to the person
      description: |
        Send the settlement transaction hash from X-PAYMENT-RESPONSE as the bearer.
        A quote above the lane's ceiling (configuration) arrives as an invoice in
        the 200 body; a declined or expired question shows its refund.
      security:
        - X402Receipt: []
      parameters:
        - name: questionId
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        '200':
          description: The question's state, while it waits and after.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/HumanQuestionState' }
        '401':
          description: bearer_required (no bearer) or bearer_invalid (not a transaction hash).
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '402':
          description: quote_payment_required. The person quoted a bigger job at or under the ceiling; pay the balance at this address.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/X402QuoteRequired' }
        '404':
          description: not_found (no question for this settlement) or lane_off (the human lane is off).
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '503':
          description: origin_waiting (the door's public address is not set yet).
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
    post:
      operationId: payHumanQuote
      summary: Pay a quote at the poll address
      description: Send the X-PAYMENT header built from the poll's 402 requirement.
      security:
        - X402Receipt: []
      parameters:
        - name: questionId
          in: path
          required: true
          schema: { type: string, format: uuid }
        - name: X-PAYMENT
          in: header
          required: true
          description: Base64 JSON of the signed payment for the balance due.
          schema: { type: string }
      responses:
        '200':
          description: The quote is paid; the question's state with the receipt.
          headers:
            X-PAYMENT-RESPONSE:
              description: Base64 JSON of the settlement (success, transaction, network, payer).
              schema: { type: string }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/HumanQuestionState' }
        '400':
          description: A refused payment, with the 402 body and the refusal code in error.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/X402PaymentRefused' }
        '503':
          description: origin_waiting (the door's public address is not set yet).
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-ark-api-key
      description: An ARKZ API key, e.g. `ark_live_...`.
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: ark_live_ or arka_
      description: An ARKZ API key (`ark_live_...`) or OAuth access credential (`arka_...`).
    X402Receipt:
      type: http
      scheme: bearer
      bearerFormat: settlement transaction hash
      description: The settlement transaction hash of a paid question to the person.
  schemas:
    HealthRequest:
      type: object
      required: [action]
      properties:
        action: { const: health }
    HealthResponse:
      type: object
      properties:
        status: { type: string, example: ok }
        service: { type: string, example: ark-api }
        version: { type: string, example: 1.1.0 }
    GetArkProfileRequest:
      type: object
      required: [action]
      properties:
        action: { const: get_ark_profile }
    ArkProfileResponse:
      type: object
      properties:
        ark:
          type: object
          properties:
            id: { type: string, format: uuid }
            name: { type: string }
            tagline: { type: [string, 'null'] }
            avatar_url: { type: [string, 'null'] }
            mastery_level: { type: [string, 'null'] }
            primary_domain: { type: [string, 'null'] }
            trainable_insights: { type: integer }
            marketplace: { type: [object, 'null'] }
    SearchArkTopicsRequest:
      type: object
      required: [action]
      properties:
        action: { const: search_ark_topics }
        query:
          type: string
          description: Optional case-insensitive substring filter over topic tags.
    ArkTopicsResponse:
      type: object
      description: |
        Topic coverage over **shareable** (training-mode) insights only. Never
        exposes raw statements or private/vault content.
      properties:
        topics:
          type: array
          items:
            type: object
            properties:
              topic: { type: string }
              insight_count: { type: integer }
        total_shareable_insights: { type: integer }
    AskArkRequest:
      type: object
      required: [action, content]
      properties:
        action: { const: ask_ark }
        content:
          type: string
          maxLength: 4000
          description: "The question or prompt (alias: `question`)."
        question:
          type: string
          description: Alias for `content`.
        response_mode:
          type: string
          enum: [quick, full, deep]
          default: full
        output_type:
          type: string
          default: general
    AskArkResponse:
      type: object
      properties:
        ark_id: { type: string, format: uuid }
        answer: { type: string }
        model: { type: [string, 'null'] }
        usage:
          type: object
          properties:
            usage_id: { type: [string, 'null'], format: uuid }
            cost_cents: { type: integer }
            billing_status:
              type: string
              enum: [billed, unbilled_insufficient, metered, free]
    Error:
      type: object
      properties:
        error: { type: string }
    X402PaymentRequired:
      type: object
      description: The x402 payment requirement (x402 version 1). A refused payment answers 400 with this body, the refusal code in error.
      required: [x402Version, error, accepts]
      properties:
        x402Version: { type: integer, const: 1 }
        error: { type: string, description: "payment_required, no_credit, the facilitator's reason, payer_mismatch, or (with a 400) the refusal code." }
        accepts:
          type: array
          items: { $ref: '#/components/schemas/X402Requirement' }
    X402PaymentRefused:
      type: object
      description: A refused payment (400). The 402 body with the refusal code in error.
      required: [x402Version, error, accepts]
      properties:
        x402Version: { type: integer, const: 1 }
        error:
          type: string
          description: The door's own checks before the facilitator (the first eleven), then the data layer's (the last four). The data layer recomputes the price and reads the payment again, so it can also answer payment_malformed, amount_below_price, amount_above_ceiling or amount_not_exact, with the same fields.
          enum: [payment_malformed, version_unsupported, scheme_unsupported, network_mismatch, pay_to_mismatch, amount_below_price, amount_above_ceiling, amount_not_exact, authorization_expiring, authorization_not_yet_valid, signature_invalid, replayed, self_ask, lane_off, not_available]
        accepts:
          type: array
          items: { $ref: '#/components/schemas/X402Requirement' }
        price_cents: { type: integer, description: With amount_below_price and amount_not_exact. }
        max_amount_cents: { type: integer, description: With amount_above_ceiling. }
    X402RequestRefused:
      type: object
      description: A body the door cannot take (400), before any price or payment. callback_invalid is the human lanes' only.
      required: [error]
      properties:
        error:
          type: string
          enum: [invalid_json, question_required, question_invalid, question_too_long, callback_invalid]
    X402Requirement:
      type: object
      required: [scheme, network, maxAmountRequired, resource, payTo, maxTimeoutSeconds, asset]
      properties:
        scheme: { const: exact }
        network: { type: string, description: 'The network name, such as base.' }
        maxAmountRequired: { type: string, description: "The price in USDC base units (six decimals): the owner's listed price." }
        resource: { type: string, format: uri, description: "The full address being paid for: the door, or the poll address for a quote." }
        description: { type: string }
        mimeType: { type: string }
        outputSchema: { type: object, description: The JSON Schema of the paid answer. }
        payTo: { type: string, description: The receiving address. }
        maxTimeoutSeconds: { type: integer, description: How long the signed payment stays acceptable. }
        asset: { type: string, description: The USDC contract address. }
        extra:
          type: object
          properties:
            name: { type: string, description: "The USDC contract's EIP-712 domain name." }
            version: { type: string, description: "The USDC contract's EIP-712 domain version." }
    X402Question:
      type: object
      required: [question]
      properties:
        question: { type: string, maxLength: 4000 }
        credit_ref: { type: string, pattern: '^[0-9a-fA-F]{64}$', description: "The credit retry only: the credit_ref from the 503 answer_unavailable body, with the same question and no X-PAYMENT." }
    X402HumanQuestion:
      type: object
      required: [question]
      properties:
        question: { type: string, maxLength: 4000 }
        callback_url: { type: string, maxLength: 2000, description: "An https address on a public host name (never an IP address, a login or a local name) the door posts the question's state to." }
    X402Receipt:
      type: object
      required: [tx_hash, network, amount_cents, verify_url]
      properties:
        tx_hash: { type: [string, 'null'], pattern: '^0x[0-9a-fA-F]{64}$' }
        network: { type: string }
        amount_cents: { type: [integer, 'null'] }
        verify_url: { type: [string, 'null'], format: uri, description: "The receipt's full address: https://www.arkz.ai/verify/receipt/{txHash}. Null only on a credit retry whose original payment could not be read." }
        credit_retry: { type: boolean, description: "True on a credit retry, which takes no second payment." }
    X402Answer:
      type: object
      required: [derivative_id, answer, model, receipt]
      properties:
        derivative_id: { type: string, format: uuid }
        answer: { type: string }
        model: { type: [string, 'null'] }
        receipt: { $ref: '#/components/schemas/X402Receipt' }
    X402AnswerUnavailable:
      type: object
      description: The payment settled but the answer failed. A credit carries credit_ref and attempts; the third failure carries refund and no credit_ref; a credit that could not be recorded carries code credit_unrecorded and no credit_ref.
      required: [error, retry_window_hours, credit_expires_at]
      properties:
        error: { const: answer_unavailable }
        retry_window_hours: { type: integer, description: The credit's window in hours; 0 when no credit is held. }
        credit_expires_at: { type: [string, 'null'], format: date-time }
        attempts: { type: integer, description: The failed answers so far; the third ends the credit. }
        credit_ref: { type: string, pattern: '^[0-9a-f]{64}$', description: "The credit's reference, shown only in this answer. Send it back with the same question and no X-PAYMENT." }
        refund:
          type: object
          description: The third failure. The payment's refund to the paying address is queued.
          properties:
            queued: { type: boolean }
        code: { const: credit_unrecorded, description: The credit could not be recorded; the unused payment is refunded. }
    HumanQuestionAccepted:
      type: object
      required: [question_id, deadline_at, poll_url, receipt]
      properties:
        question_id: { type: string, format: uuid }
        deadline_at: { type: string, format: date-time }
        poll_url: { type: string, format: uri, description: "The question's full address: https://www.arkz.ai/ask/human/{questionId}." }
        receipt: { $ref: '#/components/schemas/X402Receipt' }
    HumanQuestionState:
      type: object
      required: [question_id, state]
      properties:
        question_id: { type: string, format: uuid }
        state:
          type: string
          enum: [open, answered, declined, expired, quoted, invoiced, paid_quote, released, refund_queued, refunded]
        deadline_at: { type: [string, 'null'], format: date-time }
        created_at: { type: [string, 'null'], format: date-time }
        answer: { type: [string, 'null'] }
        answered_at: { type: [string, 'null'], format: date-time }
        declined_reason: { type: [string, 'null'] }
        quote:
          oneOf:
            - $ref: '#/components/schemas/HumanQuote'
            - type: 'null'
        invoice:
          oneOf:
            - $ref: '#/components/schemas/X402Invoice'
            - type: 'null'
        refund:
          description: The fee's refund.
          oneOf:
            - $ref: '#/components/schemas/X402Refund'
            - type: 'null'
        quote_refund:
          description: A paid quote's refund.
          oneOf:
            - $ref: '#/components/schemas/X402Refund'
            - type: 'null'
        refunds:
          type: array
          description: Every refund the question holds, a duplicate quote payment's included.
          items:
            type: object
            properties:
              state: { type: string, enum: [owed, queued, sent, failed] }
              amount_cents: { type: integer }
              tx_hash: { type: [string, 'null'] }
              reason: { type: string }
        code: { type: string, description: "Why a quote offers no requirement yet, such as chain_waiting." }
        receipt: { $ref: '#/components/schemas/X402Receipt' }
    X402Refund:
      type: object
      properties:
        state: { type: string, enum: [owed, queued, sent, failed] }
        amount_cents: { type: integer }
        tx_hash: { type: [string, 'null'] }
    HumanQuote:
      type: object
      properties:
        price_cents: { type: integer, description: The balance due now in US cents. }
        total_cents: { type: integer, description: The quoted total in US cents. }
        scope: { type: string }
        quoted_at: { type: string, format: date-time }
    X402QuoteRequired:
      type: object
      required: [x402Version, error, accepts, question_id, state, deadline_at, quote]
      properties:
        x402Version: { type: integer, const: 1 }
        error: { const: quote_payment_required }
        accepts:
          type: array
          items: { $ref: '#/components/schemas/X402Requirement' }
        question_id: { type: string, format: uuid }
        state: { const: quoted }
        deadline_at: { type: string, format: date-time }
        quote: { $ref: '#/components/schemas/HumanQuote' }
    X402Invoice:
      type: object
      description: A quote above the ceiling. Transfer exactly amount_micro USDC base units to address on that chain; the exact amount is the reference.
      required: [ref, amount_cents, total_cents, amount_micro, address, chain_key, asset, scope, deadline_at]
      properties:
        ref: { type: string }
        amount_cents: { type: integer, description: The balance due in US cents. }
        total_cents: { type: integer, description: The quoted total in US cents. }
        amount_micro: { type: string, description: The exact amount in USDC base units (six decimals). }
        address: { type: string, description: The receiving address. }
        chain_key: { type: string }
        asset: { type: string, description: The USDC contract address. }
        scope: { type: string }
        deadline_at: { type: string, format: date-time }
