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
This commit is contained in:
OpSpawn 2026-02-06 10:57:40 +00:00
parent 0ddd7f2774
commit 120f033cf5
3 changed files with 229 additions and 21 deletions

View File

@ -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 ## Features
- URL screenshot capture (PNG, JPEG) - URL screenshot capture (PNG, JPEG)
- PDF generation from URLs - PDF generation from URLs
- Markdown to PDF/PNG/HTML conversion - Markdown to PDF/PNG/HTML conversion
- **x402 micropayments** - pay-per-request with USDC on Base (no signup needed) - **x402 micropayments** - pay-per-request with USDC on Base (no signup needed)
- **Bazaar discovery** - machine-readable API schemas for AI agent auto-discovery - **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 - **MCP server** - Model Context Protocol integration for LLM agents
- API key authentication (traditional) - API key authentication (traditional)
- Rate limiting and concurrency control - Rate limiting and concurrency control
- SSRF protection - 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 ## x402 Micropayments
Pay per request with USDC on Base network. No signup, no API keys needed. Pay per request with USDC on Base network. No signup, no API keys needed.
```bash ```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 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: # With x402 payment signature:
curl -H "Payment-Signature: <signed-authorization>" \ curl -H "Payment-Signature: <signed-authorization>" \
@ -31,6 +55,14 @@ curl -H "Payment-Signature: <signed-authorization>" \
- Screenshot capture: $0.01/request - Screenshot capture: $0.01/request
- Markdown to PDF: $0.005/request - Markdown to PDF: $0.005/request
- Markdown to PNG: $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 ## MCP Server
@ -39,10 +71,7 @@ Use SnapAPI as an MCP tool server for Claude Code, Claude Desktop, or any MCP-co
### Setup ### Setup
```bash ```bash
# Install dependencies
npm install npm install
# Run MCP server (stdio transport)
node mcp-server.mjs node mcp-server.mjs
# With custom API URL and key # 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) | | `markdown_to_html` | Convert Markdown to styled HTML (free) |
| `api_status` | Check service health and stats | | `api_status` | Check service health and stats |
### Available Resources
| Resource | URI | Description |
|----------|-----|-------------|
| API Docs | `snapapi://docs` | Full API documentation |
## Traditional API ## Traditional API
### Endpoints ### Endpoints
| Method | Path | Auth | Description | | Method | Path | Auth | Price | Description |
|--------|------|------|-------------| |--------|------|------|-------|-------------|
| GET | `/api/capture` | Required | Capture screenshot/PDF from URL | | GET | `/x402` | Free | - | Service discovery catalog |
| POST | `/api/md2pdf` | Required | Markdown to PDF | | GET | `/api/capture` | Required | $0.01 | Capture screenshot/PDF from URL |
| POST | `/api/md2png` | Required | Markdown to PNG/JPEG | | POST | `/api/md2pdf` | Required | $0.005 | Markdown to PDF |
| POST | `/api/md2html` | Free | Markdown to HTML | | POST | `/api/md2png` | Required | $0.005 | Markdown to PNG/JPEG |
| GET | `/api/status` | Free | Service health | | POST | `/api/md2html` | Free | - | Markdown to HTML |
| GET | `/api/pricing` | Free | Subscription plans | | GET | `/api/status` | Free | - | Service health |
| GET | `/api/pricing` | Free | - | Subscription plans |
### Quick Start ### Quick Start

View File

@ -349,6 +349,88 @@ const server = http.createServer(async (req, res) => {
return; 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 // API docs - JSON
if (parsed.pathname === '/api') { if (parsed.pathname === '/api') {
sendJson(res, 200, { sendJson(res, 200, {

102
x402-demo-client.mjs Normal file
View File

@ -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);
});