x402-url-to-markdown / tree / src/discovery.js ยท raw (this view has old GitHub links rewritten for the mirror; raw is byte-exact)
// Discovery documents served by the Worker: GET /openapi.json (x402scan OpenAPI-first discovery),
// GET /.well-known/x402 (x402scan compatibility manifest), and /favicon.ico + /favicon.svg.
// Generated from the YourAITools draft (crypto/youraitools/guides/openapi/webread.openapi.json); price, server URL and payment
// terms are filled in from the live config so the docs can't drift from the 402 challenge.
import { SERVICE_META } from './service-meta.js';
const BASE = {
 "openapi": "3.1.0",
 "info": {
  "title": "WebRead",
  "version": "0.1.4",
  "description": "Converts a public web page into clean Markdown for LLMs: main content only, absolute links, title/description/lang metadata. Respects robots.txt; only successful reads are charged.",
  "x-guidance": "Usage: GET /v1/read?url=<absolute http(s) URL>&maxChars=<100-200000> returns the page as Markdown. $0.002 USDC on Base via x402, charged only on HTTP 200. Free 2,000-character preview: GET /v1/read/preview?url=.",
  "contact": {
   "email": "hello@youraitools.co"
  }
 },
 "servers": [
  {
   "url": "https://webread.agentgate.workers.dev"
  }
 ],
 "paths": {
  "/v1/read": {
   "get": {
    "operationId": "read_url",
    "summary": "Web page to clean Markdown",
    "tags": [
     "paid"
    ],
    "x-payment-info": {
     "price": {
      "mode": "fixed",
      "currency": "USD",
      "amount": "0.002"
     },
     "protocols": [
      {
       "x402": {}
      }
     ]
    },
    "parameters": [
     {
      "name": "url",
      "in": "query",
      "required": true,
      "schema": {
       "type": "string",
       "format": "uri"
      },
      "description": "Absolute http(s) URL of a public web page (HTML or plain text)",
      "example": "https://example.com/"
     },
     {
      "name": "maxChars",
      "in": "query",
      "required": false,
      "schema": {
       "type": "integer",
       "minimum": 100,
       "maximum": 200000,
       "default": 200000
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Success (payment settled)",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "url": {
           "type": "string"
          },
          "finalUrl": {
           "type": "string"
          },
          "status": {
           "type": "integer"
          },
          "contentType": {
           "type": "string"
          },
          "title": {
           "type": [
            "string",
            "null"
           ]
          },
          "description": {
           "type": [
            "string",
            "null"
           ]
          },
          "lang": {
           "type": [
            "string",
            "null"
           ]
          },
          "canonical": {
           "type": [
            "string",
            "null"
           ]
          },
          "markdown": {
           "type": "string"
          },
          "chars": {
           "type": "integer"
          },
          "wordCount": {
           "type": "integer"
          },
          "links": {
           "type": "integer"
          },
          "truncated": {
           "type": "boolean"
          },
          "sourceBytes": {
           "type": "integer"
          },
          "redirects": {
           "type": "integer"
          },
          "fetchMs": {
           "type": "integer"
          },
          "robots": {
           "type": "string"
          },
          "fetchedAt": {
           "type": "string"
          }
         }
        }
       }
      }
     },
     "400": {
      "description": "Invalid input (never charged)"
     },
     "402": {
      "description": "Payment Required (x402 v2 terms in the PAYMENT-REQUIRED header)"
     }
    }
   }
  },
  "/v1/read/preview": {
   "get": {
    "operationId": "read_url_preview",
    "summary": "Free 2,000-character preview (10/min/IP)",
    "security": [],
    "responses": {
     "200": {
      "description": "OK"
     },
     "429": {
      "description": "Free tier rate limit reached"
     }
    },
    "parameters": [
     {
      "name": "url",
      "in": "query",
      "required": true,
      "schema": {
       "type": "string",
       "format": "uri"
      },
      "description": "Absolute http(s) URL of a public web page (HTML or plain text)",
      "example": "https://example.com/"
     }
    ]
   }
  },
  "/health": {
   "get": {
    "operationId": "health",
    "summary": "Health",
    "security": [],
    "responses": {
     "200": {
      "description": "OK"
     }
    }
   }
  },
  "/v1/stats": {
   "get": {
    "operationId": "stats",
    "summary": "Public usage counters",
    "security": [],
    "responses": {
     "200": {
      "description": "OK"
     }
    }
   }
  },
  "/mcp": {
   "post": {
    "operationId": "mcp",
    "summary": "Remote MCP (Streamable HTTP)",
    "security": [],
    "responses": {
     "200": {
      "description": "OK"
     }
    }
   }
  },
  "/openapi.json": {
   "get": {
    "operationId": "openapi",
    "summary": "This OpenAPI document",
    "security": [],
    "responses": {
     "200": {
      "description": "OK"
     }
    }
   }
  },
  "/.well-known/x402": {
   "get": {
    "operationId": "x402_manifest",
    "summary": "x402 discovery manifest (paid resources)",
    "security": [],
    "responses": {
     "200": {
      "description": "OK"
     }
    }
   }
  }
 }
};

