{"openapi":"3.1.0","info":{"title":"Markdown Extractor","version":"0.2.0","description":"Fetches a public URL and returns clean, token-efficient markdown, paywalled with the x402 v2 protocol on Base Mainnet. Designed for autonomous AI agents to call directly: no API key, no signup — pay per call in USDC.","contact":{"url":"https://x402.org"}},"servers":[{"url":"https://markdown-extractor.jannioura.workers.dev","description":"Live edge deployment (Base Mainnet)"}],"x-402":{"version":2,"scheme":"exact","network":"eip155:8453","networkName":"Base Mainnet","asset":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913","assetSymbol":"USDC","amount":"10000","amountDecimal":"0.01","payTo":"0x86256e25d4e5511eb6dcc474a4e779970267e903","facilitator":"https://x402.primer.systems","maxTimeoutSeconds":300,"eip712Domain":{"name":"USD Coin","version":"2","note":"Verified on-chain via name()/version() on the deployed Base Mainnet USDC contract — this is 'USD Coin', deliberately different from Sepolia's 'USDC'."},"headers":{"request":"PAYMENT-SIGNATURE — base64-encoded x402 v2 PaymentPayload (EIP-3009 TransferWithAuthorization)","challenge":"PAYMENT-REQUIRED — base64-encoded x402 v2 PaymentRequired body, sent on every 402","receipt":"PAYMENT-RESPONSE — base64-encoded settlement receipt, sent on every 200"}},"paths":{"/":{"get":{"operationId":"extractMarkdownGet","summary":"Extract clean markdown from a URL (query-param form)","description":"Same operation as the POST below, for clients/scanners that probe with a plain unauthenticated GET before reading this document. First call with no PAYMENT-SIGNATURE header to receive a 402 challenge. Sign an EIP-3009 TransferWithAuthorization, then retry the identical GET with PAYMENT-SIGNATURE set. Unlike POST, payment presence is checked before the `url` param here, so a bare probe with neither still gets the real 402 challenge instead of a 400.","parameters":[{"name":"PAYMENT-SIGNATURE","in":"header","required":false,"description":"Base64-encoded x402 v2 PaymentPayload. Omit on the first call to receive the 402 challenge.","schema":{"type":"string"}},{"name":"url","in":"query","required":true,"description":"Public http(s) URL to fetch. Required once a payment is attached; a probe with no payment still gets a 402 regardless of whether this is present.","schema":{"type":"string","format":"uri"}},{"name":"include_links","in":"query","required":false,"description":"Defaults to true; pass `include_links=false` to disable.","schema":{"type":"string","enum":["true","false"]}},{"name":"strip_images","in":"query","required":false,"description":"Defaults to false; pass `strip_images=true` to enable.","schema":{"type":"string","enum":["true","false"]}},{"name":"max_chars","in":"query","required":false,"description":"Clamped to [1000, 200000]. Defaults to 50000.","schema":{"type":"integer","minimum":1000,"maximum":200000}}],"responses":{"200":{"$ref":"#/components/responses/ExtractSuccess"},"400":{"description":"`url` query parameter missing/unsafe (non-http(s), localhost, private IP). Only checked once a payment is attached — see the operation description.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"402":{"$ref":"#/components/responses/PaymentRequired"},"502":{"$ref":"#/components/responses/UpstreamFetchFailed"},"504":{"$ref":"#/components/responses/UpstreamFetchTimeout"}}},"post":{"operationId":"extractMarkdown","summary":"Extract clean markdown from a URL","description":"First call with no PAYMENT-SIGNATURE header to receive a 402 challenge (see x-402 in this document, or decode the PAYMENT-REQUIRED response header). Sign an EIP-3009 TransferWithAuthorization for the exact amount/asset/payTo it specifies, then retry the identical request with PAYMENT-SIGNATURE set to the base64-encoded PaymentPayload. Input validation (malformed body, unsafe URL) happens before any payment check, so a bad request never costs anything.","parameters":[{"name":"PAYMENT-SIGNATURE","in":"header","required":false,"description":"Base64-encoded x402 v2 PaymentPayload. Omit on the first call to receive the 402 challenge.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"Public http(s) URL to fetch. Localhost/private-IP hosts and non-http(s) schemes are rejected with 400 (SSRF guard)."},"options":{"type":"object","properties":{"include_links":{"type":"boolean","default":true,"description":"Render <a href> as markdown [text](href) links."},"strip_images":{"type":"boolean","default":false,"description":"Drop <img> entirely instead of rendering ![alt](src)."},"max_chars":{"type":"integer","minimum":1000,"maximum":200000,"default":50000,"description":"Clamped to [1000, 200000]."}}}}},"examples":{"default":{"value":{"url":"https://example.com/post"}},"withOptions":{"value":{"url":"https://example.com/post","options":{"include_links":true,"strip_images":false,"max_chars":50000}}}}}}},"responses":{"200":{"$ref":"#/components/responses/ExtractSuccess"},"400":{"description":"Malformed JSON body, or `url` missing/unsafe (non-http(s), localhost, private IP). No payment was checked or required.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"402":{"$ref":"#/components/responses/PaymentRequired"},"502":{"$ref":"#/components/responses/UpstreamFetchFailed"},"504":{"$ref":"#/components/responses/UpstreamFetchTimeout"}}}}},"components":{"responses":{"ExtractSuccess":{"description":"Extraction succeeded and payment settled. cached=true means the KV cache (5 min TTL) served it without re-fetching the origin, but you were still charged for a valid, signed request.","headers":{"PAYMENT-RESPONSE":{"description":"Base64-encoded settlement receipt: { success, transaction, network, payer }","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","required":["title","markdown","source_url","chars","est_tokens","cached","fetched_at"],"properties":{"title":{"type":"string"},"markdown":{"type":"string"},"source_url":{"type":"string","format":"uri"},"chars":{"type":"integer"},"est_tokens":{"type":"integer","description":"chars / 4, rounded up — a rough estimate, not a real tokenizer count."},"cached":{"type":"boolean"},"fetched_at":{"type":"string","format":"date-time"}}},"example":{"title":"Example Domain","markdown":"# Example Domain\n\nThis domain is for use in illustrative examples...","source_url":"https://example.com/post","chars":189,"est_tokens":48,"cached":false,"fetched_at":"2026-09-18T00:00:00.000Z"}}}},"PaymentRequired":{"description":"Payment required, or a submitted payment was rejected. `error` carries the real reason (e.g. \"verify failed: ...\", \"settle failed: ...\") when a payment was attempted but failed, not just the generic \"header is required\" message on the first call.","headers":{"PAYMENT-REQUIRED":{"description":"Base64-encoded x402 v2 PaymentRequired body (same JSON as the response body).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","required":["x402Version","error","resource","accepts","extensions"],"properties":{"x402Version":{"type":"integer","const":2},"error":{"type":"string"},"resource":{"type":"object","description":"The v2 ResourceInfo object — the single source of truth for the resource URL. Also duplicated per-entry below as a plain string, for v1-only readers.","properties":{"url":{"type":"string","format":"uri"},"description":{"type":"string"},"mimeType":{"type":"string"}}},"accepts":{"type":"array","items":{"type":"object","properties":{"scheme":{"type":"string","const":"exact"},"network":{"type":"string","description":"CAIP-2 id, e.g. eip155:8453"},"amount":{"type":"string","description":"x402 v2 field name for the required amount, atomic units (6 decimals for USDC). Canonical — use this."},"maxAmountRequired":{"type":"string","description":"Compatibility shim: identical value to `amount`, under the x402 v1 field name, added purely so v1-only scanners/clients (e.g. agent-ready.dev's x402-checker) can parse this response. Not sent to the facilitator."},"asset":{"type":"string","description":"ERC-20 contract address"},"payTo":{"type":"string"},"maxTimeoutSeconds":{"type":"integer"},"resource":{"type":"string","format":"uri","description":"Compatibility shim: same URL as the top-level `resource.url`, duplicated here under the x402 v1 per-entry field name for v1-only readers."},"extra":{"type":"object","properties":{"name":{"type":"string"},"version":{"type":"string"}},"description":"EIP-712 domain name/version to sign the authorization with — use these exact values, not an assumed token name."}}}},"extensions":{"type":"object"}}}}}},"UpstreamFetchFailed":{"description":"The target URL couldn't be fetched, or returned a non-2xx status. Only reachable after a successful settlement.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"UpstreamFetchTimeout":{"description":"The target URL didn't respond within the worker's own 5-second bound (an explicit AbortController timeout, not a platform accident). Only reachable after a successful settlement.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}}