# Fabler Labs — machine store (x402) OpenAPI 3.1 contract
#
# This document is the formal API contract for https://x402.fablerlabs.com — the
# machine-payable side of Fabler Labs, where an autonomous AI agent pays per call
# in USDC over the x402 standard (https://www.x402.org), protocol v2. Payment IS
# the auth: there is no account and no API key. An unpaid request to a paid
# endpoint returns HTTP 402 Payment Required with an empty `{}` body — the price
# and on-chain payment details ride in a base64-encoded `PAYMENT-REQUIRED`
# response header instead. The caller decodes that header, settles the USDC, and
# replays the request with a base64 `PAYMENT-SIGNATURE` header to get the result.
#
# Source of truth for live status and prices is always /products.json. The
# machine store is in preview — endpoints roll out as they are deployed.
#
# `x-pricing` (top level) is a machine-readable price registry in USD, kept in
# lockstep with /products.json by site/test/products-json.test.mjs.
openapi: 3.1.0

info:
  title: Fabler Labs machine store (x402 API)
  version: "2026-07-10"
  summary: A machine-payable HTTP API for AI agents, priced per call in USDC over x402.
  description: >-
    Machine-payable endpoints for AI agents, over x402 protocol v2. Unpaid
    requests to a paid endpoint return `402 Payment Required` with an empty `{}`
    body and a typed challenge (price, network, asset, pay-to address) in a
    base64-encoded `PAYMENT-REQUIRED` response header; decode it, pay the USDC,
    and replay with a base64 `PAYMENT-SIGNATURE` header to get the result. Built
    and operated by an autonomous AI agent, filmed for transparency. Live status
    and prices are authoritative in https://fablerlabs.com/products.json.
  x-guidance: >-
    No account or API key is required. Call a paid operation without payment:
    HTTP 402 -> decode PAYMENT-REQUIRED -> settle the exact x402 v2 Base USDC
    terms -> replay the same request with PAYMENT-SIGNATURE.
  contact:
    name: Fabler Labs support
    email: support@fablerlabs.com
    url: https://fablerlabs.com/contact.html
  termsOfService: https://fablerlabs.com/terms.html
  license:
    name: Proprietary — usage governed by the Terms of Service
    url: https://fablerlabs.com/terms.html

servers:
  - url: https://x402.fablerlabs.com
    description: Production machine store (preview)

externalDocs:
  description: Human-readable overview of the machine store
  url: https://fablerlabs.com/x402

# Machine-readable USD price registry. Micro-endpoints are priced per successful
# call; established buy-* SKUs are at Stripe parity and x402-only items carry
# their direct autonomous price.
x-pricing:
  scan-secrets: 0.005
  scrape: 0.005
  market-funding-spreads: 0.001
  nft-owner: 0.003
  market-polymarket-activity: 0.001
  market-krw-prices: 0.001
  render-og: 0.01
  audit-agent-config: 0.05
  audit-pre-deploy: 0.08
  audit-url-security: 0.08
  buy-pack: 24
  buy-agent-kit: 29
  buy-ai-coding-security-pack-v1: 29
  buy-constitution-packs-v1: 19
  buy-pre-deploy-security-checklist: 0.10

tags:
  - name: discovery
    description: Free, unpaid endpoints for capability and price discovery.
  - name: tools
    description: Paid per-call utility endpoints for AI agents.
  - name: commerce
    description: Paid autonomous purchase of downloadable product SKUs.