export const FAVICON_SVG = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\" width=\"64\" height=\"64\" role=\"img\" aria-label=\"WebRead\"><rect width=\"64\" height=\"64\" rx=\"14\" fill=\"#0f172a\"/><path d=\"M14 18h36v28H14z\" fill=\"none\" stroke=\"#7c9cff\" stroke-width=\"3\" stroke-linejoin=\"round\"/><path d=\"M14 25h36\" stroke=\"#7c9cff\" stroke-width=\"3\"/><path d=\"M21 34l4 6 4-8 4 8 4-6\" fill=\"none\" stroke=\"#4ade80\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path d=\"M42 33v8m-3-3l3 3 3-3\" fill=\"none\" stroke=\"#e6e9f0\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>";
const ICO_B64 = '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';
export const FAVICON_ICO = Uint8Array.from(atob(ICO_B64), (ch) => ch.charCodeAt(0));

export const OPENAPI_PATHS = ['/v1/read', '/v1/read/preview'];
const usd = (price) => String(price).replace(/^\$/, '');

/** OpenAPI 3.1 document with live price, origin and x402 terms. */
export function openapiDoc({ origin, price, network, payTo, asset }) {
  const doc = JSON.parse(JSON.stringify(BASE));
  doc.servers = [{ url: origin }];
  doc.info.contact = { name: 'WebRead', email: 'hello@youraitools.co', url: 'https://youraitools.co/guides/pay-per-call-web-scraper-api' };
  doc.info.license = { name: 'MIT' };
  doc.externalDocs = { description: 'Integration guide', url: 'https://youraitools.co/guides/pay-per-call-web-scraper-api' };
  const op = doc.paths['/v1/read'].get;
  op['x-payment-info'].price.amount = usd(price);
  // Informational: the exact runtime terms (the live 402 challenge is authoritative).
  op['x-x402'] = { scheme: 'exact', network, asset, payTo, amount: String(Math.round(Number(usd(price)) * 1e6)), assetSymbol: 'USDC', x402Versions: [2, 1] };
  doc.info['x-guidance'] = doc.info['x-guidance'].replace(/\$[0-9.]+ USDC/, `$${usd(price)} USDC`);
  // x402scan lists every path in this doc: publish only the paid route and its free preview.
  doc.paths = Object.fromEntries(Object.entries(doc.paths).filter(([p]) => OPENAPI_PATHS.includes(p)));
    doc.info['x-service-name'] = SERVICE_META.serviceName; doc.info['x-category'] = SERVICE_META.category;
  doc.info['x-categories'] = SERVICE_META.categories; doc.info['x-tags'] = SERVICE_META.tags; doc.info['x-logo'] = { url: SERVICE_META.iconUrl };
  return doc;
}

/** /.well-known/x402 manifest (x402scan compatibility format). */
export function wellKnownX402({ origin }) {
  return {
    version: 1,
    name: SERVICE_META.serviceName, category: SERVICE_META.category, categories: SERVICE_META.categories, tags: SERVICE_META.tags, iconUrl: SERVICE_META.iconUrl,
    resources: [`${origin}/v1/read`],
    instructions: "GET /v1/read?url=<absolute http(s) URL>&maxChars=<100-200000>. Price: $0.002 USDC on Base (eip155:8453), paid via x402 v2 (PAYMENT-SIGNATURE) or v1 (X-PAYMENT); only HTTP 200 reads are settled. Free preview: GET /v1/read/preview?url=. Full docs: /openapi.json",
    openapi: `${origin}/openapi.json`,
  };
}

agentgate-source: code mirror on Cloudflare Pages. All repos