openapi: 3.1.0
info:
  title: Marketplace Service Health API
  version: "0.2.0"
  description: >
    A paid x402 endpoint that reports the liveness of services listed in the
    Circle Agent Marketplace (https://agents.circle.com/services). Built for AI
    agents that want to confirm a provider is active *before* spending USDC to
    use it. An agent can ask about one specific provider or a batch (e.g. all
    email-like services) and receives a comparative liveness report with a
    summary it can act on.

    Payment uses the x402 open standard (https://x402.org/) over HTTP 402.
    Health probes themselves are unpaid — a service's own `402 Payment Required`
    is its healthy signal — so no USDC is spent on health checks. The paid call
    buys the decision-support report. Settlement is gasless and batched via
    Circle Gateway nanopayments (https://developers.circle.com/gateway/nanopayments).
  contact:
    name: Marketplace Health API
    email: support@fomolabs.space
  x-guidance: |
    WHAT THIS API DOES
    Returns a liveness report for services listed in the Circle Agent Marketplace.
    Call this BEFORE you pay to use a marketplace service, to avoid spending USDC
    on a service that is down.

    INPUTS (query params)
    - query:     fuzzy search across resource URLs, providers, descriptions,
                 tags. Use domain terms like "email", "weather", "search".
    - category:  one of SOCIAL_INTELLIGENCE, FINANCIAL_ANALYSIS, WEB_SEARCH_RESEARCH,
                 PREDICTION_MARKETS, CREATIVE, INFRASTRUCTURE.
    - network:   CAIP-2 blockchain id, e.g. "eip155:8453" (Base).
    - payTo:     a specific seller wallet address (filter to one provider).
    - resource:  a specific service endpoint URL (matched via Discovery fuzzy search).
    - limit:     max services to probe per call, capped at 20 (default 10).

    OUTPUT (JSON)
    - checkedAt: ISO 8601 timestamp of the check.
    - filter:    the filter that was resolved.
    - results[]: per-service object: resource, provider, category, network, payTo,
                 priceUsd, discovery="listed", probe { status, httpStatus, x402,
                 latencyMs, error }, verdict ∈ {active, degraded, inactive}.
    - summary:   counts of active, degraded, inactive, total.

    WHEN TO CALL
    Before paying for any marketplace service, call this endpoint with a filter
    that matches your target service. If its verdict is "active", the provider is
    up and correctly enforcing x402 payment. If "inactive" or "degraded", skip it
    or find a replacement. The paid call buys the report; the individual probes
    are free.

    PAYMENT
    Send no payment header on the first call to receive a 402 with a price list
    in the PAYMENT-REQUIRED header (base64 JSON, x402 `accepts` array — one entry
    per accepted network). Sign a payment with your agent wallet, then retry the
    same request with a base64 PAYMENT-SIGNATURE header. On success you get the
    report plus a PAYMENT-RESPONSE header confirming settlement.
externalDocs:
  url: https://developers.circle.com/gateway/nanopayments/concepts/x402
  description: Circle Gateway nanopayments — x402 concepts, supported networks,
    and the seller/buyer flow.
servers:
  - url: https://circlehealth.fomolabs.space
    description: Production
security: []
tags:
  - name: Health
    description: Marketplace service liveness reports.
paths:
  /api/health:
    get:
      tags: [Health]
      summary: Get liveness report for marketplace services
      description: >
        Returns a liveness report for Circle Agent Marketplace services matching
        the given filter. Unpaid requests get `402 Payment Required` with a
        machine-readable price list in the `PAYMENT-REQUIRED` header (base64 JSON,
        x402 `accepts` array, one entry per accepted network). Pay per the x402
        protocol (https://x402.org/) and retry with a `PAYMENT-SIGNATURE` header
        to receive the report and a `PAYMENT-RESPONSE` header.
      operationId: getHealth
      x-payment-info:
        price:
          mode: fixed
          currency: USDC
          amount: "0.010000"
        protocols:
          - x402: {}
        # Multi-chain: an agent funded on any of these Gateway-supported networks
        # can pay. Accepting more than one raises fill rate — an agent funded on
        # Base cannot pay an endpoint that only accepts Polygon. We accept all 12
        # Gateway mainnet networks (lib/payment.ts passes no `networks` option to
        # createGatewayMiddleware, which defaults to ALL supported networks).
        networks:
          - eip155:8453    # Base
          - eip155:1       # Ethereum
          - eip155:137     # Polygon
          - eip155:42161   # Arbitrum
          - eip155:10      # Optimism
          - eip155:43114   # Avalanche
          - eip155:999     # HyperEVM
          - eip155:1329    # Sei
          - eip155:146     # Sonic
          - eip155:130     # Unichain
          - eip155:480     # World Chain
          - eip155:5042    # Arc
      parameters:
        - name: query
          in: query
          description: >
            Fuzzy search across resource URLs, providers, descriptions, and tags.
            Use this for domain terms like `email`, `weather`, `search`.
          required: false
          schema: { type: string, example: email }
        - name: category
          in: query
          description: Discovery category.
          required: false
          schema:
            type: string
            enum:
              - SOCIAL_INTELLIGENCE
              - FINANCIAL_ANALYSIS
              - WEB_SEARCH_RESEARCH
              - PREDICTION_MARKETS
              - CREATIVE
              - INFRASTRUCTURE
        - name: network
          in: query
          description: CAIP-2 blockchain id (e.g. `eip155:8453` for Base).
          required: false
          schema: { type: string, example: eip155:8453 }
        - name: payTo
          in: query
          description: A specific seller wallet address to check one provider.
          required: false
          schema: { type: string }
        - name: resource
          in: query
          description: A specific service endpoint URL (matched via Discovery fuzzy search).
          required: false
          schema: { type: string }
        - name: limit
          in: query
          description: Max services to probe per call. Capped at 20.
          required: false
          schema: { type: integer, minimum: 1, maximum: 20, default: 10 }
        - name: PAYMENT-SIGNATURE
          in: header
          description: >
            Base64-encoded x402 payment signature, produced by signing one of the
            `accepts[]` entries from a prior `402` response's `PAYMENT-REQUIRED`
            header. Omit on the first call to receive the price list.
          required: false
          schema: { type: string }
      responses:
        "200":
          description: Liveness report.
          headers:
            PAYMENT-RESPONSE:
              description: Base64 JSON payment confirmation (transaction, network, payer).
              schema: { type: string }
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthReport"
              example:
                checkedAt: "2026-08-25T14:02:11Z"
                filter:
                  query: email
                  limit: 10
                results:
                  - resource: "https://mailagent.example/api/send"
                    provider: "MailAgent"
                    category: "INFRASTRUCTURE"
                    network: "eip155:8453"
                    payTo: "0xAbC1230000000000000000000000000000dEaD"
                    priceUsd: "0.02"
                    discovery: "listed"
                    probe:
                      status: "up"
                      httpStatus: 402
                      x402: "valid"
                      latencyMs: 184
                    verdict: "active"
                summary:
                  active: 1
                  degraded: 0
                  inactive: 0
                  total: 1
        "402":
          description: >
            Two distinct cases share this status code — check for the
            `PAYMENT-REQUIRED` header to tell them apart:

            1. No `PAYMENT-SIGNATURE` was sent. The `PAYMENT-REQUIRED` header
               carries a base64 JSON x402 price list — the `accepts` array
               with ONE ENTRY PER ACCEPTED NETWORK (Base, Ethereum, Polygon,
               Arbitrum, Optimism, Avalanche, HyperEVM, Sei, Sonic, Unichain,
               World Chain, Arc). Pay on whichever network your agent wallet
               is funded on. The body is empty JSON `{}`.
            2. A `PAYMENT-SIGNATURE` was sent but verification or settlement
               was rejected (invalid/expired signature, insufficient funds,
               unsupported network, facilitator error, etc). No
               `PAYMENT-REQUIRED` header is sent in this case. The body
               instead carries `{ error, reason, message? }` describing the
               failure — `reason` and `message` are passed through from the
               payment facilitator and are not a fixed enum. Sign a fresh
               payment and retry; do not resend the same signature.
          headers:
            PAYMENT-REQUIRED:
              description: >
                Base64 JSON price list, present ONLY on case 1 above (absent
                when this 402 is reporting a rejected payment attempt).
                Decoded shape:
                `{ x402Version: 2, resource: { url, description, mimeType },
                accepts: [{ scheme, network, asset, amount, payTo,
                maxTimeoutSeconds, extra: { name, version, verifyingContract } }] }`.
                `network` is CAIP-2 (e.g. `eip155:8453` for Base); `amount` is USDC
                atomic units (6 decimals, so 10000 = 0.01 USDC); `asset` is the USDC
                contract address on that network. Pick the entry whose `network`
                matches your agent wallet's chain and pay it.
              schema: { type: string }
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    additionalProperties: false
                    description: Case 1 — no payment attempted yet.
                  - type: object
                    description: Case 2 — a payment attempt was rejected.
                    properties:
                      error: { type: string }
                      reason: { type: string }
                      message: { type: string }
                    required: [error]
              examples:
                paymentRequired:
                  summary: No payment attempted (price list is in the header)
                  value: {}
                paymentRejected:
                  summary: Payment verification failed
                  value:
                    error: "Payment verification failed"
                    reason: "invalid_signature"
        "400":
          description: >
            Bad payment. The `PAYMENT-SIGNATURE` was missing the accepted
            requirements or named an unsupported network.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "500":
          description: Server misconfigured (SELLER_ADDRESS not set) or internal error.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "502":
          description: Discovery upstream failure.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
components:
  schemas:
    Error:
      type: object
      properties:
        error: { type: string }
        detail: { type: string }
      example:
        error: "discovery_unavailable"
        detail: "Circle Discovery returned a non-200 response."
    HealthReport:
      type: object
      properties:
        checkedAt: { type: string, format: date-time }
        filter: { $ref: "#/components/schemas/Filter" }
        results:
          type: array
          items: { $ref: "#/components/schemas/HealthResult" }
        summary: { $ref: "#/components/schemas/Summary" }
    Filter:
      type: object
      properties:
        query: { type: string }
        category: { type: string }
        network: { type: string }
        payTo: { type: string }
        resource: { type: string }
        limit: { type: integer }
    HealthResult:
      type: object
      properties:
        resource: { type: string }
        provider: { type: string }
        category: { type: string }
        network: { type: string }
        payTo: { type: string }
        priceUsd: { type: string }
        discovery: { type: string, example: listed }
        probe: { $ref: "#/components/schemas/ProbeResult" }
        verdict:
          type: string
          enum: [active, degraded, inactive]
    ProbeResult:
      type: object
      properties:
        status:
          type: string
          enum: [up, degraded, inactive, timeout, error, unknown]
        httpStatus: { type: integer }
        x402:
          type: string
          enum: [valid, invalid, none]
        latencyMs: { type: integer }
        error: { type: string }
    Summary:
      type: object
      properties:
        active: { type: integer }
        degraded: { type: integer }
        inactive: { type: integer }
        total: { type: integer }
