From 120f033cf50fd934b558b44907653c681962f84c Mon Sep 17 00:00:00 2001 From: OpSpawn Date: Fri, 6 Feb 2026 10:57:40 +0000 Subject: [PATCH] v3.2: x402 discovery endpoint, demo client, hackathon prep - Added /x402 endpoint: machine-readable service catalog for agent discovery - Added x402-demo-client.mjs: demonstrates full x402 payment flow - Updated README with three distribution layers and discovery docs - Service catalog includes pricing, input/output schemas, MCP info, Bazaar reference --- README.md | 66 +++++++++++++++++++--------- server.js | 82 ++++++++++++++++++++++++++++++++++ x402-demo-client.mjs | 102 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 229 insertions(+), 21 deletions(-) create mode 100644 x402-demo-client.mjs diff --git a/README.md b/README.md index 2c64986..08c7f8c 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,50 @@ -# SnapAPI v3.1 +# SnapAPI v3.2 -Screenshot, PDF, and Markdown conversion API with x402 micropayments and MCP support. +Screenshot, PDF, and Markdown conversion API with x402 micropayments, Bazaar discovery, and MCP support. + +**Live:** [api.opspawn.com/screenshot-api](https://api.opspawn.com/screenshot-api/) ## Features + - URL screenshot capture (PNG, JPEG) - PDF generation from URLs - Markdown to PDF/PNG/HTML conversion - **x402 micropayments** - pay-per-request with USDC on Base (no signup needed) - **Bazaar discovery** - machine-readable API schemas for AI agent auto-discovery +- **Service catalog** - `/x402` endpoint for programmatic service discovery - **MCP server** - Model Context Protocol integration for LLM agents - API key authentication (traditional) - Rate limiting and concurrency control - SSRF protection +## Three Distribution Layers + +SnapAPI implements three complementary ways for AI agents to discover and use it: + +| Layer | How It Works | For | +|-------|-------------|-----| +| **x402 Micropayments** | HTTP 402 → sign USDC → get result | Agents with wallets | +| **Bazaar Discovery** | JSON Schema in 402 response headers | Auto-discovery | +| **MCP Tools** | stdio transport for Claude Code/Desktop | LLM agent environments | + +## x402 Service Discovery + +Query `/x402` for a machine-readable catalog of all services: + +```bash +curl https://api.opspawn.com/screenshot-api/x402 +``` + +Returns structured JSON with all endpoints, pricing, input/output schemas, and payment details. Designed for agent-to-agent service discovery. + ## x402 Micropayments Pay per request with USDC on Base network. No signup, no API keys needed. ```bash -# Without payment, you get a 402 response with payment requirements: +# Without payment, get a 402 response with payment requirements: curl https://api.opspawn.com/screenshot-api/api/capture?url=https://example.com -# Returns: HTTP 402 with Payment-Required header +# Returns: HTTP 402 with Payment-Required header + Bazaar discovery metadata # With x402 payment signature: curl -H "Payment-Signature: " \ @@ -31,6 +55,14 @@ curl -H "Payment-Signature: " \ - Screenshot capture: $0.01/request - Markdown to PDF: $0.005/request - Markdown to PNG: $0.005/request +- Markdown to HTML: Free + +### Demo Client + +```bash +# Run the x402 demo to see the full payment flow: +node x402-demo-client.mjs --dry-run +``` ## MCP Server @@ -39,10 +71,7 @@ Use SnapAPI as an MCP tool server for Claude Code, Claude Desktop, or any MCP-co ### Setup ```bash -# Install dependencies npm install - -# Run MCP server (stdio transport) node mcp-server.mjs # With custom API URL and key @@ -78,24 +107,19 @@ Add to `claude_desktop_config.json`: | `markdown_to_html` | Convert Markdown to styled HTML (free) | | `api_status` | Check service health and stats | -### Available Resources - -| Resource | URI | Description | -|----------|-----|-------------| -| API Docs | `snapapi://docs` | Full API documentation | - ## Traditional API ### Endpoints -| Method | Path | Auth | Description | -|--------|------|------|-------------| -| GET | `/api/capture` | Required | Capture screenshot/PDF from URL | -| POST | `/api/md2pdf` | Required | Markdown to PDF | -| POST | `/api/md2png` | Required | Markdown to PNG/JPEG | -| POST | `/api/md2html` | Free | Markdown to HTML | -| GET | `/api/status` | Free | Service health | -| GET | `/api/pricing` | Free | Subscription plans | +| Method | Path | Auth | Price | Description | +|--------|------|------|-------|-------------| +| GET | `/x402` | Free | - | Service discovery catalog | +| GET | `/api/capture` | Required | $0.01 | Capture screenshot/PDF from URL | +| POST | `/api/md2pdf` | Required | $0.005 | Markdown to PDF | +| POST | `/api/md2png` | Required | $0.005 | Markdown to PNG/JPEG | +| POST | `/api/md2html` | Free | - | Markdown to HTML | +| GET | `/api/status` | Free | - | Service health | +| GET | `/api/pricing` | Free | - | Subscription plans | ### Quick Start diff --git a/server.js b/server.js index f676319..6d379ed 100644 --- a/server.js +++ b/server.js @@ -349,6 +349,88 @@ const server = http.createServer(async (req, res) => { return; } + // x402 discovery endpoint - machine-readable service catalog for agents + if (parsed.pathname === '/x402') { + sendJson(res, 200, { + protocol: 'x402', + version: '2.3', + service: { + name: 'SnapAPI', + description: 'Screenshot & document generation API with x402 micropayments', + provider: 'OpSpawn', + website: 'https://opspawn.com', + github: 'https://github.com/opspawn/screenshot-api', + }, + payment: { + network: x402.NETWORK, + token: 'USDC', + facilitator: 'https://facilitator.payai.network', + wallet: x402.WALLET_ADDRESS, + }, + endpoints: [ + { + method: 'GET', + path: '/api/capture', + description: 'Capture screenshot or PDF from a URL', + price: x402.PRICES.capture, + input: { + url: { type: 'string', required: true, description: 'URL to capture' }, + format: { type: 'string', required: false, description: 'png, jpeg, or pdf', default: 'png' }, + width: { type: 'number', required: false, description: 'Viewport width (320-3840)', default: 1280 }, + height: { type: 'number', required: false, description: 'Viewport height (200-2160)', default: 800 }, + fullPage: { type: 'boolean', required: false, description: 'Capture full scrollable page' }, + }, + output: { type: 'binary', mimeTypes: ['image/png', 'image/jpeg', 'application/pdf'] }, + }, + { + method: 'POST', + path: '/api/md2pdf', + description: 'Convert Markdown to PDF document', + price: x402.PRICES.md2pdf, + input: { + markdown: { type: 'string', required: true, description: 'Markdown content' }, + theme: { type: 'string', required: false, description: 'light or dark', default: 'light' }, + paperSize: { type: 'string', required: false, description: 'A4, Letter, Legal, Tabloid', default: 'A4' }, + }, + output: { type: 'binary', mimeTypes: ['application/pdf'] }, + }, + { + method: 'POST', + path: '/api/md2png', + description: 'Convert Markdown to PNG image', + price: x402.PRICES.md2png, + input: { + markdown: { type: 'string', required: true, description: 'Markdown content' }, + theme: { type: 'string', required: false, description: 'light or dark', default: 'light' }, + width: { type: 'number', required: false, description: 'Viewport width', default: 800 }, + }, + output: { type: 'binary', mimeTypes: ['image/png', 'image/jpeg'] }, + }, + { + method: 'POST', + path: '/api/md2html', + description: 'Convert Markdown to styled HTML (free, no payment required)', + price: '$0.00', + input: { + markdown: { type: 'string', required: true, description: 'Markdown content' }, + theme: { type: 'string', required: false, description: 'light or dark', default: 'light' }, + }, + output: { type: 'text', mimeTypes: ['text/html'] }, + }, + ], + mcp: { + description: 'Also available as MCP tools for Claude Code/Desktop', + tools: ['capture_screenshot', 'markdown_to_pdf', 'markdown_to_image', 'markdown_to_html', 'api_status'], + install: 'npx github:opspawn/screenshot-api/mcp-server.mjs', + }, + bazaar: { + description: 'x402 Bazaar discovery extensions included in 402 responses', + specification: 'https://github.com/x402/x402/tree/main/extensions/bazaar', + }, + }); + return; + } + // API docs - JSON if (parsed.pathname === '/api') { sendJson(res, 200, { diff --git a/x402-demo-client.mjs b/x402-demo-client.mjs new file mode 100644 index 0000000..3322f70 --- /dev/null +++ b/x402-demo-client.mjs @@ -0,0 +1,102 @@ +#!/usr/bin/env node +// x402-demo-client.mjs - Demonstrates the x402 payment flow with SnapAPI +// Usage: node x402-demo-client.mjs [endpoint] [--dry-run] +// +// This script shows how an AI agent can discover and pay for API services +// using the x402 protocol. No signup, no API keys - just HTTP + USDC. + +import { createPublicClient, createWalletClient, http, parseAbi } from 'viem'; +import { base } from 'viem/chains'; + +const SNAPAPI_URL = process.env.SNAPAPI_URL || 'https://api.opspawn.com/screenshot-api'; +const dryRun = process.argv.includes('--dry-run'); + +async function main() { + console.log('=== x402 Payment Flow Demo ===\n'); + + // Step 1: Discover available services + console.log('1. Discovering services at', SNAPAPI_URL + '/x402'); + const discovery = await fetch(SNAPAPI_URL + '/x402'); + const catalog = await discovery.json(); + + console.log(` Service: ${catalog.service.name} by ${catalog.service.provider}`); + console.log(` Payment: ${catalog.payment.token} on ${catalog.payment.network}`); + console.log(` Endpoints:`); + for (const ep of catalog.endpoints) { + console.log(` ${ep.method} ${ep.path} - ${ep.description} (${ep.price})`); + } + console.log(); + + // Step 2: Make an unauthenticated request to get payment requirements + console.log('2. Requesting screenshot without payment...'); + const captureUrl = `${SNAPAPI_URL}/api/capture?url=https://example.com`; + const noAuthResponse = await fetch(captureUrl); + + console.log(` Status: ${noAuthResponse.status} (${noAuthResponse.statusText})`); + + if (noAuthResponse.status === 402) { + // Extract x402 payment requirements from response + const paymentRequired = noAuthResponse.headers.get('payment-required'); + if (paymentRequired) { + try { + const requirements = JSON.parse(Buffer.from(paymentRequired, 'base64').toString()); + console.log(' Payment Requirements:'); + console.log(` Scheme: ${requirements.scheme || 'exact'}`); + console.log(` Network: ${requirements.network}`); + console.log(` Pay to: ${requirements.payTo}`); + console.log(` Price: ${requirements.maxAmountRequired} (raw USDC units)`); + + if (requirements.extensions) { + console.log(' Bazaar Discovery Extensions:'); + console.log(` ${JSON.stringify(requirements.extensions, null, 2).split('\n').join('\n ')}`); + } + } catch (e) { + console.log(' Payment-Required header (base64):', paymentRequired.substring(0, 80) + '...'); + } + } + console.log(); + + if (dryRun) { + console.log('3. [DRY RUN] Skipping actual payment. In production, an agent would:'); + console.log(' a. Read the payment requirements from the 402 response'); + console.log(' b. Sign a USDC authorization using their wallet private key'); + console.log(' c. Retry the request with Payment-Signature header'); + console.log(' d. Receive the screenshot as the response body'); + console.log(' e. The facilitator settles the USDC transfer on-chain'); + console.log('\n Total cost: $0.01 per screenshot, settled on Base L2'); + console.log(' No signup. No API key. Just HTTP + USDC.\n'); + } else { + console.log('3. To complete payment, an x402-enabled agent would sign the authorization'); + console.log(' with their wallet and retry the request. See @x402/client for details.\n'); + } + } else { + const body = await noAuthResponse.text(); + console.log(` Response: ${body.substring(0, 200)}`); + } + + // Step 3: Show the free endpoint (md2html doesn't need payment) + console.log('4. Testing free endpoint (md2html - no payment needed)...'); + const md2htmlResponse = await fetch(`${SNAPAPI_URL}/api/md2html`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + markdown: '# Hello from x402 Demo\n\nThis was generated by an AI agent using the x402 protocol.', + theme: 'dark' + }), + }); + + console.log(` Status: ${md2htmlResponse.status}`); + if (md2htmlResponse.ok) { + const html = await md2htmlResponse.text(); + console.log(` Response: ${html.length} bytes of styled HTML`); + console.log(` Preview: ${html.substring(0, 100)}...`); + } + + console.log('\n=== Demo Complete ==='); + console.log('Learn more: https://x402.org | https://github.com/opspawn/screenshot-api'); +} + +main().catch(err => { + console.error('Error:', err.message); + process.exit(1); +});