paths:
  /:
    get:
      tags: [discovery]
      operationId: getServiceDirectory
      summary: Service discovery (free)
      description: >-
        Returns a machine-readable directory of the machine store — protocol,
        settlement asset, base URL, and the list of endpoints with their prices.
        Free and unauthenticated; mirrors the `api` block of /products.json.
      security: []
      responses:
        "200":
          description: The service directory.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServiceDirectory"

  /scan/secrets:
    post:
      tags: [tools]
      operationId: scanSecrets
      summary: Scan text for leaked secrets (paid)
      description: >-
        Scan a code snippet or file for leaked secrets and credentials. Returns
        structured findings (rule, masked match, line, severity, fix hint).
        Priced per successful call.
      x-402:
        priceUsd: 0.005
        network: eip155:8453
        asset: USDC
      x-payment-info:
        price:
          mode: fixed
          amount: "0.005"
          currency: USD
        protocols:
          - x402: {}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ScanSecretsRequest"
      responses:
        "200":
          description: Scan complete.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScanResult"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "400":
          $ref: "#/components/responses/BadRequest"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"

  /scrape:
    post:
      tags: [tools]
      operationId: scrapeReadableWebPagePost
      summary: Fetch a public HTTPS page as clean readable text (paid, POST-compatible)
      description: >-
        POST-compatible form for agents and directories that cannot invoke paid GET
        resources. Supply url in the JSON body, or in the query string when calling a
        concrete listed URL. The query value wins when both are present. It shares the
        GET form's public-HTTPS validation, fetch limits, output contract, and price.
      x-402:
        priceUsd: 0.005
        network: eip155:8453
        asset: USDC
      x-payment-info:
        price:
          mode: fixed
          amount: "0.005"
          currency: USD
        protocols:
          - x402: {}
      parameters:
        - in: query
          name: url
          required: false
          description: Public default-port HTTPS page to fetch; takes precedence over the JSON body.
          schema:
            type: string
            format: uri
            maxLength: 2048
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                url:
                  type: string
                  format: uri
                  maxLength: 2048
              required: [url]
              additionalProperties: false
            example:
              url: https://example.com/
      responses:
        "200":
          description: Page fetched and readable content extracted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebScrapeResult"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "413":
          description: Request or target page exceeds its bounded size limit.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "415":
          description: Target returned an explicit non-HTML content type.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "502":
          description: Target failed, redirected unsafely, returned an HTTP error, or contained no readable content.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "504":
          description: Target did not respond within the bounded fetch deadline.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    get:
      tags: [tools]
      operationId: scrapeReadableWebPage
      summary: Turn any public article or docs page into clean, LLM-ready text (paid)
      description: >-
        Fetch one public default-port HTTPS page and return bounded, readable text
        plus title, author, publish date, hostname, excerpt, word count, redirect
        chain, and final URL. Ideal for feeding articles, documentation, or blog
        posts into a RAG pipeline or summarizer. Boilerplate tags such as navigation,
        headers, footers, scripts, forms, and sidebars are removed with a structured
        HTML parser. HTML only; follows at most four validated HTTPS redirects; uses
        an 8-second deadline, a 512 KiB source cap, and a 50,000-character output cap.
        Invalid, failed, oversized, or non-HTML fetches return non-2xx and do not settle.
      x-402:
        priceUsd: 0.005
        network: eip155:8453
        asset: USDC
      x-payment-info:
        price:
          mode: fixed
          amount: "0.005"
          currency: USD
        protocols:
          - x402: {}
      parameters:
        - in: query
          name: url
          required: true
          description: Public default-port HTTPS page to fetch; credentials, fragments, IP literals, and local names are rejected.
          schema:
            type: string
            format: uri
            maxLength: 2048
          example: https://example.com/
      responses:
        "200":
          description: Page fetched and readable content extracted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebScrapeResult"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "413":
          description: Target page exceeds the 512 KiB source limit.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "415":
          description: Target returned an explicit non-HTML content type.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "502":
          description: Target failed, redirected unsafely, returned an HTTP error, or contained no readable content.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "504":
          description: Target did not respond within the bounded fetch deadline.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /market/funding-spreads:
    get:
      tags: [tools]
      operationId: getFundingSpreads
      summary: Compare current perpetual funding rates across venues (paid)
      description: >-
        Fetch current public perpetual funding data from Binance, Bybit, and
        Hyperliquid, plus OKX for single-symbol requests. Each venue's raw rate
        is normalized by its funding interval with simple annualization. Pass an
        optional base symbol such as BTC or ETH; omit it for the ten widest
        comparable cross-venue spreads. Partial source failures are disclosed in
        the response. Requires at least two venues for every returned symbol.
        Excludes fees, slippage, execution risk, caps, and compounding; market
        data only, not financial advice.
      x-402:
        priceUsd: 0.001
        network: eip155:8453
        asset: USDC
      x-payment-info:
        price:
          mode: fixed
          amount: "0.001"
          currency: USD
        protocols:
          - x402: {}
      parameters:
        - in: query
          name: symbol
          required: false
          description: Perpetual base symbol such as BTC or ETH. Omit for a top-spreads overview.
          schema:
            type: string
            pattern: "^[A-Za-z0-9]{2,15}$"
          example: BTC
      responses:
        "200":
          description: Current comparable funding-rate snapshot.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FundingSpreadSnapshot"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "404":
          description: The requested symbol was not available on at least two supported venues; the call does not settle.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "502":
          description: Fewer than two supported venues returned comparable data; the call does not settle.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      tags: [tools]
      operationId: postFundingSpreads
      summary: Compare current perpetual funding rates across venues (paid POST compatibility)
      description: >-
        POST-compatible form for agent directories and x402 buyers that cannot
        originate GET payments. Accepts the optional symbol in a JSON body or
        query string and otherwise returns the same top-spreads snapshot as GET.
        Uses the same price, data sources, normalization, and risk limitations.
      x-402:
        priceUsd: 0.001
        network: eip155:8453
        asset: USDC
      x-payment-info:
        price:
          mode: fixed
          amount: "0.001"
          currency: USD
        protocols:
          - x402: {}
      parameters:
        - in: query
          name: symbol
          required: false
          description: Perpetual base symbol such as BTC or ETH. Takes precedence over the JSON body.
          schema:
            type: string
            pattern: "^[A-Za-z0-9]{2,15}$"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                symbol:
                  type: string
                  pattern: "^[A-Za-z0-9]{2,15}$"
            example: { symbol: BTC }
      responses:
        "200":
          description: Current comparable funding-rate snapshot.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FundingSpreadSnapshot"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "404":
          description: The requested symbol was not available on at least two supported venues; the call does not settle.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "502":
          description: Fewer than two supported venues returned comparable data; the call does not settle.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /nft/owner:
    get:
      tags: [tools]
      operationId: getBaseNftOwner
      summary: Look up the current owner of a Base ERC-721 token (paid)
      description: >-
        Calls ownerOf(uint256) for one ERC-721 contract on Base using a bounded,
        read-only public JSON-RPC request. The contract must be a 20-byte hex
        address and token_id must be a canonical unsigned decimal uint256 string.
        Reverts, nonexistent tokens, non-contracts, malformed RPC responses, and
        upstream failures return non-2xx and do not settle.
      x-402:
        priceUsd: 0.003
        network: eip155:8453
        asset: USDC
      x-payment-info:
        price:
          mode: fixed
          amount: "0.003"
          currency: USD
        protocols:
          - x402: {}
      parameters:
        - in: query
          name: contract
          required: true
          description: ERC-721 contract address on Base.
          schema:
            type: string
            pattern: "^0x[0-9a-fA-F]{40}$"
          example: "0x03c4738Ee98aE44591e1A4A4F3CaB6641d95DD9a"
        - in: query
          name: token_id
          required: true
          description: Canonical unsigned decimal uint256 token ID.
          schema:
            type: string
            pattern: "^(0|[1-9][0-9]{0,77})$"
          example: "115792025850165429776940374148095744853770693818332262653016833339720940464639"
      responses:
        "200":
          description: Current token owner returned by ownerOf(uint256).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NftOwnerResult"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "404":
          description: The contract did not return an owner for this token; the call does not settle.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "502":
          description: Both bounded Base RPC attempts failed or returned malformed data; the call does not settle.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "504":
          description: Both bounded Base RPC attempts timed out; the call does not settle.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      tags: [tools]
      operationId: postBaseNftOwner
      summary: Look up the current owner of a Base ERC-721 token (paid POST compatibility)
      description: >-
        POST-compatible form for agent directories and x402 buyers that cannot
        originate GET payments. Query parameters take precedence; otherwise send
        contract and token_id as JSON strings. It uses the same price, validation,
        read-only ownerOf(uint256) call, fallback policy, and failure semantics.
      x-402:
        priceUsd: 0.003
        network: eip155:8453
        asset: USDC
      x-payment-info:
        price:
          mode: fixed
          amount: "0.003"
          currency: USD
        protocols:
          - x402: {}
      parameters:
        - in: query
          name: contract
          required: false
          schema:
            type: string
            pattern: "^0x[0-9a-fA-F]{40}$"
        - in: query
          name: token_id
          required: false
          schema:
            type: string
            pattern: "^(0|[1-9][0-9]{0,77})$"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NftOwnerRequest"
            example:
              contract: "0x03c4738Ee98aE44591e1A4A4F3CaB6641d95DD9a"
              token_id: "115792025850165429776940374148095744853770693818332262653016833339720940464639"
      responses:
        "200":
          description: Current token owner returned by ownerOf(uint256).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NftOwnerResult"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "404":
          description: The contract did not return an owner for this token; the call does not settle.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "502":
          description: Both bounded Base RPC attempts failed or returned malformed data; the call does not settle.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "504":
          description: Both bounded Base RPC attempts timed out; the call does not settle.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /market/polymarket/activity:
    get:
      tags: [tools]
      operationId: getPolymarketWalletActivity
      summary: Track a wallet's public Polymarket trades as an agent signal feed (paid)
      description: >-
        Track what any Polymarket wallet is buying and selling — point your
        agent at a smart-money or whale address and get its public trades as a
        signal feed for copy-trading or market research, with no Polymarket
        account or API key. Bounded, read-only proxy for the official
        account-free Polymarket Data API
        (data-api.polymarket.com/activity). Requires a strict 0x wallet
        address. Optional bounded limit (default 25, max 100), offset (max
        10,000), activity type, BUY/SELL side, a start/end unix-second window,
        sortBy, and sortDirection. The response is sanitized to public
        transaction and market fields only — each row carries type, side,
        size, usdc_size, price, outcome, market title and slug, transaction
        hash, and unix timestamp, while per-user profile fields (name,
        pseudonym, bio, profile images) are always dropped. Read-only; Fabler
        Labs is not affiliated with Polymarket; this performs no betting
        execution, custody, or wager recommendation. Any upstream non-2xx,
        timeout, oversized response, malformed JSON, or schema drift returns a
        non-2xx result and does not settle.
      x-402:
        priceUsd: 0.001
        network: eip155:8453
        asset: USDC
      x-payment-info:
        price:
          mode: fixed
          amount: "0.001"
          currency: USD
        protocols:
          - x402: {}
      parameters:
        - in: query
          name: user
          required: true
          description: Polymarket wallet address to read public activity for — e.g. a smart-money wallet your agent tracks as a signal source.
          schema:
            type: string
            pattern: "^0x[0-9a-fA-F]{40}$"
          example: "0x0000000000000000000000000000000000dEaD"
        - in: query
          name: limit
          required: false
          description: Maximum rows to return. Defaults to 25.
          schema:
            type: integer
            minimum: 1
            maximum: 100
        - in: query
          name: offset
          required: false
          description: Row offset for pagination. Defaults to 0.
          schema:
            type: integer
            minimum: 0
            maximum: 10000
        - in: query
          name: type
          required: false
          description: Optional activity type filter.
          schema:
            type: string
            enum: [TRADE, SPLIT, MERGE, REDEEM, REWARD, CONVERSION]
        - in: query
          name: side
          required: false
          description: Optional trade side filter.
          schema:
            type: string
            enum: [BUY, SELL]
        - in: query
          name: start
          required: false
          description: Optional unix-second lower bound; must not be after end.
          schema:
            type: integer
            minimum: 0
        - in: query
          name: end
          required: false
          description: Optional unix-second upper bound; must not be before start.
          schema:
            type: integer
            minimum: 0
        - in: query
          name: sortBy
          required: false
          schema:
            type: string
            enum: [TIMESTAMP]
        - in: query
          name: sortDirection
          required: false
          schema:
            type: string
            enum: [ASC, DESC]
      responses:
        "200":
          description: Sanitized public activity for the requested wallet.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PolymarketActivitySnapshot"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "502":
          description: >-
            The upstream Polymarket Data API returned a non-2xx status,
            malformed JSON, an oversized body, or a drifted response schema;
            the call does not settle.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "504":
          description: The upstream Polymarket Data API did not respond before the bounded timeout; the call does not settle.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      tags: [tools]
      operationId: postPolymarketWalletActivity
      summary: Read one wallet's public Polymarket activity (paid POST compatibility)
      description: >-
        POST-compatible form for agent directories and x402 buyers that cannot
        originate GET payments. Accepts the same fields as query parameters or
        JSON body values; supplying the same field in both is rejected as
        ambiguous (400). Uses the same price, upstream, sanitization, and
        fail-closed behavior as the GET form.
      x-402:
        priceUsd: 0.001
        network: eip155:8453
        asset: USDC
      x-payment-info:
        price:
          mode: fixed
          amount: "0.001"
          currency: USD
        protocols:
          - x402: {}
      parameters:
        - in: query
          name: user
          required: false
          schema:
            type: string
            pattern: "^0x[0-9a-fA-F]{40}$"
        - in: query
          name: limit
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
        - in: query
          name: offset
          required: false
          schema:
            type: integer
            minimum: 0
            maximum: 10000
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PolymarketActivityRequest"
            example:
              user: "0x0000000000000000000000000000000000dEaD"
              limit: 25
      responses:
        "200":
          description: Sanitized public activity for the requested wallet.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PolymarketActivitySnapshot"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "502":
          description: >-
            The upstream Polymarket Data API returned a non-2xx status,
            malformed JSON, an oversized body, or a drifted response schema;
            the call does not settle.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "504":
          description: The upstream Polymarket Data API did not respond before the bounded timeout; the call does not settle.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /market/krw-prices:
    get:
      tags: [tools]
      operationId: getKrwPrices
      summary: Compare a Korean-won spot price across Upbit and Bithumb (paid)
      description: >-
        Fetch a current KRW spot price for one symbol from Upbit
        (api.upbit.com) and/or Bithumb's legacy public ticker
        (api.bithumb.com), both public and account-free. Requires an
        uppercase-normalized symbol of 1-10 ASCII letters/digits; an optional
        exchange filter selects all (default), upbit, or bithumb. Each
        upstream is fetched with a bounded 4-second deadline and a 32 KiB
        response cap; malformed JSON, nonfinite or negative price/volume
        values, a stale/drifted ticker timestamp, or a schema change on a
        source causes that source to be reported as unavailable rather than
        used. With exchange=all, one healthy source is enough to succeed (the
        other is disclosed as unavailable and spread is omitted); requesting
        a single exchange that fails returns a non-2xx result and the call
        does not settle. Public market data only — not financial advice, and
        this performs no execution or investment recommendation.
      x-402:
        priceUsd: 0.001
        network: eip155:8453
        asset: USDC
      x-payment-info:
        price:
          mode: fixed
          amount: "0.001"
          currency: USD
        protocols:
          - x402: {}
      parameters:
        - in: query
          name: symbol
          required: true
          description: Market symbol, such as BTC or ETH. Normalized to uppercase.
          schema:
            type: string
            pattern: "^[A-Za-z0-9]{1,10}$"
          example: BTC
        - in: query
          name: exchange
          required: false
          description: Optional exchange filter. Defaults to all (both exchanges).
          schema:
            type: string
            enum: [all, upbit, bithumb]
      responses:
        "200":
          description: KRW price snapshot for the requested symbol and exchange(s).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KrwPriceSnapshot"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "502":
          description: >-
            Every requested exchange returned a non-2xx status, malformed
            JSON, an oversized response, a stale/drifted ticker timestamp, or
            a drifted response schema; the call does not settle.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "504":
          description: The single requested exchange did not respond before the bounded per-source timeout; the call does not settle.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      tags: [tools]
      operationId: postKrwPrices
      summary: Compare a Korean-won spot price across Upbit and Bithumb (paid POST compatibility)
      description: >-
        POST-compatible form for agent directories and x402 buyers that
        cannot originate GET payments. Accepts the same fields as query
        parameters or JSON body values; supplying the same field in both is
        rejected as ambiguous (400), and unknown JSON body keys are rejected.
        Uses the same price, upstreams, sanitization, and fail-closed
        behavior as the GET form.
      x-402:
        priceUsd: 0.001
        network: eip155:8453
        asset: USDC
      x-payment-info:
        price:
          mode: fixed
          amount: "0.001"
          currency: USD
        protocols:
          - x402: {}
      parameters:
        - in: query
          name: symbol
          required: false
          schema:
            type: string
            pattern: "^[A-Za-z0-9]{1,10}$"
        - in: query
          name: exchange
          required: false
          schema:
            type: string
            enum: [all, upbit, bithumb]
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/KrwPricesRequest"
            example:
              symbol: BTC
      responses:
        "200":
          description: KRW price snapshot for the requested symbol and exchange(s).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KrwPriceSnapshot"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "502":
          description: >-
            Every requested exchange returned a non-2xx status, malformed
            JSON, an oversized response, a stale/drifted ticker timestamp, or
            a drifted response schema; the call does not settle.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "504":
          description: The single requested exchange did not respond before the bounded per-source timeout; the call does not settle.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /render/og:
    post:
      tags: [tools]
      operationId: renderOg
      summary: Render a branded Open Graph image (paid)
      description: >-
        Render a branded Open Graph image (1200x630 PNG) from a title and optional
        subtitle. Returns the PNG bytes. Priced per successful call.
      x-402:
        priceUsd: 0.01
        network: eip155:8453
        asset: USDC
      x-payment-info:
        price:
          mode: fixed
          amount: "0.01"
          currency: USD
        protocols:
          - x402: {}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RenderOgRequest"
      responses:
        "200":
          description: >-
            The rendered card. Normally a 1200x630 PNG; if the PNG rasterizer
            fails to initialize on the server, the same card is served as SVG
            with Content-Type image/svg+xml.
          content:
            image/png:
              schema:
                type: string
                format: binary
            image/svg+xml:
              schema:
                type: string
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "400":
          $ref: "#/components/responses/BadRequest"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"

  /audit/agent-config:
    post:
      tags: [tools]
      operationId: auditAgentConfig
      summary: Audit an agent config file (paid)
      description: >-
        Audit a CLAUDE.md / AGENTS.md / agent config against current best practices.
        Returns a 0-100 score with specific, prioritized fixes. Priced per call.
      x-402:
        priceUsd: 0.05
        network: eip155:8453
        asset: USDC
      x-payment-info:
        price:
          mode: fixed
          amount: "0.05"
          currency: USD
        protocols:
          - x402: {}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AuditRequest"
      responses:
        "200":
          description: Audit complete.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuditResult"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "400":
          $ref: "#/components/responses/BadRequest"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"

  /audit/pre-deploy:
    post:
      tags: [tools]
      operationId: auditPreDeploy
      summary: Validate an 18-point pre-deploy review record (paid)
      description: >-
        Validate a partial or complete pre-deploy security review record. Reports
        missing checks, failed checks, and blank evidence; returns ready only when
        all 18 required items are accounted for. This validates evidence
        completeness, not the truth of the evidence or the security of the system.
      x-402:
        priceUsd: 0.08
        network: eip155:8453
        asset: USDC
      x-payment-info:
        price:
          mode: fixed
          amount: "0.08"
          currency: USD
        protocols:
          - x402: {}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PreDeployRequest"
      responses:
        "200":
          description: Review record evaluated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PreDeployResult"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "400":
          $ref: "#/components/responses/BadRequest"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"

  /audit/url-security:
    get:
      tags: [tools]
      operationId: auditUrlSecurityGet
      summary: Snapshot public URL security headers and redirects (paid GET compatibility)
      description: >-
        GET-compatible form for agent directories and x402 buyers that cannot
        originate GET payments. Fetch bounded response metadata for one public HTTPS
        URL. Uses HEAD, or a range GET when HEAD is unsupported; follows at most four
        validated HTTPS redirects; checks HTTP status, HSTS, CSP, X-Content-Type-Options,
        Referrer-Policy, Permissions-Policy, and cookie flags. Does not retain
        response-body content, inspect TLS certificates, or claim to be a
        vulnerability scan. Private, local, credential-bearing, non-HTTPS, and
        non-default-port targets are rejected before payment settles.
      x-402:
        priceUsd: 0.08
        network: eip155:8453
        asset: USDC
      x-payment-info:
        price:
          mode: fixed
          amount: "0.08"
          currency: USD
        protocols:
          - x402: {}
      parameters:
        - in: query
          name: url
          required: false
          description: Public HTTPS URL; defaults to https://fablerlabs.com/ when omitted.
          schema:
            type: string
            format: uri
            maxLength: 2048
      responses:
        "200":
          description: Response-metadata evidence snapshot.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UrlSecurityResult"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "400":
          $ref: "#/components/responses/BadRequest"
    post:
      tags: [tools]
      operationId: auditUrlSecurity
      summary: Snapshot public URL security headers and redirects (paid)
      description: >-
        Fetch bounded response metadata for one public HTTPS URL. Uses HEAD, or a
        range GET when HEAD is unsupported; follows at most four validated HTTPS
        redirects; checks HTTP status, HSTS, CSP, X-Content-Type-Options,
        Referrer-Policy, Permissions-Policy, and cookie flags. Does not retain
        response-body content, inspect TLS certificates, or claim to be a
        vulnerability scan. Private, local, credential-bearing, non-HTTPS, and
        non-default-port targets are rejected before payment settles.
      x-402:
        priceUsd: 0.08
        network: eip155:8453
        asset: USDC
      x-payment-info:
        price:
          mode: fixed
          amount: "0.08"
          currency: USD
        protocols:
          - x402: {}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UrlSecurityRequest"
      responses:
        "200":
          description: Response-metadata evidence snapshot.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UrlSecurityResult"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "400":
          $ref: "#/components/responses/BadRequest"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"

  # Each purchasable SKU is a concrete path (not a templated /buy/{sku}) so x402
  # indexers that enumerate openapi paths can probe and price every product
  # individually — the routing in src/index.ts registers them per-slug anyway.
  /buy/pack:
    get:
      tags: [commerce]
      operationId: buyPack
      summary: Buy the AI Coding Workflow Pack ($24, zip)
      description: >-
        Buy the AI Coding Workflow Pack with USDC, at parity with the human Stripe
        checkout. On settlement, the response body is the product .zip itself
        (Content-Disposition attachment).
      x-402:
        priceUsd: 24
        network: eip155:8453
        asset: USDC
      x-payment-info:
        price:
          mode: fixed
          amount: "24.00"
          currency: USD
        protocols:
          - x402: {}
      parameters:
        - $ref: "#/components/parameters/FilenameOverride"
      responses:
        "200":
          description: Payment settled; the product .zip bytes.
          content:
            application/zip:
              schema:
                type: string
                format: binary
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
  /buy/agent-kit:
    get:
      tags: [commerce]
      operationId: buyAgentKit
      summary: Buy the Autonomous Agent Starter Kit ($29, zip)
      description: >-
        Buy the Autonomous Agent Starter Kit with USDC, at parity with the human
        Stripe checkout. On settlement, the response body is the product .zip itself
        (Content-Disposition attachment).
      x-402:
        priceUsd: 29
        network: eip155:8453
        asset: USDC
      x-payment-info:
        price:
          mode: fixed
          amount: "29.00"
          currency: USD
        protocols:
          - x402: {}
      parameters:
        - $ref: "#/components/parameters/FilenameOverride"
      responses:
        "200":
          description: Payment settled; the product .zip bytes.
          content:
            application/zip:
              schema:
                type: string
                format: binary
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
  /buy/ai-coding-security-pack-v1:
    get:
      tags: [commerce]
      operationId: buySecurityPack
      summary: Buy the AI Coding Security Pack ($29, zip)
      description: >-
        Buy the AI Coding Security Pack with USDC, at parity with the human Stripe
        checkout. On settlement, the response body is the product .zip itself
        (Content-Disposition attachment).
      x-402:
        priceUsd: 29
        network: eip155:8453
        asset: USDC
      x-payment-info:
        price:
          mode: fixed
          amount: "29.00"
          currency: USD
        protocols:
          - x402: {}
      parameters:
        - $ref: "#/components/parameters/FilenameOverride"
      responses:
        "200":
          description: Payment settled; the product .zip bytes.
          content:
            application/zip:
              schema:
                type: string
                format: binary
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
  /buy/constitution-packs-v1:
    get:
      tags: [commerce]
      operationId: buyConstitutionPack
      summary: Buy the Agent Constitution Pack ($19, zip)
      description: >-
        Buy the Agent Constitution Pack with USDC, at parity with the human Stripe
        checkout. On settlement, the response body is the product .zip itself
        (Content-Disposition attachment).
      x-402:
        priceUsd: 19
        network: eip155:8453
        asset: USDC
      x-payment-info:
        price:
          mode: fixed
          amount: "19.00"
          currency: USD
        protocols:
          - x402: {}
      parameters:
        - $ref: "#/components/parameters/FilenameOverride"
      responses:
        "200":
          description: Payment settled; the product .zip bytes.
          content:
            application/zip:
              schema:
                type: string
                format: binary
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"
  /buy/pre-deploy-security-checklist:
    get:
      tags: [commerce]
      operationId: buyPreDeploySecurityChecklist
      summary: Buy the 18-Point Pre-Deploy Security Checklist ($0.10, zip)
      description: >-
        Buy the editable pre-deploy security checklist with USDC. On settlement,
        the response body is the product .zip itself (Content-Disposition
        attachment). This low-ticket entry product is sold directly over x402.
      x-402:
        priceUsd: 0.10
        network: eip155:8453
        asset: USDC
      x-payment-info:
        price:
          mode: fixed
          amount: "0.10"
          currency: USD
        protocols:
          - x402: {}
      parameters:
        - $ref: "#/components/parameters/FilenameOverride"
      responses:
        "200":
          description: Payment settled; the product .zip bytes.
          content:
            application/zip:
              schema:
                type: string
                format: binary
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          $ref: "#/components/responses/PaymentRequired"

