openapi: 3.1.0
info:
  title: DiemSum Public Buyer API
  version: "0.1.0"
  summary: Agent-first quote-then-pay inference API for the DiemSum Venice x402 marketplace.
  description: |
    DiemSum resells unused Venice inference credits over x402 with ERC-7710
    delegations. This document describes the public buyer endpoints exactly as
    they are shipped in this repository. There is no API key for buyers: access
    is gated by payment.

    ## Quote-first + paid follow-up contract

    `POST /api/v1/inference` is called twice for one paid completion:

    1. First call (no payment header): the server prices the exact request,
       persists a quote shell, and returns `402 Payment Required` with the x402
       payment envelope (also base64-encoded in the `PAYMENT-REQUIRED` header).
    2. Second call (with the `PAYMENT-SIGNATURE` header carrying a base64 x402
       payload): the agent re-sends the **identical** request body, the server
       verifies the payment, runs Venice, settles, and returns `200` with the
       completion plus a receipt and a `nextTurn` lineage block.

    The request body on the paid call must match the quoted request, otherwise
    the server returns `400 quoted_request_mismatch`. Only `offerId`/`model`,
    `maxOutputTokens`, `policyPreset`, and `buyerAddress` are validated against
    the quote; the transcript itself is re-bound through the `requestHash`
    carried inside the `PAYMENT-SIGNATURE` quote id.

    ## No raw prompt retention

    Every quote pins `policy.promptRetention = "hash_only"`. DiemSum stores only
    a `sha256:` digest of the transcript (`promptHash` / `transcriptHash`) —
    never the raw prompt. This is why multi-turn follow-ups must re-send the
    full `messages[]` transcript client-side (see #15 / #37). The completion
    text of a paid request is retained server-side so the same output can be
    re-delivered when settlement is retried; public receipts expose hashes,
    ids, usage, and payment proof, not transcript text.

    ## Local testing

    Run the server with `X402_VERIFY_MODE=stub npm run dev` to stub facilitator
    verify and settle so the full paid path completes without a live facilitator.
    A runnable end-to-end agent walkthrough lives in
    `docs/examples/agent-quickstart.mjs`.
servers:
  - url: http://localhost:3000
    description: Local development server (npm run dev).
  - url: https://diemsum.com
    description: Production deployment.
# Buyer access is gated by payment, not by an API token. The empty requirement
# `{}` means "no auth required"; presenting the optional trusted bearer only
# raises the rate limit (#16).
security:
  - {}
  - trustedBearer: []
tags:
  - name: offers
    description: Public discovery of buyer-callable inference offers.
  - name: inference
    description: Quote-first paid inference (x402 / ERC-7710).
  - name: quotes
    description: Quote status retrieval.
  - name: market
    description: Public market snapshot for the Menu/Markets page.
  - name: agent-permissions
    description: |
      Agent permission bootstrap: a headless agent obtains a scoped recurring
      ERC-7715 spending mandate from its human owner, then buys autonomously
      within it.
paths:
  /api/v1/offers:
    get:
      operationId: listOffers
      tags:
        - offers
      summary: List buyer-callable offers
      description: |
        Returns the offers an agent may quote against. Only offers that are
        operator-approved (#27), seller-active, and launch-gated (#29) appear.
        Each offer carries its `launchClassification`. This endpoint is rate
        limited (#16); a bearer token from `DIEMSUM_TRUSTED_API_TOKENS` raises
        the limit.
      security:
        - {}
        - trustedBearer: []
      responses:
        "200":
          description: The current set of buyer-callable offers.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - offers
                properties:
                  offers:
                    type: array
                    items:
                      $ref: "#/components/schemas/Offer"
        "429":
          $ref: "#/components/responses/RateLimited"
  /api/v1/market:
    get:
      operationId: getMarketSnapshot
      tags:
        - market
      summary: Public market snapshot
      description: |
        Returns every buyer-callable offer joined with the Venice reference
        price for the same model, plus the Venice reference model list, so a
        client can show how each seller's resale price compares to Venice list.
        Venice reference pricing uses the deterministic public snapshot
        (`venice.source = "reference"`); live per-seller pricing stays
        server-side. This endpoint is rate limited (#16) under the same bucket
        as `/api/v1/offers`; a bearer token from `DIEMSUM_TRUSTED_API_TOKENS`
        raises the limit.
      security:
        - {}
        - trustedBearer: []
      responses:
        "200":
          description: The current market snapshot.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MarketSnapshot"
        "429":
          $ref: "#/components/responses/RateLimited"
  /api/v1/inference:
    post:
      operationId: createOrPayInference
      tags:
        - inference
      summary: Create an inference quote, or pay an existing quote
      description: |
        Without a `PAYMENT-SIGNATURE` (or legacy `X-PAYMENT`) header, this
        prices the request and returns `402` with the x402 payment envelope.

        With a `PAYMENT-SIGNATURE` header carrying a base64-encoded
        `X402PaymentPayload`, this verifies the payment, executes Venice,
        settles, and returns `200` with the completion, receipt, and `nextTurn`
        lineage. On the paid call the request body must be the same body that
        was quoted (see `quoted_request_mismatch`).

        The body accepts both snake_case and camelCase keys. Exactly one of
        `prompt` or `messages` is required. Continued-conversation lineage
        fields (#37) are optional on the first turn and carry the prior turn's
        `nextTurn` ids on a follow-up.
      security:
        - {}
        - trustedBearer: []
      parameters:
        - $ref: "#/components/parameters/PaymentSignatureHeader"
        - $ref: "#/components/parameters/LegacyPaymentHeader"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateInferenceBody"
            examples:
              firstTurnPrompt:
                summary: First turn (single prompt)
                value:
                  offerId: offer_venice-llama-3_3-70b
                  prompt: Summarize the latest ledger state.
                  maxOutputTokens: 512
              firstTurnMessages:
                summary: First turn (messages transcript, #15)
                value:
                  offerId: offer_venice-llama-3_3-70b
                  messages:
                    - role: system
                      content: You are a concise financial analyst.
                    - role: user
                      content: Summarize the latest ledger state.
                  maxOutputTokens: 512
              followUpTurn:
                summary: Follow-up turn carrying prior nextTurn lineage (#37)
                value:
                  offerId: offer_venice-llama-3_3-70b
                  messages:
                    - role: system
                      content: You are a concise financial analyst.
                    - role: user
                      content: Summarize the latest ledger state.
                    - role: assistant
                      content: "...the prior reply the agent kept client-side..."
                    - role: user
                      content: And summarize the risks.
                  maxOutputTokens: 512
                  conversationId: conv_0c2e...
                  parentInferenceRequestId: inf_8a1f...
                  parentReceiptId: rcpt_3d90...
      responses:
        "200":
          description: |
            Payment verified and settled; inference output released.
          headers:
            X-DiemSum-Quote-Id:
              $ref: "#/components/headers/XDiemSumQuoteId"
            X-DiemSum-Request-Hash:
              $ref: "#/components/headers/XDiemSumRequestHash"
            PAYMENT-REQUIRED:
              $ref: "#/components/headers/PaymentRequired"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaidInferenceResult"
        "402":
          description: |
            Payment required. The body carries the quote and the x402 payment
            envelope; the same envelope is base64-encoded in the
            `PAYMENT-REQUIRED` header.
          headers:
            X-DiemSum-Quote-Id:
              $ref: "#/components/headers/XDiemSumQuoteId"
            X-DiemSum-Request-Hash:
              $ref: "#/components/headers/XDiemSumRequestHash"
            PAYMENT-REQUIRED:
              $ref: "#/components/headers/PaymentRequired"
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - error
                  - code
                  - quote
                  - paymentRequirements
                properties:
                  error:
                    type: string
                  code:
                    type: string
                    const: payment_required
                  quote:
                    $ref: "#/components/schemas/PublicQuote"
                  paymentRequirements:
                    $ref: "#/components/schemas/X402PaymentRequiredEnvelope"
        "400":
          description: |
            Invalid request or quoted-request mismatch.
            `code` is one of: `invalid_json`, `invalid_request`,
            `quoted_request_mismatch`, `transcript_hash_mismatch`,
            `payment_requirement_mismatch`, `payer_mismatch`,
            `invalid_payment_signature`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                quotedRequestMismatch:
                  value:
                    error: This request body no longer matches the issued quote.
                    code: quoted_request_mismatch
        "404":
          description: |
            The referenced offer or quote does not exist.
            `code` is one of: `offer_not_found`, `quote_not_found`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: |
            The quote is no longer payable (already consumed/canceled).
            `code` is `quote_not_payable`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "410":
          description: |
            The quote expired before payment. `code` is `quote_expired`.
            Request a fresh quote.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
        "502":
          description: |
            Upstream Venice execution failed after payment was authorized.
            `code` is `venice_execution_failed`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: |
            The deployment is not payment-capable or the seller route is
            unavailable. `code` is one of: `facilitator_config_missing` (#13),
            `seller_route_unavailable`, `seller_route_invalid`. Also returned for
            a withheld-settlement failure (`code` mirrors the facilitator error).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                facilitatorConfigMissing:
                  value:
                    error: This deployment is not configured with an x402 facilitator and cannot accept payment.
                    code: facilitator_config_missing
        "500":
          description: |
            Unhandled error. The generic fallback `code` is
            `inference_request_failed`; internal invariant violations may use
            `invalid_quote_state` / `invalid_withheld_state`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/inference/{inferenceRequestId}:
    get:
      operationId: getInferenceRequest
      tags:
        - inference
      summary: Get the status of an inference request
      parameters:
        - name: inferenceRequestId
          in: path
          required: true
          schema:
            type: string
          example: inf_8a1f2c3d-0000-0000-0000-000000000000
      responses:
        "200":
          description: The persisted inference request and its quote projection.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - request
                  - quote
                properties:
                  request:
                    $ref: "#/components/schemas/PublicInferenceRequest"
                  quote:
                    $ref: "#/components/schemas/PublicQuote"
        "404":
          description: |
            No such inference request (or its quote is gone).
            `code` is `inference_request_not_found`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/quotes/{quoteId}:
    get:
      operationId: getQuote
      tags:
        - quotes
      summary: Get a quote and its payment envelope
      parameters:
        - name: quoteId
          in: path
          required: true
          schema:
            type: string
          example: quote_0c2e1b4a-0000-0000-0000-000000000000
      responses:
        "200":
          description: The persisted quote and its x402 payment envelope.
          headers:
            X-DiemSum-Quote-Id:
              $ref: "#/components/headers/XDiemSumQuoteId"
            X-DiemSum-Request-Hash:
              $ref: "#/components/headers/XDiemSumRequestHash"
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - quote
                  - paymentRequirements
                properties:
                  quote:
                    $ref: "#/components/schemas/PublicQuote"
                  paymentRequirements:
                    $ref: "#/components/schemas/X402PaymentRequiredEnvelope"
        "404":
          description: No such quote. `code` is `quote_not_found`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "410":
          description: |
            The quote expired. `code` is `quote_expired`. The body still carries
            the (now-expired) quote and payment envelope.
          headers:
            X-DiemSum-Quote-Id:
              $ref: "#/components/headers/XDiemSumQuoteId"
            X-DiemSum-Request-Hash:
              $ref: "#/components/headers/XDiemSumRequestHash"
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - error
                  - code
                  - quote
                  - paymentRequirements
                properties:
                  error:
                    type: string
                  code:
                    type: string
                    const: quote_expired
                  quote:
                    $ref: "#/components/schemas/PublicQuote"
                  paymentRequirements:
                    $ref: "#/components/schemas/X402PaymentRequiredEnvelope"
  /api/v1/agent-permissions/request:
    post:
      operationId: requestAgentPermission
      tags:
        - agent-permissions
      summary: Request a scoped recurring spending mandate
      description: |
        Starts the agent permission bootstrap. A headless agent asks its human
        owner for a scoped recurring ERC-7715 `erc20-token-periodic` USDC budget
        whose delegate is the agent's own address. Returns the pending request,
        an unauthenticated `approvalUrl` (the `/permit/{requestId}` page the human
        opens in MetaMask), and a `poll` block. The security boundary is that the
        human verifies the delegate address in the MetaMask prompt before signing.
        Rate limited (#16, `agent_permissions` bucket). The body accepts both
        snake_case and camelCase keys.
      security:
        - {}
        - trustedBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AgentPermissionRequestBody"
            examples:
              dailyBudget:
                summary: 100 USD/day mandate
                value:
                  agentAddress: "0x00000000000000000000000000000000000a9e37"
                  periodAmountUsd: 100
                  periodDurationSeconds: 86400
                  expirySeconds: 2592000
                  agentLabel: My research agent
                  justification: Autonomous DiemSum inference, up to 100 USDC/day.
      responses:
        "201":
          description: Pending request created.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - request
                  - approvalUrl
                  - poll
                properties:
                  request:
                    $ref: "#/components/schemas/AgentPermissionRequest"
                  approvalUrl:
                    type: string
                    description: |
                      Site origin + `/permit/{requestId}`. The human opens this in
                      MetaMask to grant the permission.
                  poll:
                    type: object
                    additionalProperties: false
                    required:
                      - url
                      - intervalSeconds
                    properties:
                      url:
                        type: string
                        description: The GET poll endpoint for this request.
                      intervalSeconds:
                        type: integer
                        const: 5
        "400":
          description: |
            Invalid request. `code` is `invalid_agent_permission_request`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          description: |
            Persistence failed. `code` is `agent_permission_store_failed`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/agent-permissions/{requestId}:
    get:
      operationId: getAgentPermission
      tags:
        - agent-permissions
      summary: Poll an agent permission request
      description: |
        Returns the request and, once granted, the grant. `grant` is null unless
        `request.status` is `granted`. A pending request past `requestExpiresAt`
        is reported with status `expired`, and the store persists that transition
        lazily. Rate limited.
      parameters:
        - $ref: "#/components/parameters/AgentPermissionRequestId"
      responses:
        "200":
          description: The request and its grant (grant null unless granted).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentPermissionEnvelope"
        "404":
          description: |
            No such request. `code` is `agent_permission_request_not_found`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
  /api/v1/agent-permissions/{requestId}/grant:
    post:
      operationId: grantAgentPermission
      tags:
        - agent-permissions
      summary: Record the human's granted permission
      description: |
        Posted by the permit page after the human grants the MetaMask permission.
        Transitions a pending request to `granted`. Validation order: 404 not
        found, 410 expired (also persisted), 409 not pending, then 400 mismatch.
        A grant mismatches (`agent_permission_grant_mismatch`) when `granted.to`
        does not equal the request `agentAddress`, the normalized chain differs,
        the permission type is not `erc20-token-periodic`, `permission.data.tokenAddress`
        differs from the request token, `permission.data.periodAmount` differs
        from the request budget, `permission.data.periodDuration` differs from
        the request period, `context` is not a 0x hex string longer than 2 chars,
        `delegationManager` is not the Base DelegationManager, or
        `delegationManager` / `granterAddress` are not valid addresses.
        The stored granter prefers `granted.from`, else the posted
        `granterAddress`, lowercased. `permissionExpiresAt` is stamped server-side
        as grant time plus the request `expirySeconds`. Rate limited.
      parameters:
        - $ref: "#/components/parameters/AgentPermissionRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AgentPermissionGrantBody"
      responses:
        "200":
          description: Granted; same body shape as the GET poll response.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentPermissionEnvelope"
        "400":
          description: |
            Grant does not match the request. `code` is
            `agent_permission_grant_mismatch`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: |
            No such request. `code` is `agent_permission_request_not_found`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: |
            The request is already granted, declined, or expired. `code` is
            `agent_permission_request_not_pending`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "410":
          description: |
            The request expired before it was answered. `code` is
            `agent_permission_request_expired`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
  /api/v1/agent-permissions/{requestId}/decline:
    post:
      operationId: declineAgentPermission
      tags:
        - agent-permissions
      summary: Decline an agent permission request
      description: |
        Transitions a pending request to `declined`. The body is empty or ignored.
        Same 404 / 410 / 409 state machine as grant. Rate limited.
      parameters:
        - $ref: "#/components/parameters/AgentPermissionRequestId"
      responses:
        "200":
          description: Declined.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - request
                properties:
                  request:
                    $ref: "#/components/schemas/AgentPermissionRequest"
        "404":
          description: |
            No such request. `code` is `agent_permission_request_not_found`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: |
            The request is no longer pending. `code` is
            `agent_permission_request_not_pending`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "410":
          description: |
            The request expired. `code` is `agent_permission_request_expired`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
components:
  securitySchemes:
    trustedBearer:
      type: http
      scheme: bearer
      description: |
        Optional. A token from `DIEMSUM_TRUSTED_API_TOKENS` raises the rate
        limit (#16). It is NOT required to call any endpoint; payment, not a
        token, gates inference.
  parameters:
    PaymentSignatureHeader:
      name: PAYMENT-SIGNATURE
      in: header
      required: false
      description: |
        Base64-encoded `X402PaymentPayload` (the decoded object is documented in
        components/schemas/X402PaymentPayload). Present on the paid retry only.
        Its `accepted.extra.quoteId` selects the quote to settle.
      content:
        # The header value is base64; decoded it is an X402PaymentPayload.
        application/json:
          schema:
            $ref: "#/components/schemas/X402PaymentPayload"
    LegacyPaymentHeader:
      name: X-PAYMENT
      in: header
      required: false
      description: |
        Legacy alias for `PAYMENT-SIGNATURE`. Same base64 `X402PaymentPayload`
        encoding. Used only if `PAYMENT-SIGNATURE` is absent.
      schema:
        type: string
    AgentPermissionRequestId:
      name: requestId
      in: path
      required: true
      description: The `aperm_`-prefixed agent permission request id.
      schema:
        type: string
      example: aperm_0c2e1b4a-0000-0000-0000-000000000000
  headers:
    XDiemSumQuoteId:
      description: The quote id for this request.
      schema:
        type: string
    XDiemSumRequestHash:
      description: The sha256 request hash bound to this quote.
      schema:
        type: string
    PaymentRequired:
      description: |
        Base64-encoded `X402PaymentRequiredEnvelope` (the same object as the
        `paymentRequirements` body field). On the paid `200` it echoes the
        envelope that was settled.
      schema:
        type: string
  responses:
    RateLimited:
      description: |
        Too many requests (#16). `code` is `rate_limited`.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: string
        X-RateLimit-Limit:
          description: Request ceiling for the current window/tier.
          schema:
            type: string
        X-RateLimit-Remaining:
          description: Remaining requests in the current window.
          schema:
            type: string
        X-RateLimit-Reset:
          description: Unix epoch seconds when the window resets.
          schema:
            type: string
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            rateLimited:
              value:
                error: Too many requests. Slow down and retry after the cooldown.
                code: rate_limited
  schemas:
    ErrorResponse:
      type: object
      additionalProperties: false
      required:
        - error
        - code
      properties:
        error:
          type: string
          description: Human-readable error message.
        code:
          type: string
          description: |
            Stable machine code. Known values across the buyer API:
            `invalid_json`, `invalid_request`, `quoted_request_mismatch`,
            `transcript_hash_mismatch`, `payment_requirement_mismatch`,
            `invalid_payment_signature`, `payer_mismatch`, `payment_required`,
            `offer_not_found`, `quote_not_found`, `inference_request_not_found`,
            `quote_expired`, `quote_not_payable`, `rate_limited`,
            `facilitator_config_missing`, `seller_route_unavailable`,
            `seller_route_invalid`, `venice_execution_failed`,
            `invalid_quote_state`, `invalid_withheld_state`,
            `inference_request_failed`, `invalid_agent_permission_request`,
            `agent_permission_store_failed`, `agent_permission_request_not_found`,
            `agent_permission_request_expired`,
            `agent_permission_request_not_pending`,
            `agent_permission_grant_mismatch`.
    AgentPermissionRequestBody:
      type: object
      description: |
        Body for POST /api/v1/agent-permissions/request. Snake_case and camelCase
        keys are both accepted; the camelCase form is shown. Only `agentAddress`
        and `periodAmountUsd` are required.
      required:
        - agentAddress
        - periodAmountUsd
      properties:
        agentAddress:
          type: string
          description: |
            EVM address of the agent delegate the human will grant to. Alias
            `agent_address`. Validated and stored lowercase.
        periodAmountUsd:
          type: number
          minimum: 0.01
          maximum: 1000
          description: |
            Recurring budget per period, in USD. Alias `period_amount_usd`. The
            max is deployment-capped by `DIEMSUM_AGENT_PERMISSION_MAX_PERIOD_USD`
            (default 1000).
        periodDurationSeconds:
          type: integer
          default: 86400
          minimum: 3600
          maximum: 7776000
          description: Budget-reset window, seconds. Alias `period_duration_seconds`.
        expirySeconds:
          type: integer
          default: 2592000
          minimum: 3600
          maximum: 31536000
          description: Requested permission lifetime, seconds. Alias `expiry_seconds`.
        justification:
          type: string
          maxLength: 280
          description: Shown to the human and embedded in the 7715 request.
        agentLabel:
          type: string
          maxLength: 80
          description: Shown on the permit page. Alias `agent_label`.
    AgentPermissionGrantBody:
      type: object
      additionalProperties: false
      description: |
        Body for POST /api/v1/agent-permissions/{requestId}/grant, posted by the
        permit page after the human grants the MetaMask permission.
      required:
        - granted
        - granterAddress
      properties:
        granted:
          type: object
          description: The wallet PermissionResponse-shaped grant.
          required:
            - context
            - delegationManager
            - chainId
            - to
            - permission
          properties:
            context:
              type: string
              description: 0x-prefixed hex delegation context (encodeDelegations).
            delegationManager:
              type: string
            chainId:
              description: Number or 0x-hex string; normalized server-side.
              oneOf:
                - type: integer
                - type: string
            to:
              type: string
              description: Delegate address; must equal the request `agentAddress`.
            from:
              type: string
              description: |
                Optional on-chain payer. Preferred as the stored granter when
                present.
            permission:
              type: object
              required:
                - type
                - data
              properties:
                type:
                  type: string
                  const: erc20-token-periodic
                data:
                  type: object
                  additionalProperties: true
                isAdjustmentAllowed:
                  type: boolean
        granterAddress:
          type: string
          description: The connected wallet address that granted.
    AgentPermissionEnvelope:
      type: object
      additionalProperties: false
      description: |
        The `{ request, grant }` body returned by the GET poll and the grant POST.
        `grant` is null unless `request.status` is `granted`.
      required:
        - request
        - grant
      properties:
        request:
          $ref: "#/components/schemas/AgentPermissionRequest"
        grant:
          oneOf:
            - $ref: "#/components/schemas/AgentPermissionGrant"
            - type: "null"
    AgentPermissionRequest:
      type: object
      additionalProperties: false
      description: |
        The public agent-permission request shape returned everywhere a request
        appears (toPublicAgentPermissionRequest). `status` reflects the lazy
        pending -> expired transition applied at read time.
      required:
        - requestId
        - status
        - agentAddress
        - chainId
        - tokenAddress
        - asset
        - periodAmountUsd
        - periodAmountBaseUnits
        - periodDurationSeconds
        - expirySeconds
        - justification
        - agentLabel
        - requestExpiresAt
        - createdAt
        - updatedAt
      properties:
        requestId:
          type: string
          example: aperm_0c2e1b4a-0000-0000-0000-000000000000
        status:
          type: string
          enum:
            - pending
            - granted
            - declined
            - expired
        agentAddress:
          type: string
          description: Lowercase agent delegate address.
        chainId:
          type: integer
          const: 8453
        tokenAddress:
          type: string
          description: Budget token address; Base USDC by default.
        asset:
          type: string
          const: USDC
        periodAmountUsd:
          type: number
        periodAmountBaseUnits:
          type: string
          description: The 6-decimal USDC atomic amount (viem parseUnits).
        periodDurationSeconds:
          type: integer
        expirySeconds:
          type: integer
        justification:
          type:
            - string
            - "null"
        agentLabel:
          type:
            - string
            - "null"
        requestExpiresAt:
          type: string
          description: |
            ISO; `createdAt` + the request TTL
            (`DIEMSUM_AGENT_PERMISSION_REQUEST_TTL_SECONDS`, default 3600).
        createdAt:
          type: string
        updatedAt:
          type: string
    AgentPermissionGrant:
      type: object
      additionalProperties: false
      description: |
        The granted permission view returned alongside a granted request
        (toAgentPermissionGrantView). Bigints inside `permissionData` are
        serialized as strings.
      required:
        - permissionContext
        - delegationManager
        - chainId
        - permissionType
        - permissionData
        - granterAddress
        - agentAddress
        - grantedAt
        - permissionExpiresAt
      properties:
        permissionContext:
          type: string
          description: 0x-prefixed hex ERC-7715 root permission context.
        delegationManager:
          type: string
        chainId:
          type: integer
          const: 8453
        permissionType:
          type: string
          const: erc20-token-periodic
        permissionData:
          type: object
          additionalProperties: true
          description: The granted permission data as stored (bigints stringified).
        granterAddress:
          type: string
          description: Lowercase payer whose funds the delegation spends.
        agentAddress:
          type: string
          description: Lowercase agent delegate; equals `request.agentAddress`.
        grantedAt:
          type: string
        permissionExpiresAt:
          type:
            - string
            - "null"
          description: |
            ISO; grant time + `expirySeconds`. Server-side metadata — the on-chain
            truth is whatever MetaMask encoded.
    Offer:
      type: object
      additionalProperties: false
      description: A buyer-callable offer as returned by GET /api/v1/offers.
      required:
        - offerId
        - sellerAddress
        - sellerDisplayName
        - model
        - pricingMode
        - billingMode
        - launchClassification
        - inputUsdPer1M
        - outputUsdPer1M
        - maxOutputTokens
        - asset
        - chainId
        - routeStatus
        - updatedAt
      properties:
        offerId:
          type: string
          example: offer_venice-llama-3_3-70b
        sellerAddress:
          type: string
          description: Seller wallet address.
          example: "0x0000000000000000000000000000000000000000"
        sellerDisplayName:
          type:
            - string
            - "null"
        model:
          type: string
          description: Venice model id (the offer's modelId).
          example: venice-llama-3_3-70b
        pricingMode:
          type: string
          enum:
            - quick-sell
            - custom
        billingMode:
          type: string
          enum:
            - exact-usage
            - fixed-quote
            - max-charge
        launchClassification:
          description: |
            Venice launch-gate classification for this route (#29). Null if the
            offer has no recorded classification.
          oneOf:
            - type: string
              enum:
                - exact-usage
                - fixed-quote
                - max-charge
                - unsupported
            - type: "null"
        inputUsdPer1M:
          type: number
          description: Quoted input price per 1,000,000 tokens, in USD.
        outputUsdPer1M:
          type: number
          description: Quoted output price per 1,000,000 tokens, in USD.
        maxOutputTokens:
          type: integer
        asset:
          type: string
          const: USDC
        chainId:
          type: integer
          const: 8453
        routeStatus:
          type: string
          enum:
            - live
            - degraded
            - offline
        updatedAt:
          type: string
          description: ISO timestamp of the last offer update.
    MarketSnapshot:
      type: object
      additionalProperties: false
      description: The body returned by GET /api/v1/market.
      required:
        - updatedAt
        - summary
        - venice
        - offers
      properties:
        updatedAt:
          type: string
          description: ISO timestamp when this snapshot was generated.
        summary:
          type: object
          additionalProperties: false
          required:
            - offerCount
            - sellerCount
            - modelCount
            - bestSavingsPct
          properties:
            offerCount:
              type: integer
            sellerCount:
              type: integer
              description: Distinct seller addresses across the offers.
            modelCount:
              type: integer
              description: Distinct Venice model ids across the offers.
            bestSavingsPct:
              type:
                - integer
                - "null"
              description: |
                The largest output-price savings (whole percent) versus the
                Venice reference across all offers; null when no offer has a
                comparable Venice reference price.
        venice:
          type: object
          additionalProperties: false
          description: The Venice reference price list used for comparison.
          required:
            - source
            - models
          properties:
            source:
              type: string
              const: reference
              description: |
                Always "reference": the deterministic public snapshot, not live
                per-seller pricing.
            models:
              type: array
              items:
                $ref: "#/components/schemas/VeniceReferenceModel"
        offers:
          type: array
          items:
            $ref: "#/components/schemas/MarketOffer"
    VeniceReferenceModel:
      type: object
      additionalProperties: false
      description: A Venice reference model and its list pricing.
      required:
        - id
        - name
        - category
        - contextWindow
        - inputUsdPer1M
        - outputUsdPer1M
        - healthStatus
      properties:
        id:
          type: string
          example: venice-llama-3_3-70b
        name:
          type: string
          example: Venice Llama 3.3 70B
        category:
          type: string
          enum:
            - chat
            - reasoning
            - code
            - image
        contextWindow:
          type: string
          description: Human-readable context window (e.g. "128k").
          example: 128k
        inputUsdPer1M:
          type: number
          description: Venice reference input price per 1,000,000 tokens, in USD.
        outputUsdPer1M:
          type: number
          description: Venice reference output price per 1,000,000 tokens, in USD.
        healthStatus:
          type: string
          enum:
            - live
            - beta
            - deprecated
            - offline
    MarketOffer:
      type: object
      additionalProperties: false
      description: |
        A buyer-callable offer enriched with the Venice reference price for the
        same model, as returned in GET /api/v1/market `offers[]`.
      required:
        - offerId
        - sellerAddress
        - sellerDisplayName
        - model
        - modelName
        - category
        - contextWindow
        - pricingMode
        - billingMode
        - launchClassification
        - inputUsdPer1M
        - outputUsdPer1M
        - veniceInputUsdPer1M
        - veniceOutputUsdPer1M
        - savingsPct
        - maxOutputTokens
        - asset
        - chainId
        - routeStatus
        - updatedAt
      properties:
        offerId:
          type: string
          example: offer_venice-llama-3_3-70b
        sellerAddress:
          type: string
          example: "0x0000000000000000000000000000000000000000"
        sellerDisplayName:
          type:
            - string
            - "null"
        model:
          type: string
          description: Venice model id (the offer's modelId).
          example: venice-llama-3_3-70b
        modelName:
          type: string
          description: |
            Human-readable model name from the Venice reference; falls back to
            the model id when no reference exists.
        category:
          description: |
            Venice model category from the reference snapshot; null when the
            offer's model has no matching reference entry.
          oneOf:
            - type: string
              enum:
                - chat
                - reasoning
                - code
                - image
            - type: "null"
        contextWindow:
          type:
            - string
            - "null"
          description: |
            Human-readable context window from the reference (e.g. "128k");
            null when no reference exists.
        pricingMode:
          type: string
          enum:
            - quick-sell
            - custom
        billingMode:
          type: string
          enum:
            - exact-usage
            - fixed-quote
            - max-charge
        launchClassification:
          description: |
            Venice launch-gate classification for this route (#29); null when
            the offer has no recorded classification.
          oneOf:
            - type: string
              enum:
                - exact-usage
                - fixed-quote
                - max-charge
                - unsupported
            - type: "null"
        inputUsdPer1M:
          type: number
          description: Seller's resale input price per 1,000,000 tokens, in USD.
        outputUsdPer1M:
          type: number
          description: Seller's resale output price per 1,000,000 tokens, in USD.
        veniceInputUsdPer1M:
          type:
            - number
            - "null"
          description: |
            Venice reference input price per 1,000,000 tokens; null when no
            reference exists.
        veniceOutputUsdPer1M:
          type:
            - number
            - "null"
          description: |
            Venice reference output price per 1,000,000 tokens; null when no
            reference exists.
        savingsPct:
          type:
            - integer
            - "null"
          description: |
            Whole-percent output-price savings versus the Venice reference
            output price. Only strictly-positive savings are reported; null when
            there is no comparable Venice reference price, or when the offer is
            not priced below it (compare `outputUsdPer1M` against
            `veniceOutputUsdPer1M` for any markup).
        maxOutputTokens:
          type: integer
        asset:
          type: string
          const: USDC
        chainId:
          type: integer
          const: 8453
        routeStatus:
          type: string
          enum:
            - live
            - degraded
            - offline
        updatedAt:
          type: string
          description: ISO timestamp of the last offer update.
    CreateInferenceBody:
      type: object
      description: |
        Request body for POST /api/v1/inference. Snake_case and camelCase keys
        are both accepted; the camelCase form is shown. Exactly one of `prompt`
        or `messages` is required.
      properties:
        offerId:
          type: string
          description: |
            Target offer id. Alias: `offer_id`. Either `offerId` or `model`
            must resolve to a buyer-callable offer.
        model:
          type: string
          description: Venice model id; an alternative to `offerId`.
        prompt:
          type: string
          description: Single-turn prompt. Mutually exclusive with `messages`.
        messages:
          type: array
          description: |
            Multi-turn transcript (#15). On a follow-up turn the agent re-sends
            the full transcript including prior assistant content, since DiemSum
            retains no raw text.
          items:
            $ref: "#/components/schemas/InferenceMessage"
        maxOutputTokens:
          type: integer
          description: |
            Max output tokens. Alias: `max_output_tokens`. Clamped to the
            offer's `maxOutputTokens`; defaults to it when omitted.
        policyPreset:
          type: string
          description: 'Quote policy preset. Alias: `policy_preset`. Defaults to "balanced".'
        buyerAddress:
          type: string
          description: |
            Optional buyer wallet address. Alias: `buyer_address`. If set, it
            must match the verified payer on the paid call.
        conversationId:
          type: string
          description: |
            Continued-conversation id (#37). Alias: `conversation_id`. Omitted
            on a first turn (the server generates one); carried from the prior
            `nextTurn.conversationId` on a follow-up.
        turnId:
          type: string
          description: |
            Alias: `turn_id`. Accepted but server-generated per turn; not bound
            into the request hash.
        parentInferenceRequestId:
          type: string
          description: |
            Prior turn's inference request id (#37). Alias:
            `parent_inference_request_id`. Hash-bound: changing it yields a new
            quote.
        parentReceiptId:
          type: string
          description: |
            Prior turn's receipt id (#37). Alias: `parent_receipt_id`.
            Hash-bound.
        transcriptHash:
          type: string
          description: |
            Optional `sha256:<64-hex>` digest of the submitted transcript.
            Alias: `transcript_hash`. If supplied it must equal the server's
            computed prompt hash, else `transcript_hash_mismatch`.
          pattern: "^sha256:[0-9a-f]{64}$"
      examples:
        - offerId: offer_venice-llama-3_3-70b
          prompt: Summarize the latest ledger state.
          maxOutputTokens: 512
    InferenceMessage:
      type: object
      additionalProperties: false
      required:
        - role
        - content
      properties:
        role:
          type: string
          enum:
            - system
            - user
            - assistant
            - tool
        content:
          type: string
        name:
          type: string
          description: Optional author name.
    QuotePolicy:
      type: object
      additionalProperties: false
      description: The policy pinned to a quote.
      required:
        - preset
        - promptRetention
        - maxOutputTokens
        - quoteTtlSeconds
        - releaseRule
      properties:
        preset:
          type: string
        promptRetention:
          type: string
          const: hash_only
          description: |
            DiemSum never stores raw prompt text; only sha256 digests. Paid
            completion text is retained server-side for settlement recovery and
            is stripped from public receipt metadata.
        maxOutputTokens:
          type: integer
        quoteTtlSeconds:
          type: integer
        releaseRule:
          type: string
          const: verify_payment_and_debit_before_output
        estimatedInputTokens:
          type: integer
        lineage:
          type: object
          additionalProperties: false
          description: Continued-conversation lineage (#37), ids/hashes only.
          required:
            - conversationId
            - turnId
            - parentInferenceRequestId
            - parentReceiptId
            - transcriptHash
          properties:
            conversationId:
              type: string
            turnId:
              type: string
            parentInferenceRequestId:
              type:
                - string
                - "null"
            parentReceiptId:
              type:
                - string
                - "null"
            transcriptHash:
              type: string
    PublicQuote:
      type: object
      additionalProperties: false
      description: The buyer-facing quote projection (toPublicQuote).
      required:
        - quoteId
        - inferenceRequestId
        - buyerAddress
        - sellerAddress
        - offerId
        - model
        - requestHash
        - promptHash
        - asset
        - chainId
        - quotedMaxUsd
        - estimatedInputTokens
        - maxOutputTokens
        - billingMode
        - status
        - paymentRequirements
        - policy
        - conversationId
        - turnId
        - parentInferenceRequestId
        - parentReceiptId
        - transcriptHash
        - expiresAt
        - createdAt
      properties:
        quoteId:
          type: string
        inferenceRequestId:
          type: string
        buyerAddress:
          type:
            - string
            - "null"
        sellerAddress:
          type: string
        offerId:
          type: string
        model:
          type: string
        requestHash:
          type: string
          description: "sha256:<64-hex> hash binding the exact quoted request."
        promptHash:
          type: string
          description: "sha256:<64-hex> hash of the transcript."
        asset:
          type: string
          const: USDC
        chainId:
          type: integer
          const: 8453
        quotedMaxUsd:
          type: number
        estimatedInputTokens:
          type: integer
        maxOutputTokens:
          type: integer
        billingMode:
          type: string
          enum:
            - exact-usage
            - fixed-quote
            - max-charge
        status:
          type: string
          enum:
            - pending
            - reserved
            - consumed
            - expired
            - canceled
        paymentRequirements:
          $ref: "#/components/schemas/X402PaymentRequiredEnvelope"
        policy:
          $ref: "#/components/schemas/QuotePolicy"
        conversationId:
          type:
            - string
            - "null"
        turnId:
          type:
            - string
            - "null"
        parentInferenceRequestId:
          type:
            - string
            - "null"
        parentReceiptId:
          type:
            - string
            - "null"
        transcriptHash:
          type: string
          description: |
            Equals the prompt hash for this turn; falls back to promptHash when
            no lineage is set.
        expiresAt:
          type: string
        createdAt:
          type: string
    PublicInferenceRequest:
      type: object
      additionalProperties: false
      description: The buyer-facing inference-request projection (toPublicInferenceRequest).
      required:
        - inferenceRequestId
        - quoteId
        - buyerAddress
        - sellerAddress
        - offerId
        - model
        - requestHash
        - responseHash
        - reservedAmountUsd
        - chargedAmountUsd
        - status
        - quoteStatus
        - paymentRequirements
        - policy
        - conversationId
        - turnId
        - parentInferenceRequestId
        - parentReceiptId
        - transcriptHash
        - expiresAt
        - startedAt
        - completedAt
        - error
        - createdAt
      properties:
        inferenceRequestId:
          type: string
        quoteId:
          type: string
        buyerAddress:
          type:
            - string
            - "null"
        sellerAddress:
          type: string
        offerId:
          type: string
        model:
          type: string
        requestHash:
          type: string
        responseHash:
          type:
            - string
            - "null"
          description: "sha256:<64-hex> hash of the completion, once executed."
        reservedAmountUsd:
          type: number
        chargedAmountUsd:
          type:
            - number
            - "null"
          description: |
            Equals the quoted max: the x402 `exact` scheme settles the full
            quoted amount on-chain. The usage-based value of the turn is
            reported separately as `usage.usageValueUsd` on the receipt.
        status:
          type: string
          enum:
            - quoted
            - reserved
            - running
            - completed
            - charged
            - failed
            - canceled
            - withheld
        quoteStatus:
          type: string
          enum:
            - pending
            - reserved
            - consumed
            - expired
            - canceled
        paymentRequirements:
          $ref: "#/components/schemas/X402PaymentRequiredEnvelope"
        policy:
          $ref: "#/components/schemas/QuotePolicy"
        conversationId:
          type:
            - string
            - "null"
        turnId:
          type:
            - string
            - "null"
        parentInferenceRequestId:
          type:
            - string
            - "null"
        parentReceiptId:
          type:
            - string
            - "null"
        transcriptHash:
          type: string
        expiresAt:
          type: string
        startedAt:
          type:
            - string
            - "null"
        completedAt:
          type:
            - string
            - "null"
        error:
          type:
            - string
            - "null"
        createdAt:
          type: string
    PublicReceipt:
      type: object
      additionalProperties: false
      description: |
        The buyer-facing receipt projection (toPublicReceipt). Exposes hashes,
        ids, usage, and payment proof — never raw transcript text.
      required:
        - receiptId
        - inferenceRequestId
        - quoteId
        - buyerAddress
        - sellerAddress
        - offerId
        - requestHash
        - responseHash
        - chargedAmountUsd
        - asset
        - chainId
        - ledgerEntryId
        - settlementJobId
        - paymentProof
        - usage
        - metadata
        - createdAt
      properties:
        receiptId:
          type: string
        inferenceRequestId:
          type: string
        quoteId:
          type: string
        buyerAddress:
          type:
            - string
            - "null"
        sellerAddress:
          type: string
        offerId:
          type: string
        requestHash:
          type: string
        responseHash:
          type:
            - string
            - "null"
        chargedAmountUsd:
          type: number
        asset:
          type: string
          const: USDC
        chainId:
          type: integer
          const: 8453
        ledgerEntryId:
          type:
            - string
            - "null"
        settlementJobId:
          type:
            - string
            - "null"
        paymentProof:
          type: object
          additionalProperties: true
          description: Facilitator settlement evidence (tx hash, settle result, etc.).
        usage:
          type: object
          additionalProperties: true
          description: Token/usage accounting for the charged turn.
        metadata:
          type: object
          additionalProperties: true
          description: |
            Receipt metadata with the raw `completionContent` stripped; carries
            lineage ids/hashes (#37) so a follow-up can be traced to its parent.
        createdAt:
          type: string
    X402PaymentRequirement:
      type: object
      additionalProperties: false
      description: A single x402 accepted-payment requirement.
      required:
        - scheme
        - network
        - amount
        - asset
        - payTo
        - maxTimeoutSeconds
        - extra
      properties:
        scheme:
          type: string
          const: exact
        network:
          type: string
          const: "eip155:8453"
        amount:
          type: string
          description: Atomic USDC amount (string integer).
        asset:
          type: string
          const: "0x833589fCD6EDb6E08f4c7C32D4f71b54bDa02913"
          description: Base USDC token address.
        payTo:
          type: string
          description: Seller payout address.
        maxTimeoutSeconds:
          type: integer
          const: 300
        extra:
          type: object
          additionalProperties: true
          required:
            - assetTransferMethod
            - facilitators
            - facilitatorEndpoint
            - chainId
            - quoteId
            - requestHash
            - offerId
            - sellerAddress
            - maxAmountUsd
            - billingMode
            - quoteExpiresAt
          properties:
            assetTransferMethod:
              type: string
              const: erc7710
            facilitators:
              type: array
              description: Facilitator (redeemer) addresses.
              items:
                type: string
            facilitatorAddresses:
              type: array
              description: |
                Preferred facilitator addresses; falls back to `facilitators`
                when empty.
              items:
                type: string
            facilitatorEndpoint:
              type: string
            facilitatorAddressSource:
              type: string
              enum:
                - supported
                - env_override
                - env_fallback
                - stub
            chainId:
              type: integer
              const: 8453
            quoteId:
              type: string
            requestHash:
              type: string
            offerId:
              type: string
            sellerAddress:
              type: string
            maxAmountUsd:
              type: number
            billingMode:
              type: string
            quoteExpiresAt:
              type: string
            redeemerCaveatMode:
              type: string
              enum:
                - required
                - disabled
    X402PaymentRequiredEnvelope:
      type: object
      additionalProperties: false
      description: |
        The x402 402-challenge envelope. Carried in the `paymentRequirements`
        body field and, base64-encoded, in the `PAYMENT-REQUIRED` header.
      required:
        - x402Version
        - resource
        - accepts
      properties:
        x402Version:
          type: integer
          const: 2
        resource:
          type: object
          additionalProperties: false
          required:
            - url
            - description
            - mimeType
            - serviceName
          properties:
            url:
              type: string
            description:
              type: string
            mimeType:
              type: string
            serviceName:
              type: string
        accepts:
          type: array
          items:
            $ref: "#/components/schemas/X402PaymentRequirement"
        error:
          type: string
        extensions:
          type: object
          additionalProperties: true
    X402PaymentPayload:
      type: object
      additionalProperties: false
      description: |
        The base64-encoded body of the `PAYMENT-SIGNATURE` (or legacy
        `X-PAYMENT`) header. `accepted` must equal the quoted requirement; its
        `extra.quoteId` selects the quote.
      required:
        - x402Version
        - accepted
        - payload
      properties:
        x402Version:
          type: integer
          const: 2
        resource:
          type: object
          additionalProperties: true
        accepted:
          $ref: "#/components/schemas/X402PaymentRequirement"
        payload:
          type: object
          additionalProperties: false
          required:
            - delegationManager
            - permissionContext
            - delegator
          properties:
            delegationManager:
              type: string
              description: ERC-7710 DelegationManager address.
            permissionContext:
              type: string
              description: ABI-encoded signed delegation chain (encodeDelegations).
            delegator:
              type: string
              description: Buyer smart-account address.
    PaidInferenceResult:
      type: object
      additionalProperties: false
      description: The 200 body returned after a verified, settled paid call.
      required:
        - quote
        - inferenceRequest
        - receipt
        - settlementJob
        - ledger
        - completion
        - nextTurn
      properties:
        quote:
          $ref: "#/components/schemas/PublicQuote"
        inferenceRequest:
          $ref: "#/components/schemas/PublicInferenceRequest"
        receipt:
          $ref: "#/components/schemas/PublicReceipt"
        settlementJob:
          type: object
          additionalProperties: true
          description: |
            The stored settlement job (status, txHash, attempts, facilitator,
            metadata, timestamps). Serialized as stored.
        ledger:
          type: object
          additionalProperties: false
          required:
            - reserveEntryId
            - buyerDebitEntryId
            - sellerCreditEntryId
            - buyerReleaseEntryId
          properties:
            reserveEntryId:
              type:
                - string
                - "null"
            buyerDebitEntryId:
              type:
                - string
                - "null"
            sellerCreditEntryId:
              type:
                - string
                - "null"
            buyerReleaseEntryId:
              type:
                - string
                - "null"
        completion:
          type: object
          additionalProperties: false
          required:
            - model
            - content
            - finishReason
            - usage
          properties:
            model:
              type: string
            content:
              type: string
              description: The released inference output.
            finishReason:
              type:
                - string
                - "null"
            usage:
              type: object
              additionalProperties: false
              required:
                - promptTokens
                - completionTokens
                - totalTokens
              properties:
                promptTokens:
                  type: integer
                completionTokens:
                  type: integer
                totalTokens:
                  type: integer
        nextTurn:
          type: object
          additionalProperties: false
          description: |
            Lineage an agent carries into the next paid follow-up turn (#37).
          required:
            - conversationId
            - parentInferenceRequestId
            - parentReceiptId
            - priorTranscriptHash
          properties:
            conversationId:
              type:
                - string
                - "null"
            parentInferenceRequestId:
              type: string
            parentReceiptId:
              type: string
            priorTranscriptHash:
              type: string
