openapi: 3.0.3
info:
  title: Clear
  description: |
    A page an AI agent can buy in one HTTP call. Public HTML extract
    (Readability + Turndown). Charge only on a successful extract.

    No signup. No API key. Public pages only.

    Price: $0.005 USDC on Base via x402 / PayAI. JS-only shells fail
    and are not billed. v1 does not run Playwright or hosted Chrome.

    Live host: https://clear-agent-fetch.fly.dev
    payTo: 0x2382EF343ba76a6D56efC8C04920d7df2F98A54D
  version: 1.0.0
  contact:
    name: Clear
    url: https://clear-agent-fetch.fly.dev
externalDocs:
  description: Machine docs (llms.txt)
  url: https://clear-agent-fetch.fly.dev/llms.txt
servers:
  - url: https://clear-agent-fetch.fly.dev
    description: Live
x-discovery:
  ownershipProofs:
    - "0x2382EF343ba76a6D56efC8C04920d7df2F98A54D"
tags:
  - name: fetch
paths:
  /v1/fetch:
    get:
      tags: [fetch]
      summary: Method not allowed
      operationId: fetchUrlGet
      responses:
        '405':
          description: POST only
          headers:
            Allow:
              schema:
                type: string
                example: POST
    post:
      tags: [fetch]
      summary: Fetch a public URL as markdown
      description: |
        First call without PAYMENT-SIGNATURE returns 402 with one accept
        (USDC on Base). Retry the same request with PAYMENT-SIGNATURE
        (x402 v2) after signing.

        VERIFY runs before any fetch. After a valid payment we GET the
        public HTML (8s timeout) and extract markdown. SETTLE only if
        the extract is good markdown. Challenge pages, JS-only shells,
        timeouts, and robots.txt refusals are not billed.
      operationId: fetchUrl
      x-payment-info:
        protocols: [x402]
        price:
          mode: fixed
          currency: USD
          amount: "0.005"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FetchRequest'
            example:
              url: https://example.com
      responses:
        '200':
          description: Successful extract. Payment settled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FetchSuccess'
        '400':
          description: Missing or non-http(s) url.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          description: Policy refuse (robots.txt or non-public target). Not billed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FetchFailure'
        '405':
          description: GET is not allowed. Use POST.
        '422':
          description: Extract failed (not a usable public page). Not billed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FetchFailure'
        '429':
          description: |
            One in-flight fetch per payer wallet, or 5 unsettled
            failures in 60 minutes for that wallet, or global concurrency.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  responses:
    PaymentRequired:
      description: |
        Payment required (x402 v2). One accept: USDC on Base.
        Also sent as a base64 PAYMENT-REQUIRED header.
      headers:
        PAYMENT-REQUIRED:
          description: Base64-encoded PaymentRequired JSON (x402 v2).
          schema:
            type: string
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PaymentRequired'
          examples:
            usdcBase:
              summary: USDC on Base
              value:
                x402Version: 2
                error: PAYMENT-SIGNATURE header is required
                resource:
                  url: https://clear-agent-fetch.fly.dev/v1/fetch
                  description: Fetch a public URL as markdown from the public HTML (Readability)
                  mimeType: application/json
                accepts:
                  - scheme: exact
                    network: eip155:8453
                    amount: "5000"
                    asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
                    payTo: "0x2382EF343ba76a6D56efC8C04920d7df2F98A54D"
                    maxTimeoutSeconds: 60
                    extra:
                      name: USD Coin
                      version: "2"
  schemas:
    FetchRequest:
      type: object
      additionalProperties: false
      required: [url]
      properties:
        url:
          type: string
          format: uri
          description: Public http(s) URL to fetch. Not authenticated or private.
          example: https://example.com
    FetchSuccess:
      type: object
      required: [url, markdown, success, charged, paid_usd, rail]
      properties:
        url:
          type: string
          format: uri
        final_url:
          type: string
          format: uri
          description: URL after public redirects.
        title:
          type: string
        markdown:
          type: string
          description: Page content extracted from the public HTML (Readability).
        success:
          type: boolean
          enum: [true]
        charged:
          type: boolean
          enum: [true]
        paid_usd:
          type: string
          description: USD quoted and settled for this extract.
          example: "0.005"
        rail:
          type: string
          enum: [usdc-base]
          description: Which accept the client paid.
    FetchFailure:
      type: object
      required: [url, success, charged, error]
      properties:
        url:
          type: string
          format: uri
        success:
          type: boolean
          enum: [false]
        charged:
          type: boolean
          enum: [false]
          description: Failed extracts and policy refusals are not billed.
        error:
          type: string
          enum: [extract_failed, policy_refused]
        detail:
          type: string
    PaymentRequired:
      type: object
      required: [x402Version, resource, accepts]
      properties:
        x402Version:
          type: integer
          example: 2
        error:
          type: string
        resource:
          $ref: '#/components/schemas/ResourceInfo'
        accepts:
          type: array
          minItems: 1
          maxItems: 1
          items:
            $ref: '#/components/schemas/PaymentAccept'
    ResourceInfo:
      type: object
      required: [url]
      properties:
        url:
          type: string
          format: uri
        description:
          type: string
        mimeType:
          type: string
    PaymentAccept:
      type: object
      required: [scheme, network, amount, asset, payTo]
      properties:
        scheme:
          type: string
          description: "`exact` for USDC on Base."
          enum: [exact]
        network:
          type: string
          description: "`eip155:8453` (Base)."
          example: eip155:8453
        amount:
          type: string
          description: USDC atomic units (6 decimals). $0.005 = "5000".
          example: "5000"
        asset:
          type: string
          example: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
        payTo:
          type: string
          example: "0x2382EF343ba76a6D56efC8C04920d7df2F98A54D"
        maxTimeoutSeconds:
          type: integer
        extra:
          type: object
          additionalProperties: true
          description: For USDC on Base, name is the on-chain EIP-712 name "USD Coin", version "2".
          properties:
            name:
              type: string
              example: USD Coin
            version:
              type: string
              example: "2"
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: string
        detail:
          type: string