components:
  parameters:
    FilenameOverride:
      name: filename
      in: query
      required: false
      description: >-
        Optional override for the downloaded file's name, sent back in the
        Content-Disposition header (the file's bytes and content are unchanged —
        this only renames the saved file). Must match `^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$`
        (no path separators, quotes, or control characters); a non-matching value
        is rejected with 400 before payment is requested. Omit it to get the
        product's default filename.
      schema:
        type: string
        pattern: "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"
        examples: ["my-pack.zip"]

  responses:
    PaymentRequired:
      description: >-
        Payment required. The response body is always empty (`{}`) — the
        accepted payment terms (price, network, asset, pay-to address) ride in a
        base64-encoded `PAYMENT-REQUIRED` response header instead (x402 protocol
        v2; see the `PaymentRequired` schema for the decoded shape). Decode it,
        pay the USDC, and replay the request with a `PAYMENT-SIGNATURE` header.
      headers:
        PAYMENT-REQUIRED:
          description: >-
            Base64-encoded JSON matching the `PaymentRequired` schema. Decode
            with e.g. `curl -si ... | grep -i '^payment-required:' | cut -d' '
            -f2 | base64 -d`.
          schema:
            type: string
            format: byte
            examples: ["eyJ4NDAyVmVyc2lvbiI6Miwi..."]
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            description: >-
              Always empty in x402 protocol v2 — the challenge is carried in the
              `PAYMENT-REQUIRED` header, not the body.
          examples:
            emptyBody:
              value: {}

    BadRequest:
      description: >-
        Malformed request body — not valid JSON, not a top-level JSON object, or a
        required field is missing or the wrong type. The body is a typed error.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          examples:
            invalidJson:
              value: { error: invalid_json, message: request body is not valid JSON }
            invalidRequest:
              value: { error: invalid_request, message: 'body must be {"text": string}' }

    PayloadTooLarge:
      description: >-
        Request body exceeds the 1 MiB (1048576-byte) limit. Rejected before
        payment verification. Normal agent requests are kilobytes; this cap sits
        well above every documented use.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"

  schemas:
    ServiceDirectory:
      type: object
      description: Machine-readable directory of the machine store.
      required: [protocol, base_url, settlement, endpoints]
      properties:
        brand:
          type: string
          examples: ["Fabler Labs"]
        protocol:
          type: string
          examples: ["x402"]
        spec:
          type: string
          format: uri
          examples: ["https://www.x402.org"]
        base_url:
          type: string
          format: uri
          examples: ["https://x402.fablerlabs.com"]
        settlement:
          type: string
          examples: ["USDC"]
        status:
          type: string
          examples: ["preview"]
        service_desc:
          type: string
          format: uri
          description: URL of this OpenAPI document.
          examples: ["https://fablerlabs.com/x402/openapi.yaml"]
        endpoints:
          type: array
          items:
            $ref: "#/components/schemas/EndpointInfo"

    EndpointInfo:
      type: object
      required: [id, path, method, price_usd]
      properties:
        id:
          type: string
        path:
          type: string
          examples: ["/scan/secrets"]
        method:
          type: string
          examples: ["POST"]
        price_usd:
          description: Price in USD; a number for fixed-price endpoints or a note for parity pricing.
          oneOf:
            - type: number
            - type: string
        description:
          type: string
        url:
          type: string
          format: uri

    PaymentRequired:
      type: object
      description: >-
        A typed x402 protocol v2 payment challenge. Never sent in the 402
        response body (always `{}`) — this is the shape you get after
        base64-decoding the `PAYMENT-REQUIRED` response header.
      required: [x402Version, resource, accepts]
      properties:
        x402Version:
          type: integer
          examples: [2]
        resource:
          type: string
          format: uri
          description: The resource being paid for.
          examples: ["https://x402.fablerlabs.com/audit/agent-config"]
        accepts:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/PaymentRequirement"
      examples:
        - summary: decoded PAYMENT-REQUIRED header for /audit/agent-config
          value:
            x402Version: 2
            resource: https://x402.fablerlabs.com/audit/agent-config
            accepts:
              - scheme: exact
                network: eip155:8453
                asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
                amount: "50000"
                payTo: "0x0000000000000000000000000000000000000000"
                maxTimeoutSeconds: 300
                extra: { name: "USD Coin", version: "2" }
        - summary: decoded PAYMENT-REQUIRED header for /scan/secrets
          value:
            x402Version: 2
            resource: https://x402.fablerlabs.com/scan/secrets
            accepts:
              - scheme: exact
                network: eip155:8453
                asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
                amount: "5000"
                payTo: "0x0000000000000000000000000000000000000000"
                maxTimeoutSeconds: 300
                extra: { name: "USD Coin", version: "2" }
        - summary: decoded PAYMENT-REQUIRED header for /render/og
          value:
            x402Version: 2
            resource: https://x402.fablerlabs.com/render/og
            accepts:
              - scheme: exact
                network: eip155:8453
                asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
                amount: "10000"
                payTo: "0x0000000000000000000000000000000000000000"
                maxTimeoutSeconds: 300
                extra: { name: "USD Coin", version: "2" }
        - summary: decoded PAYMENT-REQUIRED header for /market/funding-spreads
          value:
            x402Version: 2
            resource: https://x402.fablerlabs.com/market/funding-spreads
            accepts:
              - scheme: exact
                network: eip155:8453
                asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
                amount: "1000"
                payTo: "0x0000000000000000000000000000000000000000"
                maxTimeoutSeconds: 300
                extra: { name: "USD Coin", version: "2" }
        - summary: decoded PAYMENT-REQUIRED header for /nft/owner
          value:
            x402Version: 2
            resource: https://nft.fablerlabs.com/nft/owner
            accepts:
              - scheme: exact
                network: eip155:8453
                asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
                amount: "3000"
                payTo: "0x0000000000000000000000000000000000000000"
                maxTimeoutSeconds: 300
                extra: { name: "USD Coin", version: "2" }
        - summary: decoded PAYMENT-REQUIRED header for /market/polymarket/activity
          value:
            x402Version: 2
            resource: https://polymarket.fablerlabs.com/market/polymarket/activity
            accepts:
              - scheme: exact
                network: eip155:8453
                asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
                amount: "1000"
                payTo: "0x0000000000000000000000000000000000000000"
                maxTimeoutSeconds: 300
                extra: { name: "USD Coin", version: "2" }
        - summary: decoded PAYMENT-REQUIRED header for /market/krw-prices
          value:
            x402Version: 2
            resource: https://prices.fablerlabs.com/market/krw-prices
            accepts:
              - scheme: exact
                network: eip155:8453
                asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
                amount: "1000"
                payTo: "0x0000000000000000000000000000000000000000"
                maxTimeoutSeconds: 300
                extra: { name: "USD Coin", version: "2" }
        - summary: decoded PAYMENT-REQUIRED header for /buy/agent-kit
          value:
            x402Version: 2
            resource: https://x402.fablerlabs.com/buy/agent-kit
            accepts:
              - scheme: exact
                network: eip155:8453
                asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
                amount: "29000000"
                payTo: "0x0000000000000000000000000000000000000000"
                maxTimeoutSeconds: 300
                extra: { name: "USD Coin", version: "2" }

    PaymentRequirement:
      type: object
      description: One acceptable way to pay for the resource (x402 protocol v2).
      required: [scheme, network, asset, amount, payTo, maxTimeoutSeconds, extra]
      properties:
        scheme:
          type: string
          examples: ["exact"]
        network:
          type: string
          description: CAIP-2 chain id (not the v1 bare string `"base"`).
          examples: ["eip155:8453"]
        asset:
          type: string
          description: On-chain token contract address.
          examples: ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"]
        amount:
          type: string
          description: >-
            Amount to pay, as a decimal string of atomic units. Replaces x402
            v1's `maxAmountRequired`.
          examples: ["50000"]
        payTo:
          type: string
          description: On-chain address that must receive the payment.
          examples: ["0x0000000000000000000000000000000000000000"]
        maxTimeoutSeconds:
          type: integer
          description: How long the challenge remains valid.
          examples: [300]
        extra:
          type: object
          description: >-
            The asset's EIP-712 domain params (`name`, `version`) a signer
            needs to build the authorization typed data.
          examples: [{ name: "USD Coin", version: "2" }]

    ScanSecretsRequest:
      type: object
      description: Text to scan for leaked secrets.
      required: [text]
      properties:
        text:
          type: string
          description: The text to scan.

    ScanResult:
      type: object
      required: [findings, clean]
      properties:
        findings:
          type: array
          items:
            $ref: "#/components/schemas/ScanFinding"
        clean:
          type: boolean
          description: true when no findings were produced.

    ScanFinding:
      type: object
      required: [rule, match_masked, line, severity, fix_hint]
      properties:
        rule:
          type: string
          description: Name of the detection rule that fired.
          examples: ["aws access key id"]
        match_masked:
          type: string
          description: The matched substring, masked (first 4 chars + "***") — never the full secret.
          examples: ["AKIA***"]
        line:
          type: integer
          description: 1-indexed line number within the scanned text.
          examples: [12]
        severity:
          type: string
          enum: [low, medium, high, critical]
        fix_hint:
          type: string
          description: Short, actionable remediation hint for this rule.

    WebScrapeResult:
      type: object
      required:
        [url, final_url, status, content_type, title, author, date, hostname, excerpt, text, word_count, truncated, redirects, fetched_at]
      properties:
        url:
          type: string
          format: uri
          description: Normalized requested URL.
        final_url:
          type: string
          format: uri
          description: Final URL after validated redirects.
        status:
          type: integer
          examples: [200]
        content_type:
          type: [string, "null"]
        title:
          type: [string, "null"]
        author:
          type: [string, "null"]
        date:
          type: [string, "null"]
          description: Published date metadata as supplied by the source page.
        hostname:
          type: string
        excerpt:
          type: string
          maxLength: 500
        text:
          type: string
          maxLength: 50000
        word_count:
          type: integer
          minimum: 1
        truncated:
          type: boolean
          description: True when extracted text was capped at 50,000 characters.
        redirects:
          type: array
          maxItems: 4
          items:
            $ref: "#/components/schemas/UrlSecurityRedirect"
        fetched_at:
          type: string
          format: date-time

    FundingSpreadSnapshot:
      type: object
      required: [as_of, requested_symbol, sources, spreads, scope]
      properties:
        as_of:
          type: string
          format: date-time
        requested_symbol:
          type: [string, "null"]
          description: Uppercase requested base symbol, or null for an overview.
        sources:
          type: array
          items:
            $ref: "#/components/schemas/FundingSourceStatus"
        spreads:
          type: array
          maxItems: 10
          items:
            $ref: "#/components/schemas/FundingSpread"
        scope:
          type: string

    NftOwnerRequest:
      type: object
      additionalProperties: false
      required: [contract, token_id]
      properties:
        contract:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
        token_id:
          type: string
          pattern: "^(0|[1-9][0-9]{0,77})$"

    NftOwnerResult:
      type: object
      required: [chain, contract, token_id, owner, block_tag, upstream, as_of]
      properties:
        chain:
          type: object
          required: [name, chain_id, caip2]
          properties:
            name:
              type: string
              const: Base
            chain_id:
              type: integer
              const: 8453
            caip2:
              type: string
              const: eip155:8453
        contract:
          type: string
          pattern: "^0x[0-9a-f]{40}$"
        token_id:
          type: string
          pattern: "^(0|[1-9][0-9]*)$"
        owner:
          type: string
          pattern: "^0x[0-9a-f]{40}$"
        block_tag:
          type: string
          const: latest
        upstream:
          type: string
          enum: ["https://mainnet.base.org", "https://base-rpc.publicnode.com"]
          description: Successful public Base JSON-RPC endpoint; no secret provider data.
        as_of:
          type: string
          format: date-time

    PolymarketActivityRequest:
      type: object
      additionalProperties: false
      required: [user]
      properties:
        user:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
        limit:
          type: integer
          minimum: 1
          maximum: 100
        offset:
          type: integer
          minimum: 0
          maximum: 10000
        type:
          type: string
          enum: [TRADE, SPLIT, MERGE, REDEEM, REWARD, CONVERSION]
        side:
          type: string
          enum: [BUY, SELL]
        start:
          type: integer
          minimum: 0
        end:
          type: integer
          minimum: 0
        sortBy:
          type: string
          enum: [TIMESTAMP]
        sortDirection:
          type: string
          enum: [ASC, DESC]

    PolymarketActivitySnapshot:
      type: object
      required: [source, user, count, activity, fetched_at, scope]
      properties:
        source:
          type: string
          format: uri
          const: "https://data-api.polymarket.com/activity"
        user:
          type: string
          pattern: "^0x[0-9a-fA-F]{40}$"
        count:
          type: integer
          minimum: 0
        activity:
          type: array
          items:
            $ref: "#/components/schemas/PolymarketActivityItem"
        fetched_at:
          type: string
          format: date-time
        scope:
          type: string

    PolymarketActivityItem:
      type: object
      description: >-
        Public transaction and market fields only. Upstream per-user profile
        fields (name, pseudonym, bio, profile images) are never included.
      required:
        [proxy_wallet, timestamp, condition_id, type, size, usdc_size, transaction_hash, price, asset, side, outcome, outcome_index, title, slug, event_slug, icon]
      properties:
        proxy_wallet:
          type: string
        timestamp:
          type: integer
        condition_id:
          type: string
        type:
          type: string
        size:
          type: [number, "null"]
        usdc_size:
          type: [number, "null"]
        transaction_hash:
          type: string
        price:
          type: [number, "null"]
        asset:
          type: [string, "null"]
        side:
          type: [string, "null"]
          enum: [BUY, SELL, null]
        outcome:
          type: [string, "null"]
        outcome_index:
          type: [integer, "null"]
        title:
          type: [string, "null"]
        slug:
          type: [string, "null"]
        event_slug:
          type: [string, "null"]
        icon:
          type: [string, "null"]

    FundingSourceStatus:
      type: object
      required: [venue, status, markets]
      properties:
        venue:
          type: string
          enum: [binance, bybit, hyperliquid, okx]
        status:
          type: string
          enum: [ok, error, not_requested]
        markets:
          type: integer
          minimum: 0
        error:
          type: string
          description: Bounded source error category; upstream response bodies are never exposed.
        note:
          type: string

    FundingSpread:
      type: object
      required: [symbol, gross_annualized_spread_pct, lowest_rate_venue, highest_rate_venue, venues]
      properties:
        symbol:
          type: string
        gross_annualized_spread_pct:
          type: number
          description: Difference in simple annualized percentage points between the highest and lowest venue rates.
        lowest_rate_venue:
          type: string
          enum: [binance, bybit, hyperliquid, okx]
        highest_rate_venue:
          type: string
          enum: [binance, bybit, hyperliquid, okx]
        venues:
          type: array
          minItems: 2
          maxItems: 4
          items:
            $ref: "#/components/schemas/VenueFunding"

    VenueFunding:
      type: object
      required:
        [venue, symbol, funding_rate, funding_rate_pct, funding_interval_hours, annualized_rate_pct, mark_price_usd, next_funding_at]
      properties:
        venue:
          type: string
          enum: [binance, bybit, hyperliquid, okx]
        symbol:
          type: string
        funding_rate:
          type: number
          description: Venue-reported funding rate as a decimal for one funding interval.
        funding_rate_pct:
          type: number
          description: Venue-reported funding rate expressed as a percentage for one funding interval.
        funding_interval_hours:
          type: number
          minimum: 0
          maximum: 24
        annualized_rate_pct:
          type: number
          description: Simple annualization of the current interval rate; not compounded.
        mark_price_usd:
          type: [number, "null"]
        next_funding_at:
          type: [string, "null"]
          format: date-time

    KrwPricesRequest:
      type: object
      additionalProperties: false
      required: [symbol]
      properties:
        symbol:
          type: string
          pattern: "^[A-Za-z0-9]{1,10}$"
        exchange:
          type: string
          enum: [all, upbit, bithumb]

    KrwPriceSnapshot:
      type: object
      required: [symbol, quote, requested_exchange, sources, exchanges, spread, fetched_at, scope]
      properties:
        symbol:
          type: string
          description: Uppercase-normalized requested symbol.
        quote:
          type: string
          const: KRW
        requested_exchange:
          type: string
          enum: [all, upbit, bithumb]
        sources:
          type: array
          items:
            $ref: "#/components/schemas/KrwSourceStatus"
        exchanges:
          type: object
          description: Keyed by exchange (upbit, bithumb); only present for exchanges that returned a fresh, well-formed quote.
          properties:
            upbit:
              $ref: "#/components/schemas/KrwExchangeQuote"
            bithumb:
              $ref: "#/components/schemas/KrwExchangeQuote"
        spread:
          oneOf:
            - $ref: "#/components/schemas/KrwPriceSpread"
            - type: "null"
          description: Present only when both upbit and bithumb returned a quote; null otherwise.
        fetched_at:
          type: string
          format: date-time
        scope:
          type: string

    KrwSourceStatus:
      type: object
      required: [exchange, status]
      properties:
        exchange:
          type: string
          enum: [upbit, bithumb]
        status:
          type: string
          enum: [ok, error, not_requested]
        error:
          type: string
          description: Bounded source error category; upstream response bodies are never exposed.

    KrwExchangeQuote:
      type: object
      required: [price_krw, volume_24h_token, notional_24h_krw, change_rate, change_basis, upstream_as_of]
      properties:
        price_krw:
          type: number
          exclusiveMinimum: 0
        volume_24h_token:
          type: number
          minimum: 0
        notional_24h_krw:
          type: number
          minimum: 0
        change_rate:
          type: number
          description: Exchange-reported change as a decimal fraction (e.g. 0.0123 for +1.23%). Interpret using change_basis.
        change_basis:
          type: string
          enum: [previous_close, rolling_24h]
          description: Upbit reports change versus the previous daily close; Bithumb reports rolling 24-hour change.
        upstream_as_of:
          type: string
          format: date-time

    KrwPriceSpread:
      type: object
      required: [absolute_krw, midpoint_pct, lower_exchange, higher_exchange]
      properties:
        absolute_krw:
          type: number
          minimum: 0
        midpoint_pct:
          type: number
          minimum: 0
          description: Absolute spread as a percentage of the two exchanges' midpoint price.
        lower_exchange:
          type: string
          enum: [upbit, bithumb]
        higher_exchange:
          type: string
          enum: [upbit, bithumb]

    PreDeployRequest:
      type: object
      additionalProperties: false
      required: [results]
      properties:
        results:
          type: array
          minItems: 1
          maxItems: 18
          items:
            $ref: "#/components/schemas/PreDeployCheckInput"

    PreDeployCheckInput:
      type: object
      additionalProperties: false
      required: [id, status, evidence]
      properties:
        id:
          type: string
          enum:
            - secrets-scanned
            - env-history-clean
            - production-debug-off
            - default-credentials-changed
            - cors-origin-allowlist
            - mutating-authz
            - secure-credential-hashing
            - session-cookie-flags
            - auth-rate-limits
            - parameterized-queries
            - output-sanitization
            - upload-bounds
            - dependency-audit
            - dependency-maintenance
            - infrastructure-least-access
            - deploy-credential-scope
            - rollback-ready
            - residual-risk-owners
        status:
          type: string
          enum: [pass, fail, not-applicable]
        evidence:
          type: string
          maxLength: 500
          description: One-line evidence or not-applicable justification; blank evidence blocks readiness.

    PreDeployResult:
      type: object
      required: [verdict, ready, summary, checks, blocking, scope]
      properties:
        verdict:
          type: string
          enum: [ready, blocked]
        ready:
          type: boolean
        summary:
          $ref: "#/components/schemas/PreDeploySummary"
        checks:
          type: array
          items:
            $ref: "#/components/schemas/PreDeployCheckResult"
        blocking:
          type: array
          items:
            $ref: "#/components/schemas/PreDeployBlocker"
        scope:
          type: string
          description: Explicit boundary that this is an evidence-completeness gate, not a security guarantee.

    PreDeploySummary:
      type: object
      required: [required, submitted, passed, failed, notApplicable, missing, evidenceGaps]
      properties:
        required: { type: integer, examples: [18] }
        submitted: { type: integer }
        passed: { type: integer }
        failed: { type: integer }
        notApplicable: { type: integer }
        missing: { type: integer }
        evidenceGaps: { type: integer }

    PreDeployCheckResult:
      type: object
      required: [id, label, section, status, evidence, blockingReason]
      properties:
        id: { type: string }
        label: { type: string }
        section: { type: string }
        status:
          type: string
          enum: [pass, fail, not-applicable, missing]
        evidence: { type: string }
        blockingReason:
          oneOf:
            - type: string
              enum: [failed, missing, evidence_required]
            - type: "null"

    PreDeployBlocker:
      type: object
      required: [id, label, reason]
      properties:
        id: { type: string }
        label: { type: string }
        reason:
          type: string
          enum: [failed, missing, evidence_required]

    UrlSecurityRequest:
      type: object
      additionalProperties: false
      required: [url]
      properties:
        url:
          type: string
          format: uri
          maxLength: 2048
          description: Public HTTPS URL using the default port, with no credentials or fragment.

    UrlSecurityResult:
      type: object
      required: [verdict, reachable, requested_url, final_url, fetched_at, method, status, redirects, headers, checks, summary, scope]
      properties:
        verdict:
          type: string
          enum: [pass, warn, block]
        reachable: { type: boolean }
        requested_url: { type: string, format: uri }
        final_url:
          type: [string, "null"]
          format: uri
        fetched_at: { type: string, format: date-time }
        method:
          type: string
          enum: [HEAD, GET]
        status:
          type: [integer, "null"]
        redirects:
          type: array
          maxItems: 5
          items:
            $ref: "#/components/schemas/UrlSecurityRedirect"
        headers:
          $ref: "#/components/schemas/UrlSecurityHeaders"
        checks:
          type: array
          items:
            $ref: "#/components/schemas/UrlSecurityCheck"
        summary:
          $ref: "#/components/schemas/UrlSecuritySummary"
        scope: { type: string }

    UrlSecurityRedirect:
      type: object
      required: [status, url, location]
      properties:
        status: { type: integer }
        url: { type: string, format: uri }
        location: { type: string, format: uri }

    UrlSecurityHeaders:
      type: object
      required: [strict_transport_security, content_security_policy, x_content_type_options, referrer_policy, permissions_policy, cookie_count]
      properties:
        strict_transport_security: { type: [string, "null"] }
        content_security_policy: { type: [string, "null"] }
        x_content_type_options: { type: [string, "null"] }
        referrer_policy: { type: [string, "null"] }
        permissions_policy: { type: [string, "null"] }
        cookie_count: { type: integer, minimum: 0 }

    UrlSecurityCheck:
      type: object
      required: [id, status, evidence]
      properties:
        id: { type: string }
        status:
          type: string
          enum: [pass, warn, fail]
        evidence: { type: string }

    UrlSecuritySummary:
      type: object
      required: [passed, warnings, failed]
      properties:
        passed: { type: integer, minimum: 0 }
        warnings: { type: integer, minimum: 0 }
        failed: { type: integer, minimum: 0 }

    RenderOgRequest:
      type: object
      required: [title]
      properties:
        title:
          type: string
        subtitle:
          type: string
        theme:
          type: string
          enum: [dark, light]
          default: dark

    AuditRequest:
      type: object
      description: An agent config file to audit.
      required: [content, kind]
      properties:
        content:
          type: string
          description: Raw text of the document to audit.
        kind:
          type: string
          description: Which rubric to apply ("CLAUDE.md" and "claude-md" are equivalent).
          enum: ["CLAUDE.md", "claude-md", "constitution"]
          examples: ["CLAUDE.md"]

    AuditResult:
      type: object
      required: [score, findings, summary]
      properties:
        score:
          type: integer
          minimum: 0
          maximum: 100
        findings:
          type: array
          description: One finding per rubric rule, ordered worst-first (fail, warn, pass).
          items:
            $ref: "#/components/schemas/AuditFinding"
        summary:
          type: string
          description: One-line plain-language summary of the score and top gaps.

    AuditFinding:
      type: object
      required: [rule, label, severity, excerpt, fix, weight]
      properties:
        rule:
          type: string
          description: Stable rule id.
          examples: ["data-not-instructions"]
        label:
          type: string
          description: Human-readable rule name.
        severity:
          type: string
          enum: [pass, warn, fail]
        excerpt:
          type: string
          description: The offending line lifted from the document, or "" when none applies.
        fix:
          type: string
          description: Concrete, ready-to-apply guidance (positive confirmation on pass).
        weight:
          type: integer
          description: Max points this rule contributes to the score.

    Error:
      type: object
      required: [error]
      properties:
        error:
          type: string
        detail:
          type: string

# No API authentication is required. Payment requirements are declared per
# operation with x-payment-info and challenged at runtime over x402.
security: []
