{"openapi":"3.1.0","info":{"title":"Relaystation","version":"v1","description":"Pay-per-call API platform for AI agents and developers. Baton — a prepaid storage object configured along five groups (write behavior · capacity · sharing · lifecycle · trust); five named preconfigs (Drop / Pass / Scratchpad / Checkpoint / Ledger) are fast-start prefills, not the organizing principle. Engine-priced everywhere — one quote, frozen at create.","contact":{"url":"https://relaystation.ai","email":"hi@relaystation.ai"},"license":{"name":"Proprietary"}},"servers":[{"url":"https://api.relaystation.ai","description":"production"}],"x-discovery":{"ownershipProofs":["0x5F7254f252eF9b9f92B6B4F2cF91205eD7A96C7A"]},"x-payment-info":{"protocol":"x402","version":2,"scheme":"exact","asset":"USDC","networks":["eip155:84532","eip155:8453"],"facilitator":"https://api.cdp.coinbase.com","discovery":"https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources","description":"Per-call payment via x402 v2 (EIP-3009 exact, USDC/EURC). Billable routes return 402 + accepts[] + a bazaar discovery extension; settled through CDP."},"tags":[{"name":"baton","description":"Storage — store a file, share it by link (no account for the recipient), collaborate on a doc with a person or another agent, save state snapshots, keep an append-only or tamper-evident hash-chained ledger. The prepaid storage object: create, read, write, share, extend."},{"name":"baton-pricing","description":"Storage — public preconfigs + price-of-record (quote before you store)."},{"name":"baton-trust","description":"Storage trust — notarize a document (witness its hash, kept 7 years), prove a witnessed document offline, verify a hash-chain's integrity."},{"name":"esigndoc","description":"Documents & signatures — send a PDF for legally-binding e-signature, place signature/approval boxes, route to multiple signers and approvers in a set order, reuse a contract template, chase unsigned recipients, retrieve the signed copy. (Verify a signed PDF's signatures via pdf_verify_signatures.) Billed per sent envelope."},{"name":"idverify","description":"Identity & compliance — verify a government ID (passport / driver's license / national ID), run a liveness check, match a face to the ID photo, optional AML/PEP screening as part of the check. Government-ID documents only, not arbitrary credentials. Hosted human-in-the-loop; create returns a hostedUrl. Billed per verification."},{"name":"screening","description":"Identity & compliance — screen a name against sanctions (OFAC / EU / UK; UN rides along) and Wikidata-derived PEP lists. Composable pay-per-call; charge-on-attempt. Scope via lists:[sanctions,pep] (default both). PEP coverage has known named gaps — not a substitute for a commercial AML provider. Free sources; attribution returned in every response."},{"name":"cputools","description":"Stateless pay-per-call CPU utilities, grouped by job: Microsoft Office (Word/Excel/PowerPoint → PDF, generate from markdown, Excel I/O), PDF tools (merge/split/OCR/render/watermark/encrypt/verify-signatures), convert documents (markup & HTML→PDF), images, audio & video, data & spreadsheets (SQL/filter/join/pipelines), and text utilities. Inline base64 ≤ 4 MiB or a storage-ref { inputKey } from upload-url."},{"name":"llm","description":"Language & AI tasks — translate, summarize, extract to JSON, classify, proofread, rewrite, sentiment, keywords, title, repair JSON. Task-based (never model-based); pick a tier (budget/value/best), authorize a published ceiling, pay the model's real cost + a small markup. The model behind each tier is named + dated on the disclosure page. GET /v1/llm/tasks is the live catalog."},{"name":"location","description":"Location — geocode an address, reverse-geocode coordinates, search places/POIs, calculate a route (AWS Location Service). Flat pay-per-call."},{"name":"docai","description":"Document AI — OCR with layout, extract form fields/tables, parse invoices/receipts and identity documents, ask questions of a page (sync Textract; single-page docs ≤ 5 MB at launch). Flat pay-per-document."},{"name":"vision","description":"Vision — label objects/scenes, moderate unsafe content, detect faces + attributes, compare faces, recognize celebrities, detect protective equipment (AWS Rekognition). Flat pay-per-image."},{"name":"bridge","description":"Messaging & coordination — give an agent an email address, message another agent, read an agent inbox, ask a human and wait for a reply, or notify a human (Telegram / email / SMS)."},{"name":"contracts","description":"Agreements & arbitration — create a binding-by-goodwill agreement (a hash-chained, auto-witnessed record), sign it (EIP-712 wallet or authenticated account; no cross-method satisfaction), and arbitrate disputes via a transparent AI arbiter. No escrow, no money held or moved; NOT legally binding, not court-enforceable — goodwill-honored. Offline-verifiable via GET /v1/contracts/disclosure."},{"name":"account","description":"Account & billing — balance, transactions, top-up, API keys, product enrollment."},{"name":"auth","description":"Account & billing — wallet-JWT challenge/verify (the crypto-native sign-in)."},{"name":"webhooks","description":"Account & billing — outbound webhook registrations + delivery history."},{"name":"discovery","description":"Discovery — OpenAPI / llms.txt / MCP advertisement / system info."}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"rs_live_<32-hex> | rs_test_<32-hex>","description":"API key minted at app.relaystation.ai after OAuth signup."},"walletJwt":{"type":"http","scheme":"bearer","bearerFormat":"JWT (HS256)","description":"Issued by POST /v1/auth/verify after EIP-191 wallet signature. Used for read paths."},"x402":{"type":"apiKey","in":"header","name":"PAYMENT-SIGNATURE","description":"Base64-encoded EIP-3009 PaymentPayload (USDC or EURC, v2, on Base / Base Sepolia). The 402 challenge advertises one accepts[] entry per accepted asset; sign the chosen one. One call = one payment."},"adminSession":{"type":"apiKey","in":"cookie","name":"rs_admin_session","description":"Operator-only admin session cookie (issued by admin Google OAuth, gated by the admin_users allowlist). Used by the operator-facing admin routes — NOT a customer auth mode. Documented here only so the Courier admin refund operation is discoverable; customers cannot call it."}},"schemas":{"WebhookEventName":{"type":"string","enum":["baton.written","token.used","token.exhausted","pass.all_tokens_consumed","bridge.ask.replied","envelope.sent","envelope.viewed","envelope.signed","envelope.completed","envelope.rejected","envelope.expired","envelope.cancelled","verification.started","verification.document_processed","verification.age_check","verification.manual_review","verification.completed","verification.failed","verification.expired","job.completed"],"description":"A webhook event type from the live allowlist (GET /v1/webhooks/events)."},"V1Webhook":{"type":"object","required":["id","url","events","active","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEventName"}},"description":{"type":"string","nullable":true},"active":{"type":"boolean"},"consecutiveFailures":{"type":"integer"},"secret":{"type":"string","description":"Returned ONCE on POST /v1/webhooks. Subsequent reads omit."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"TabularFormat":{"type":"string","enum":["csv","tsv","json","ndjson"],"description":"A tabular file format: csv, tsv, json (array of objects), or ndjson."},"V1WebhookDelivery":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"webhookId":{"type":"string","format":"uuid"},"event":{"type":"string"},"subjectId":{"type":"string","format":"uuid","nullable":true},"status":{"type":"string","enum":["PENDING","SUCCESS","FAILED"]},"attempts":{"type":"integer"},"responseCode":{"type":"integer","nullable":true},"errorMessage":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid auth.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found or not owned by the customer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ValidationError":{"description":"Invalid request body or parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/health":{"get":{"tags":["discovery"],"summary":"Health","description":"Health check.","security":[],"responses":{"200":{"description":"OK"}},"operationId":"healthCheck"}},"/openapi.json":{"get":{"tags":["discovery"],"summary":"OpenAPI spec","description":"This OpenAPI 3.1 spec.","security":[],"responses":{"200":{"description":"OpenAPI document."}},"operationId":"getOpenApiSpec"}},"/llms.txt":{"get":{"tags":["discovery"],"summary":"llms.txt","description":"Plain-text capability summary for LLM agent discovery.","security":[],"responses":{"200":{"description":"Capability summary.","content":{"text/plain":{"schema":{"type":"string"}}}}},"operationId":"getLlmsTxt"}},"/llms-full.txt":{"get":{"tags":["discovery"],"summary":"llms-full.txt","description":"Long-form plain-text spec — every endpoint, the five-group baton model + preconfigs, the x402 wire, the trust model.","security":[],"responses":{"200":{"description":"Full agent reference.","content":{"text/plain":{"schema":{"type":"string"}}}}},"operationId":"getLlmsFullTxt"}},"/.well-known/mcp.json":{"get":{"tags":["discovery"],"summary":"MCP advertisement","description":"MCP-server advertisement.","security":[],"responses":{"200":{"description":"MCP advertisement."},"404":{"description":"MCP disabled (`chassis.feature.mcp_enabled = false`)."}},"operationId":"getMcpManifest"}},"/.well-known/ai-catalog.json":{"get":{"tags":["discovery"],"summary":"ARD ai-catalog manifest","description":"Agentic Resource Discovery (ARD) manifest — this host's AI-facing resources (the MCP server, the REST API, llms.txt, the recipe pages) in one machine-readable catalog. ARD v0.9 draft (Linux Foundation WG, 2026-06); media type `application/ai-catalog+json`. Also served on the apex: https://relaystation.ai/.well-known/ai-catalog.json.","security":[],"responses":{"200":{"description":"The ai-catalog manifest (`application/ai-catalog+json`)."}},"operationId":"getAiCatalogManifest"}},"/mcp":{"post":{"tags":["discovery"],"summary":"MCP transport","description":"JSON-RPC over HTTP. Streamable-HTTP MCP server. Methods: `initialize`, `tools/list`, `tools/call`. Auth flows through the same modes as /v1/*: API key, x402, wallet JWT. Brief 150 — URL-parameter fallback: `?key=rs_live_<key>` is accepted when no `Authorization` header is present (for clients like Cowork that only accept a server URL with no Bearer-token field). Bearer header takes precedence; a Bearer-present-but-invalid request is rejected without consulting `?key=`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"JSON-RPC result."},"503":{"description":"MCP disabled."}},"operationId":"mcpTransport"}},"/v1/auth/challenge":{"get":{"tags":["auth"],"summary":"Wallet challenge","description":"Issue a wallet-sign-in nonce + EIP-191 message.","security":[],"parameters":[{"name":"wallet","in":"query","required":true,"schema":{"type":"string","pattern":"^0x[a-fA-F0-9]{40}$"}}],"responses":{"200":{"description":"Challenge."}},"operationId":"getWalletChallenge"}},"/v1/auth/verify":{"post":{"tags":["auth"],"summary":"Verify signature","description":"Verify EIP-191 signature and mint a wallet JWT.","security":[],"responses":{"200":{"description":"JWT minted."}},"operationId":"verifyWalletSignature"}},"/v1/webhooks":{"post":{"tags":["webhooks"],"summary":"Register","description":"Register a webhook (returns secret ONCE).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"201":{"description":"Webhook registered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Webhook"}}}}},"operationId":"webhook_register"},"get":{"tags":["webhooks"],"summary":"List","description":"List webhook registrations.","security":[{"apiKey":[]},{"walletJwt":[]}],"responses":{"200":{"description":"Webhook list."}},"operationId":"listWebhooks"}},"/v1/webhooks/events":{"get":{"tags":["webhooks"],"summary":"Event registry","description":"The live event-type allowlist a webhook can subscribe to (the same set returned in a 400 INVALID_EVENTS response). PUBLIC — discovery, no auth, no per-account variation.","security":[],"responses":{"200":{"description":"Event registry — `{ events: string[] }`.","content":{"application/json":{"schema":{"type":"object","required":["events"],"properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEventName"}}}}}}}},"operationId":"listWebhookEvents"}},"/v1/webhooks/{id}":{"get":{"tags":["webhooks"],"summary":"Get","description":"Webhook detail (no secret).","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Webhook."}},"operationId":"getWebhook"},"put":{"tags":["webhooks"],"summary":"Update","description":"Update webhook (active / events / url / description).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Updated."}},"operationId":"updateWebhook"},"delete":{"tags":["webhooks"],"summary":"Delete","description":"Delete webhook (deliveries kept for audit).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Deleted."}},"operationId":"deleteWebhook"}},"/v1/webhooks/{id}/deliveries":{"get":{"tags":["webhooks"],"summary":"Delivery history","description":"Paginated delivery history.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}},{"name":"status","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Delivery page."}},"operationId":"listWebhookDeliveries"}},"/v1/account":{"get":{"tags":["account"],"summary":"Get account","description":"Customer profile + balance.","security":[{"apiKey":[]},{"walletJwt":[]}],"responses":{"200":{"description":"Account info."}},"operationId":"getAccount"}},"/v1/account/ledger":{"get":{"tags":["account"],"summary":"List ledger entries","description":"Paginated transaction ledger for the calling customer (credits + debits, newest first). The authoritative source for the customer's balance history. Query: limit (default 50, max 200), offset. The MCP equivalent is the `transactions` tool.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0}}],"responses":{"200":{"description":"{ data: LedgerEntry[], pagination }."},"401":{"description":"UNAUTHENTICATED."}},"operationId":"listAccountLedger"}},"/v1/account/products/{product}/enable":{"post":{"tags":["account"],"summary":"Enable product","description":"Enable a Relaystation-family product for the calling customer. Dashboard/interactive auth (rs_session cookie OR wallet-JWT); not an x402-payable route. Idempotent — repeated enables return the same shape. No product slugs are registered yet (KNOWN_PRODUCTS is empty); the endpoint stays as a chassis primitive ready for the first V3-native product.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"product","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Product enabled."}},"operationId":"enableAccountProduct"}},"/v1/account/products":{"get":{"tags":["account"],"summary":"List enrollments","description":"Enrollment list for the calling customer. Returns the products this customer is enrolled in. Accepts API-key, rs_session, or wallet-JWT. Rejects x402 with X402_NOT_APPLICABLE (lodestone callers per D43 never enroll).","security":[{"apiKey":[]},{"walletJwt":[]}],"responses":{"200":{"description":"Enrollment list (may be empty)."},"400":{"description":"X402_NOT_APPLICABLE."},"401":{"description":"MISSING_AUTH."}},"operationId":"listAccountProducts"}},"/v1/baton/system/info":{"get":{"tags":["discovery"],"summary":"System info","description":"Agent-facing system descriptor (the five-group model, preconfigs, payment modes, merchant wallet, endpoint roster).","security":[],"responses":{"200":{"description":"System info.","content":{"application/json":{"schema":{"type":"object"}}}}},"operationId":"getBatonSystemInfo"}},"/v1/baton/system/egress-ips":{"get":{"tags":["discovery"],"summary":"Egress IPs","description":"Outbound webhook delivery egress IPs (currently dynamic; HMAC-verify advisory in body).","security":[],"responses":{"200":{"description":"Egress IP advisory.","content":{"application/json":{"schema":{"type":"object"}}}}},"operationId":"getBatonEgressIps"}},"/v1/baton/prices":{"get":{"tags":["baton-pricing"],"summary":"Preconfigs","description":"The named preconfigs and their default shapes (resource tuple + headlineCapacity framing) — fast-start prefills, not priced SKUs.","security":[],"responses":{"200":{"description":"Preconfig list."}},"operationId":"baton_prices"}},"/v1/baton/quote":{"post":{"tags":["baton-pricing"],"summary":"Quote","description":"Public price-of-record for any baton shape (a preconfig ref or a custom shape + optional flags). Engine-quoted; never charges.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["preset"],"description":"The price-relevant subset of the create body. Same branch rule: tier \"custom\" (or tier omitted) requires customShape; bundled tiers must not supply one.","properties":{"preset":{"type":"string","enum":["drop","pass","scratchpad","checkpoint","ledger"],"description":"Named preconfig prefill. The LIVE active set is GET /v1/baton/prices."},"tier":{"type":"string","enum":["femto","pico","nano","micro","standard","big","custom"],"description":"Bundled resource tier, or \"custom\" (requires customShape)."},"customShape":{"type":"object","required":["sizeBytes","durationSeconds","maxEgressBytes"],"properties":{"sizeBytes":{"description":"Capacity in bytes (number or stringified bigint)."},"durationSeconds":{"description":"Lifetime in seconds (number or stringified bigint)."},"maxEgressBytes":{"description":"Egress budget in bytes (number or stringified bigint)."},"writesAllowed":{"type":"integer","minimum":0},"readsAllowed":{"type":"integer","minimum":0}}},"flags":{"type":"object","properties":{"hashChaining":{"type":"boolean"},"witness":{"type":"boolean"}},"description":"Echoed on bundle orders (catalog price is the total); priced on custom shapes."}}}}}},"responses":{"200":{"description":"Quote."},"404":{"description":"Preconfig not found."},"422":{"description":"Flag dependency unmet."}},"operationId":"quote_baton"}},"/v1/storage/drop":{"post":{"tags":["baton"],"summary":"Create a drop baton","description":"Store a file and get a durable, shareable URL — no account, no signup; the wallet is the identity. From $0.001; tiers by size, lifetime, egress. Part of Relaystation: 155 agent tools on one balance — storage, e-sign, KYC, PDF/data/media, sub-cent LLM. relaystation.ai\n\nStore a file (single object, multi-read until expiry). Preset-fixed alias of POST /v1/baton — the route injects preset:\"drop\" (overriding any body value) and delegates to the same create handler, so body shape, billing, and the x402 path are identical. Billable — the engine quote of the shape.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Same body as POST /v1/baton (tier, customShape, content, tokens, flags, …); the preset field is fixed by the route.","properties":{"tier":{"type":"string","enum":["femto","pico","nano","micro","standard","big","custom"],"description":"Bundled resource tier, or \"custom\" (requires customShape)."}}}}}},"responses":{"201":{"description":"The created baton."},"402":{"description":"Payment required (x402 challenge)."},"422":{"description":"Invalid shape."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"createDropBaton"}},"/v1/storage/pass":{"post":{"tags":["baton"],"summary":"Create a pass baton","description":"Hand a file to another agent or person — scoped, read-capped share tokens that burn once consumed. From $0.001; no account for the recipient. Part of Relaystation: 155 agent tools on one balance — storage, e-sign, KYC, PDF/data/media, sub-cent LLM. relaystation.ai\n\nShare a file (single object + a read-capped token; burns once consumed). Preset-fixed alias of POST /v1/baton — the route injects preset:\"pass\" (overriding any body value) and delegates to the same create handler, so body shape, billing, and the x402 path are identical. Billable — the engine quote of the shape.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Same body as POST /v1/baton (tier, customShape, content, tokens, flags, …); the preset field is fixed by the route.","properties":{"tier":{"type":"string","enum":["femto","pico","nano","micro","standard","big","custom"],"description":"Bundled resource tier, or \"custom\" (requires customShape)."}}}}}},"responses":{"201":{"description":"The created baton."},"402":{"description":"Payment required (x402 challenge)."},"422":{"description":"Invalid shape."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"createPassBaton"}},"/v1/storage/scratchpad":{"post":{"tags":["baton"],"summary":"Create a scratchpad baton","description":"Shared working memory for agents — append, read, and collaborate on a durable scratchpad with per-collaborator tokens; humans get an online editor. From $0.001. Part of Relaystation: 155 agent tools on one balance — storage, e-sign, KYC, PDF/data/media, sub-cent LLM. relaystation.ai\n\nCollaborate (append log + collaborator tokens). Preset-fixed alias of POST /v1/baton — the route injects preset:\"scratchpad\" (overriding any body value) and delegates to the same create handler, so body shape, billing, and the x402 path are identical. Billable — the engine quote of the shape.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Same body as POST /v1/baton (tier, customShape, content, tokens, flags, …); the preset field is fixed by the route.","properties":{"tier":{"type":"string","enum":["femto","pico","nano","micro","standard","big","custom"],"description":"Bundled resource tier, or \"custom\" (requires customShape)."}}}}}},"responses":{"201":{"description":"The created baton."},"402":{"description":"Payment required (x402 challenge)."},"422":{"description":"Invalid shape."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"createScratchpadBaton"}},"/v1/storage/checkpoint":{"post":{"tags":["baton"],"summary":"Create a checkpoint baton","description":"Save and restore agent state — durable snapshots you overwrite as you go and resume from anywhere. From $0.001, metered by size, lifetime, egress; no account, the wallet is the identity. Part of Relaystation: 155 agent tools on one balance — relaystation.ai\n\nSave state (overwrite; replace the snapshot as you go). Preset-fixed alias of POST /v1/baton — the route injects preset:\"checkpoint\" (overriding any body value) and delegates to the same create handler, so body shape, billing, and the x402 path are identical. Billable — the engine quote of the shape.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Same body as POST /v1/baton (tier, customShape, content, tokens, flags, …); the preset field is fixed by the route.","properties":{"tier":{"type":"string","enum":["femto","pico","nano","micro","standard","big","custom"],"description":"Bundled resource tier, or \"custom\" (requires customShape)."}}}}}},"responses":{"201":{"description":"The created baton."},"402":{"description":"Payment required (x402 challenge)."},"422":{"description":"Invalid shape."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"createCheckpointBaton"}},"/v1/storage/ledger":{"post":{"tags":["baton"],"summary":"Create a ledger baton","description":"Audited ledger (append, hash-chained; tamper-evident; auto-witnessed). Preset-fixed alias of POST /v1/baton — the route injects preset:\"ledger\" (overriding any body value) and delegates to the same create handler, so body shape, billing, and the x402 path are identical. Billable — the engine quote of the shape.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Same body as POST /v1/baton (tier, customShape, content, tokens, flags, …); the preset field is fixed by the route.","properties":{"tier":{"type":"string","enum":["femto","pico","nano","micro","standard","big","custom"],"description":"Bundled resource tier, or \"custom\" (requires customShape)."}}}}}},"responses":{"201":{"description":"The created baton."},"402":{"description":"Payment required (x402 challenge)."},"422":{"description":"Invalid shape."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"createLedgerBaton"}},"/v1/baton":{"post":{"tags":["baton"],"summary":"Create","description":"A baton is a durable storage object agents create, append, read, and cryptographically prove — then hand off to other agents or people. Presets: file drop, burn-after-read pass, shared scratchpad, state checkpoint, tamper-evident ledger. Metered by bytes, time, egress, writes; price frozen at purchase. 155 tools, one balance — relaystation.ai\n\nCreate a baton. Billable — the engine quote of the shape (a preconfig ref or a custom shape), floored at the network minimum.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["preset"],"description":"A preconfig ref (preset + tier) or a custom shape (tier \"custom\" or tier omitted + customShape). Bundled tiers must not supply customShape.","properties":{"preset":{"type":"string","enum":["drop","pass","scratchpad","checkpoint","ledger","witness"],"description":"Named preconfig prefill. The LIVE active set is GET /v1/baton/prices."},"tier":{"type":"string","enum":["femto","pico","nano","micro","standard","big","custom"],"description":"Bundled resource tier, or \"custom\" (requires customShape)."},"customShape":{"type":"object","required":["sizeBytes","durationSeconds","maxEgressBytes"],"properties":{"sizeBytes":{"description":"Capacity in bytes (number or stringified bigint)."},"durationSeconds":{"description":"Lifetime in seconds (number or stringified bigint)."},"maxEgressBytes":{"description":"Egress budget in bytes (number or stringified bigint)."},"writesAllowed":{"type":"integer","minimum":0},"readsAllowed":{"type":"integer","minimum":0}}},"contentType":{"type":"string","maxLength":255},"contentEncoding":{"type":"string","enum":["utf-8","base64","binary"]},"name":{"type":"string","maxLength":255},"description":{"type":"string","maxLength":2000},"tags":{"type":"array","items":{"type":"string","maxLength":64},"maxItems":32},"content":{"type":"string","description":"Inline initial content (base64-encoded when contentEncoding is base64/binary). Omit for an empty baton."},"requestPresignedUpload":{"type":"boolean","description":"Mint a presigned-PUT URL for an out-of-band initial upload instead of inline content."},"acl":{"type":"object","additionalProperties":{"type":"string","enum":["editor","reader"]},"description":"Collaborator ACL keyed by customer UUID."},"eventTriggers":{"type":"array","description":"Webhook event triggers; each { url, events[], description? }. The secret is server-minted and returned ONCE.","items":{"type":"object"}},"tokens":{"type":"array","description":"Create-time collaborator tokens; each entry follows the POST /v1/baton/{id}/tokens body.","items":{"type":"object"}},"flags":{"type":"object","properties":{"hashChaining":{"type":"boolean"},"witness":{"type":"boolean"}},"description":"flags.hashChaining: true auto-commits a prepaid witness (an explicit witness: false alongside it → 422 FLAG_DEPENDENCY_UNMET)."},"onExpiration":{"type":"string","enum":["hard_delete","tombstone_then_delete"]},"deleteOnAllTokensConsumed":{"type":"boolean"},"schemaRef":{"type":"string","maxLength":255},"rateLimits":{"type":"object"}}}}}},"responses":{"201":{"description":"Baton created."},"402":{"description":"Payment required (x402 challenge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"create_baton"}},"/v1/baton/{id}":{"get":{"tags":["baton"],"summary":"Read","description":"Read a baton (owner-addressed by UUID, OR token-addressed by `tok_<43-char>`).","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Baton."},"410":{"description":"Content gone."}},"operationId":"read_baton"},"post":{"tags":["baton"],"summary":"Write","description":"Write/append (owner-addressed by UUID, OR token-addressed). Append on append / append-chained batons; overwrite on single-object / overwrite batons.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Entry appended."},"409":{"description":"Quota exhausted / baton frozen."}},"operationId":"append_to_baton"},"delete":{"tags":["baton"],"summary":"Delete","description":"Soft-delete; sweeper purges per on_expiration.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Delete scheduled."}},"operationId":"delete_baton"}},"/v1/baton/{id}/entries":{"get":{"tags":["baton"],"summary":"List entries","description":"Paginated entries (owner or token-addressed).","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}},{"name":"since","in":"query","schema":{"type":"string"}},{"name":"until","in":"query","schema":{"type":"string"}},{"name":"writer","in":"query","schema":{"type":"string"}},{"name":"type","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Entries page."}},"operationId":"list_entries"}},"/v1/baton/{id}/tail":{"get":{"tags":["baton"],"summary":"Tail","description":"Long-poll for new entries (owner or token-addressed).","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"New entries since `?since=`."}},"operationId":"tail_baton"}},"/v1/baton/{id}/add":{"post":{"tags":["baton"],"summary":"Add resources","description":"Buy more of the same preconfig (another tier of resources). Billable. Owner only.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["tier"],"properties":{"tier":{"type":"string","enum":["femto","pico","nano","micro","standard","big"],"description":"The tier bundle to add (the baton's frozen preset, at this tier's resources + price)."}}}}}},"responses":{"200":{"description":"Additive resources applied.","content":{"application/json":{"schema":{"type":"object","description":"The updated baton after the tier was added.","properties":{"id":{"type":"string","format":"uuid"},"preset":{"type":"string"},"tier":{"type":"string"},"sizeBytes":{"type":"string","description":"Total size quota after the add (micros/bytes as string)."},"expiresAt":{"type":"string","format":"date-time"},"egressBytes":{"type":"string"}},"additionalProperties":true}}}}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"add_baton"}},"/v1/baton/{id}/extend":{"post":{"tags":["baton"],"summary":"Extend","description":"Buy à-la-carte dimensions (size / duration / egress / writes). At least one required. addDurationSeconds pushes the expiry out (expiry = createdAt + total durationSeconds). Billable; engine-quoted. Owner only.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"At least one of the four must be provided.","properties":{"addDurationSeconds":{"type":"string","description":"Seconds of life to ADD (decimal string). Pushes the expiry out."},"addSizeBytes":{"type":"string","description":"Bytes of capacity to add (decimal string)."},"addEgressBytes":{"type":"string","description":"Egress (read) budget bytes to add (decimal string)."},"addWritesAllowed":{"type":"integer","minimum":0,"description":"Additional writes to allow."}}}}}},"responses":{"200":{"description":"Dimensions extended.","content":{"application/json":{"schema":{"type":"object","description":"The updated baton after the à-la-carte dimensions were applied.","properties":{"id":{"type":"string","format":"uuid"},"sizeBytes":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"},"egressBytes":{"type":"string"},"maxWrites":{"type":"integer"}},"additionalProperties":true}}}}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"extend_baton"}},"/v1/baton/{id}/meta":{"get":{"tags":["baton"],"summary":"Read metadata","description":"Read metadata + flags snapshot.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Metadata."}},"operationId":"baton_meta"},"post":{"tags":["baton"],"summary":"Update metadata","description":"Mutate metadata (allowlisted fields only).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Metadata updated."},"422":{"description":"INVALID_FIELD."}},"operationId":"updateBatonMeta"}},"/v1/baton/{id}/event-logs":{"get":{"tags":["baton"],"summary":"Audit log","description":"Paginated audit log.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}},{"name":"since","in":"query","schema":{"type":"string"}},{"name":"until","in":"query","schema":{"type":"string"}},{"name":"writer","in":"query","schema":{"type":"string"}},{"name":"type","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Audit log page."}},"operationId":"getBatonEventLogs"}},"/v1/baton/{id}/tokens":{"post":{"tags":["baton"],"summary":"Share a file or add a collaborator (mint an access token)","description":"Mint a collaborator token (read / write / read_write capability + per-token caps). Owner-only, free.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["read","write","read_write"]},"reads_allowed":{"type":["integer","null"],"minimum":0,"description":"Per-token read sub-cap; null/omitted = unlimited within the baton bundle."},"writes_allowed":{"type":["integer","null"],"minimum":0,"description":"Per-token write sub-cap; null/omitted = unlimited within the baton bundle."},"expires_at":{"type":["string","null"],"format":"date-time"},"require_fingerprint":{"type":"boolean"},"ip_allow_list":{"type":"array","items":{"type":"string","maxLength":64},"maxItems":64,"description":"IPv4 CIDRs only (an IPv6 CIDR → 422 IPV6_NOT_SUPPORTED)."}}}}}},"responses":{"201":{"description":"Token issued."}},"operationId":"mint_token"},"get":{"tags":["baton"],"summary":"List tokens","description":"List tokens (includes revoked).","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Token list."}},"operationId":"listBatonTokens"}},"/v1/baton/{id}/tokens/{tokenId}":{"post":{"tags":["baton"],"summary":"Modify or revoke","description":"Modify (mutable subset) OR revoke a token. Owner-only, free.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"tokenId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Token modified / revoked."}},"operationId":"revoke_token"}},"/v1/baton/{id}/trust/document-witness":{"post":{"tags":["baton-trust"],"summary":"Witness","description":"Freeze + sign the baton. Conditionally billable ($0.05 standalone OR free on prepaid path).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Baton witnessed."},"409":{"description":"ALREADY_WITNESSED / BATON_NOT_ACTIVE / CHAIN_NOT_INITIALIZED."},"422":{"description":"witness_key_not_configured."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"witness_baton"}},"/v1/baton/{id}/trust/verify-chain":{"post":{"tags":["baton-trust"],"summary":"Verify chain","description":"Verify chained-hash integrity (modes: links | full). FREE.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"mode":{"type":"string","enum":["links","full"],"description":"Default links. 'full' re-hashes entry content and is gated by the baton.trust.verify_chain_full_enabled tunable — 403 MODE_DISABLED when off."}}}}}},"responses":{"200":{"description":"Chain status."}},"operationId":"verifyBatonChain"}},"/v1/baton/{id}/trust/challenge":{"post":{"tags":["baton-trust"],"summary":"Challenge","description":"Match a candidate against a witnessed baton. PUBLIC, FREE, per-IP rate-limited.","security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Match result."},"429":{"description":"Rate-limited."}},"operationId":"challengeBatonProof"}},"/v1/baton/{id}/trust/proof":{"get":{"tags":["baton-trust"],"summary":"Get proof","description":"Retrieve the signed attestation. PUBLIC, FREE.","security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Proof envelope."},"409":{"description":"NOT_WITNESSED."}},"operationId":"getBatonProof"}},"/v1/cputools/upload-url":{"post":{"tags":["cputools"],"summary":"Mint a storage-ref upload URL","description":"Mint a customer-scoped presigned POST for the storage-ref input path — used to upload a file too large for the inline base64 field (> cputools.io.max_inline_bytes, 4 MiB) before passing { inputKey } to a PDF op. NOT billable (minting is free; the op that consumes the bytes bills). Auth: api-key / wallet-JWT / cookie, OR a verify-only x402 payment (the EIP-712 signature is verified but NOT settled — this lets an account-less x402 lodestone caller obtain a scoped key and transform files > 4 MB). The minted key is scoped to the resolved customer (scratch/<customerId>/<uuid>); a cputool only admits the caller's own keys. Returns an HTML-form-style multipart POST (Brief 149): the client POSTs multipart/form-data to `url` with every `fields` entry plus a `file` part — NOT a raw PUT. The upload is hard-capped at cputools.io.max_storage_ref_bytes (50 MiB) by the presigned-POST content-length-range.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"ext":{"type":"string","maxLength":12,"description":"Optional file extension, sanitized to [a-z0-9]{0,12}."},"contentType":{"type":"string","maxLength":255}}}}}},"responses":{"201":{"description":"{ url, fields, inputKey, expiresAt, maxBytes } — POST multipart/form-data (all fields + a `file` part) to `url`; then pass { inputKey } to a PDF op."},"401":{"description":"No account identity and no valid x402 signature."}},"operationId":"cputools_upload_url"}},"/v1/outputs/{key}":{"get":{"tags":["cputools"],"summary":"Re-fetch a stored op output","description":"Stable re-fetch for any binary op output (the `outputKey` an op returned when its output exceeded the inline threshold). The presigned `outputUrl` an op returns expires in ~1 h; this route re-mints a fresh one on demand for as long as the underlying scratch object lives (≤ 24 h bucket lifecycle), then 404s. NOT billable. Returns an output record (JSON) — programmatic-first, not the bytes: { outputKey, url (fresh presigned GET, ~1 h), sizeBytes, contentType, expiresAt } where `url` IS the immediate download. Auth = the same ownership gate as a storage-ref input: the key must live under the caller's scratch/<customerId>/ namespace (resolved via api-key / wallet-JWT / cookie OR a verify-only x402 signature). A no-identity caller, or a key under another tenant's namespace, gets 404 — never 403, never a cross-customer existence oracle.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string"},"description":"The full scratch output key (scratch/<customerId>/<uuid>.<ext>) — exactly the `outputKey` an op returned."}],"responses":{"200":{"description":"{ outputKey, url, sizeBytes, contentType, expiresAt } — `url` is a fresh presigned GET (~1 h) for the current object."},"404":{"description":"No such output under the caller's namespace, or the object has aged past the 24 h scratch lifecycle."}},"operationId":"getOutput"}},"/v1/pdf/merge":{"post":{"tags":["cputools"],"summary":"Merge PDFs","description":"$0.001/page. Combine any number of PDFs into one, in the order you choose. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nConcatenate 2+ PDFs into one. Body: { files: [<source>, …], filename? } where each <source> is { inline: <base64> } (≤ 4 MiB decoded) or { inputKey: <scratch key from upload-url> }. Billed per output page. Output: { output: { inline?: <base64> | outputKey + outputUrl (presigned GET, when > 4 MiB), sizeBytes, contentType } }.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Merged PDF (uniform JSON output envelope)."},"402":{"description":"Payment required (x402 challenge with the body-derived price)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"INPUT_TOO_LARGE (inline > 4 MiB) / INPUT_EMPTY / PDF_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pdf_merge"}},"/v1/pdf/split":{"post":{"tags":["cputools"],"summary":"Split a PDF","description":"$0.001/page. Split a PDF by page ranges or burst it into single pages. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nSplit one PDF into multiple. Body: { file: <source>, ranges?: \"1-3,5\", burst?: true }. `burst` (or no ranges) = one file per page; `ranges` = one file per comma segment. Billed per SOURCE page. Capped at cputools.pdf.split.max_outputs (100). Output: a manifest { files: [{ index, outputKey, outputUrl (presigned GET), pages, sizeBytes }, …] } — never inline.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Split manifest of presigned-GET refs."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"inputKey not owned by the caller."},"413":{"description":"Input exceeds max_storage_ref_bytes."},"422":{"description":"BAD_RANGE / TOO_MANY_OUTPUTS / PDF_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pdf_split"}},"/v1/pdf/rotate":{"post":{"tags":["cputools"],"summary":"Rotate pages","description":"$0.001/page. Rotate PDF pages — all or a selection, any multiple of 90°. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRotate pages by 90/180/270°. Body: { file: <source>, degrees: \"90\"|\"180\"|\"270\", pages?: \"all\"|\"1-3,5\" } (pages are 1-based; folds onto existing rotation). Billed per rotated page. Output: uniform JSON output envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","degrees"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"degrees":{"type":"string","enum":["90","180","270"]},"pages":{"type":"string","description":"1-based pages/ranges, \"all\" or \"1-3,5\" (default all)."}}}}}},"responses":{"200":{"description":"Rotated PDF."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"inputKey not owned by the caller."},"422":{"description":"BAD_RANGE / PDF_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pdf_rotate"}},"/v1/pdf/pages":{"post":{"tags":["cputools"],"summary":"Delete / reorder / insert pages","description":"$0.001/page. Delete, reorder, or insert PDF pages — restructure without desktop software. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nEdit the page set. Body is a discriminated union on `action`: { action:\"delete\", file, pages:\"2,4-5\" } | { action:\"reorder\", file, order:[3,1,2] } | { action:\"insert\", file, insert:<source>, at:1 }. All page references are 1-based; insert `at`=N+1 appends. Billed per RESULT page. Output: uniform JSON output envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","required":["action","file","pages"],"properties":{"action":{"type":"string","enum":["delete"]},"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"pages":{"type":"string","description":"1-based pages/ranges to delete, e.g. \"2,4-5\"."}}},{"type":"object","required":["action","file","order"],"properties":{"action":{"type":"string","enum":["reorder"]},"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"order":{"type":"array","items":{"type":"integer","minimum":1},"minItems":1,"description":"1-based page order, e.g. [3,1,2]."}}},{"type":"object","required":["action","file","insert","at"],"properties":{"action":{"type":"string","enum":["insert"]},"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"insert":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"at":{"type":"integer","minimum":1,"description":"1-based insert position; at = N+1 appends."}}}]}}}},"responses":{"200":{"description":"Edited PDF."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"inputKey not owned by the caller."},"422":{"description":"BAD_RANGE / PDF_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pdf_pages"}},"/v1/pdf/extract-text":{"post":{"tags":["cputools"],"summary":"Extract text","description":"$0.002/page. Pull text and structure out of a PDF — clean input for LLMs and search. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nExtract text via unpdf (a serverless pdf.js build). Body: { file: <source>, pages?: \"1-3,5\" }. Billed per source page. Output: JSON { totalPages, pages: [{ page, text }, …], text } — no bytes.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Extracted per-page + merged text."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"inputKey not owned by the caller."},"422":{"description":"BAD_RANGE / PDF_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pdf_extract_text"}},"/v1/pdf/metadata":{"post":{"tags":["cputools"],"summary":"Get / set metadata","description":"$0.001/call. Read or set PDF document metadata — title, author, keywords, dates. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRead or write document metadata. Body: { file: <source>, set?: { title?, author?, subject?, keywords?, creator?, producer? } }. No `set` → returns the metadata JSON; with `set` → returns the modified PDF (uniform JSON output envelope). Flat-billed (cputools.price.pdf.metadata.flat_micros).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Metadata JSON (get) or modified PDF (set)."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"inputKey not owned by the caller."},"422":{"description":"PDF_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pdf_metadata"}},"/v1/pdf/watermark":{"post":{"tags":["cputools"],"summary":"Watermark / stamp","description":"$0.001/page. Stamp text watermarks across PDF pages — DRAFT, CONFIDENTIAL, or your own. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nStamp text on pages. Body: { file: <source>, text, opacity?:0-1, size?:int, color?:\"#RRGGBB\", position?:\"center\"|\"top-left\"|\"top-right\"|\"bottom-left\"|\"bottom-right\", pages?:\"all\"|\"1-3\" }. Billed per stamped page. Output: uniform JSON output envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","text"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"text":{"type":"string","minLength":1,"maxLength":500},"opacity":{"type":"number","minimum":0,"maximum":1},"size":{"type":"integer","minimum":1,"maximum":400},"color":{"type":"string","pattern":"^#?[0-9a-fA-F]{6}$"},"position":{"type":"string","enum":["center","top-left","top-right","bottom-left","bottom-right"]},"pages":{"type":"string","description":"1-based pages/ranges, \"all\" or \"1-3\" (default all)."}}}}}},"responses":{"200":{"description":"Watermarked PDF."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"inputKey not owned by the caller."},"422":{"description":"BAD_RANGE / PDF_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pdf_watermark"}},"/v1/pdf/form":{"post":{"tags":["cputools"],"summary":"Form-fill / flatten","description":"$0.001/page. Fill or flatten PDF forms programmatically — field values in, finished PDF out. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nFill AcroForm fields and/or flatten. Body: { file: <source>, fields?: { <name>: <string|boolean> }, flatten?: bool } — string → text/dropdown/radio, boolean → checkbox. Billed per page. Output: uniform JSON output envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Filled/flattened PDF."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"inputKey not owned by the caller."},"422":{"description":"FIELD_NOT_FOUND / FIELD_TYPE_MISMATCH / PDF_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pdf_form"}},"/v1/pdf/encrypt":{"post":{"tags":["cputools"],"summary":"Password-protect a PDF","description":"$0.001/page. Password-protect a PDF with 256-bit AES and set permissions. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nEncrypt (password-protect) a PDF via qpdf (256-bit AES) in the cputools worker. Body: { file: <inline base64 | { inputKey }>, userPassword?, ownerPassword?, print?, copy?, modify?, annotate?, fillForms?, extract?, assemble?, printHighRes? } (at least one password; neither may start with \"-\"). Standard PDF model: an ownerPassword alone leaves the open (user) password EMPTY — anyone opens, permissions are owner-gated. The optional permission booleans (the canonical PDF permission octet) map to qpdf restriction flags; an ABSENT boolean means the qpdf default (allowed). print + printHighRes fold into one print level (none/low/full). `copy` is the live text/graphics extraction control; `extract` is the accessibility-extraction bit, which PDF 2.0 / 256-bit encryption deprecated and always grants (so extract:false may be a no-op). Billed per page. Output: uniform JSON envelope (the encrypted PDF). Passwords ride only in the IAM-gated worker invoke — never logged.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"userPassword":{"type":"string","minLength":1,"maxLength":500,"description":"The open password. Must not start with \"-\"."},"ownerPassword":{"type":"string","minLength":1,"maxLength":500,"description":"The permissions password. Must not start with \"-\"."},"print":{"type":"boolean","description":"Allow printing (absent = allowed). Pair with printHighRes for the print level."},"copy":{"type":"boolean","description":"Allow text/graphics extraction — the live \"copy\" permission (absent = allowed)."},"modify":{"type":"boolean","description":"Allow document modification (absent = allowed)."},"annotate":{"type":"boolean","description":"Allow annotation (absent = allowed)."},"fillForms":{"type":"boolean","description":"Allow filling in form fields (absent = allowed)."},"extract":{"type":"boolean","description":"Allow extraction for accessibility (absent = allowed). NOTE: PDF 2.0 / 256-bit encryption deprecated this bit and always grants it — may be a no-op; use `copy` to control extraction."},"assemble":{"type":"boolean","description":"Allow document assembly — insert/delete/rotate pages (absent = allowed)."},"printHighRes":{"type":"boolean","description":"Allow high-resolution printing (absent = allowed). With print:true, printHighRes:false restricts to low-res printing."}}}}}},"responses":{"200":{"description":"Encrypted PDF (uniform JSON output envelope)."},"402":{"description":"Payment required (x402 challenge with the per-page price)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"PDF_PARSE_FAILED / BAD_REQUEST (no password)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pdf_encrypt"}},"/v1/pdf/compress":{"post":{"tags":["cputools"],"summary":"Compress a PDF","description":"$0.001/page. Recompress and linearize a PDF — smaller files, fast web view. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRecompress a PDF via qpdf (object + content streams, flate) in the cputools worker. Body: { file: <source>, linearize?: bool } (linearize = web \"fast view\" / byte-range streaming — there is no separate pdf/linearize op). Billed PER PAGE — not by savings; the compression ratio varies by document (an already-optimized PDF may not shrink). Output: uniform JSON envelope (the recompressed PDF).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"linearize":{"type":"boolean","description":"Web-optimize (linearize) the output for byte-range \"fast view\" streaming (default false)."}}}}}},"responses":{"200":{"description":"Recompressed PDF (uniform JSON output envelope)."},"402":{"description":"Payment required (x402 challenge with the per-page price)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"PDF_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pdf_compress"}},"/v1/pdf/render":{"post":{"tags":["cputools"],"summary":"Render PDF pages to images","description":"$0.001/page. Rasterize PDF pages to PNG or JPEG — previews, thumbnails, vision-model input. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRasterize PDF pages to PNG/JPEG via poppler pdftoppm in the cputools worker. Body: { file: <source>, pages?: \"1-3,5\" (default all), dpi?: int ≤ cputools.render.max_dpi (300), format?: \"png\"|\"jpeg\" }. Non-embedded standard-14 fonts render via a vendored DejaVu Sans + fontconfig. Billed per OUTPUT page. Capped at cputools.render.max_pages (40). Output: a manifest { images: [{ index, page, outputKey, outputUrl (presigned GET) }, …] } — never inline.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"pages":{"type":"string","description":"1-based pages/ranges, e.g. \"1-3,5\" (default all)."},"dpi":{"type":"integer","minimum":36,"maximum":600,"description":"Render DPI; runtime-capped at cputools.render.max_dpi."},"format":{"type":"string","enum":["png","jpeg"]}}}}}},"responses":{"200":{"description":"Render manifest of presigned-GET image refs."},"402":{"description":"Payment required (x402 challenge with the per-page price)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"BAD_RANGE / RENDER_TOO_MANY_PAGES / DPI_TOO_HIGH / PDF_PARSE_FAILED / PDF_ENCRYPTED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pdf_render"}},"/v1/pdf/ocr":{"post":{"tags":["cputools"],"summary":"OCR a PDF","description":"$0.003/page. Extract text from a scanned PDF (Tesseract) — make paper searchable data. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nExtract text from a scanned/image PDF via pdftoppm → tesseract (LSTM) in the cputools worker. Body: { file: <source>, pages?: \"1-3,5\" (default all), lang?: <roster tag, default \"eng\"> }. SYNCHRONOUS and capped at cputools.ocr.max_sync_pages (5) — the call must clear the 30 s API-Gateway timeout; larger asynchronous jobs are coming. Billed per SOURCE page (premium tier). Output: JSON { pages: [{ page, text }, …], text } — no bytes.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"pages":{"type":"string","description":"1-based pages/ranges, e.g. \"1-3,5\" (default all)."},"lang":{"type":"string","enum":["eng","spa","fra","deu","ita","por","nld","pol","rus","chi_sim"],"description":"OCR language tag (default \"eng\"). The live roster is the operator-tunable cputools.ocr.langs (deployed traineddata: eng, spa, fra, deu, ita, por, nld, pol, rus, chi_sim); an off-roster lang is a free 422 UNSUPPORTED_LANG naming the roster."}}}}}},"responses":{"200":{"description":"Per-page + merged OCR text."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"inputKey not owned by the caller."},"413":{"description":"Input exceeds max_storage_ref_bytes."},"422":{"description":"BAD_RANGE / OCR_TOO_MANY_PAGES_SYNC / UNSUPPORTED_LANG (free, names the roster) / PDF_PARSE_FAILED / PDF_ENCRYPTED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pdf_ocr"}},"/v1/pdf/ocr-searchable":{"post":{"tags":["cputools"],"summary":"Make a scanned PDF searchable (OCR text layer)","description":"$0.004/page. Add an invisible OCR text layer to a scanned PDF — looks identical, fully searchable. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nConvert a scanned/image PDF into a SEARCHABLE PDF: per page, pdftoppm rasterizes, tesseract emits a page PDF with the image + an invisible text layer, and qpdf merges them. Body: { file: <source>, pages?: \"1-3,5\" (default all), lang?: <roster tag, default \"eng\"> }. SYNCHRONOUS and capped at cputools.ocr.max_sync_pages (5). Billed per SOURCE page at cputools.price.pdf.ocr-searchable.per_page_micros — a premium over plain OCR (it runs OCR plus PDF assembly). Output: the uniform JSON envelope, ALWAYS as a storage ref ({ output: { outputKey, outputUrl (presigned GET), sizeBytes, contentType: \"application/pdf\", filename } }) — never inline, even for small outputs (the render convention).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"pages":{"type":"string","description":"1-based pages/ranges, e.g. \"1-3,5\" (default all)."},"lang":{"type":"string","enum":["eng","spa","fra","deu","ita","por","nld","pol","rus","chi_sim"],"description":"OCR language tag (default \"eng\"). The live roster is the operator-tunable cputools.ocr.langs (deployed traineddata: eng, spa, fra, deu, ita, por, nld, pol, rus, chi_sim); an off-roster lang is a free 422 UNSUPPORTED_LANG naming the roster."}}}}}},"responses":{"200":{"description":"Searchable PDF (storage-ref output envelope — outputKey + presigned outputUrl)."},"402":{"description":"Payment required (x402 challenge with the per-page price)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"BAD_RANGE / OCR_TOO_MANY_PAGES_SYNC / UNSUPPORTED_LANG (free, names the roster) / PDF_PARSE_FAILED / PDF_ENCRYPTED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pdf_ocr_searchable"}},"/v1/image/ocr":{"post":{"tags":["cputools"],"summary":"OCR an image","description":"$0.003/call. Extract text from an image — Tesseract, 10 languages, plain text out. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nExtract text from a raster image (PNG/JPEG/TIFF/BMP/WebP — anything leptonica reads) via tesseract (LSTM) in the cputools worker. Body: { file: <source>, lang?: <roster tag, default \"eng\">, tsv?: bool }. With tsv: true the response also carries tesseract's TSV (per-word bounding boxes + confidences). No pre-charge image-format guard — tesseract is the parser; a non-image input is a refunded 422 IMAGE_PARSE_FAILED. FLAT-billed (cputools.price.image.ocr.flat_micros). Output: JSON { text, tsv? } — no bytes.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"lang":{"type":"string","enum":["eng","spa","fra","deu","ita","por","nld","pol","rus","chi_sim"],"description":"OCR language tag (default \"eng\"). The live roster is the operator-tunable cputools.ocr.langs (deployed traineddata: eng, spa, fra, deu, ita, por, nld, pol, rus, chi_sim); an off-roster lang is a free 422 UNSUPPORTED_LANG naming the roster."},"tsv":{"type":"boolean","description":"Also return tesseract TSV (per-word boxes + confidences)."}}}}}},"responses":{"200":{"description":"Extracted text (+ optional TSV)."},"402":{"description":"Payment required (x402 challenge with the flat price)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"UNSUPPORTED_LANG (free, names the roster) / IMAGE_PARSE_FAILED (input is not a readable raster image — refunded)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"image_ocr"}},"/v1/pdf/repair":{"post":{"tags":["cputools"],"summary":"Repair a damaged PDF","description":"$0.001/page. Diagnose and rewrite a damaged PDF — recover files other tools reject. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nDiagnose and rewrite a damaged PDF via qpdf in the cputools worker: `qpdf --check` produces a findings report (exit 0 clean / 2 errors / 3 warnings), then a full re-serialization reconstructs the xref and normalizes structure. Body: { file: <source> }. Billed per page (compress's grain — same engine, structural rewrite). Output: { output: { outputKey, outputUrl (presigned GET), sizeBytes, contentType: \"application/pdf\", filename }, repair: { exitCode, findings: [...] } } — the repaired PDF is ALWAYS a storage ref. A PDF too damaged to parse at all is a 422 PDF_PARSE_FAILED.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."}}}}}},"responses":{"200":{"description":"Repaired PDF (storage-ref output envelope) + the qpdf --check findings report."},"402":{"description":"Payment required (x402 challenge with the per-page price)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"PDF_PARSE_FAILED (unparseable beyond repair) / PDF_ENCRYPTED / WRONG_PASSWORD."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pdf_repair"}},"/v1/pdf/verify-signatures":{"post":{"tags":["cputools"],"summary":"Verify PDF digital signatures (structural)","description":"$0.001/call. Verify a PDF's digital signatures — structural + digest checks, results as JSON. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nInspect a PDF's digital signatures via poppler pdfsig in the cputools worker. Body: { file: <source> }. HONEST SEMANTICS: this is STRUCTURAL verification + digest intactness — signature present, the signed byte range unmodified (signatureValidation), signer identity fields surfaced (common name, DN, signing time, hash algorithm). Certificate-chain trust is NOT evaluated — no CA store is deployed, so certificateValidation typically reports the chain could not be checked. FLAT-billed (cputools.price.pdf.verify-signatures.flat_micros). Output: JSON { signatures: [{ index, signerCommonName?, signerDistinguishedName?, signingTime?, hashAlgorithm?, signatureType?, signatureValidation?, certificateValidation? }, …], signatureCount } — an UNSIGNED document returns signatures: [] (not an error). Pairs with the e-sign product (esigndoc) as the verification half.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."}}}}}},"responses":{"200":{"description":"Signature report (possibly empty — unsigned is a normal outcome)."},"402":{"description":"Payment required (x402 challenge with the flat price)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"PDF_PARSE_FAILED (input is not a usable PDF — refunded)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pdf_verify_signatures"}},"/v1/pdf/from-html":{"post":{"tags":["cputools"],"summary":"Generate a PDF from HTML (headless Chromium)","description":"$0.003/call. Render HTML to PDF — sandboxed headless Chromium, print-quality output. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRender caller-supplied HTML to a PDF on headless Chromium (a dedicated render worker) — invoices, reports, contracts, certificates. Body: { html: string, options?: { format?: \"A4\"|\"Letter\"|\"Legal\"|\"A3\"|\"A5\"|\"Tabloid\"|\"Ledger\", landscape?, printBackground?, scale?: 0.1–2, margin?: { top, right, bottom, left }, displayHeaderFooter?, headerTemplate?, footerTemplate?, pageRanges?: \"1-5, 8\", tagged? } }. Header/footer templates are HTML print furniture (use the `date`/`title`/`url`/`pageNumber`/`totalPages` classes); supplying one implies displayHeaderFooter unless you set it false. `pageRanges` prints a subset; `tagged` (default true) emits an accessible/tagged PDF. SANDBOXED: the renderer has NO network and NO JavaScript — every request whose scheme is not `data:` is aborted (external URLs, `<script>`, `@import`, `file://` all blocked), and the header/footer templates render under the same wall — so ALL assets must be inlined as `data:` URIs / inline `<style>` (the contract). Billed FLAT per render (`cputools.price.pdf.from-html.flat_micros`) — the output page count is not known until after the render, so it cannot drive an x402 pre-auth. Output: uniform JSON envelope (application/pdf) + the page count.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["html"],"properties":{"html":{"type":"string","minLength":1,"description":"The HTML to render; all assets inlined as data: URIs / inline <style>."},"options":{"type":"object","properties":{"format":{"type":"string","enum":["A4","Letter","Legal","A3","A5","Tabloid","Ledger"]},"landscape":{"type":"boolean"},"printBackground":{"type":"boolean"},"scale":{"type":"number","minimum":0.1,"maximum":2},"margin":{"type":"object","properties":{"top":{"type":"string","description":"CSS size, e.g. \"10mm\"."},"right":{"type":"string"},"bottom":{"type":"string"},"left":{"type":"string"}}},"displayHeaderFooter":{"type":"boolean","description":"Render the header/footer templates (default false; auto-enabled when a template is supplied)."},"headerTemplate":{"type":"string","maxLength":50000,"description":"HTML print header; use the date/title/url/pageNumber/totalPages classes. Rendered under the data:-only sandbox."},"footerTemplate":{"type":"string","maxLength":50000,"description":"HTML print footer; same classes/sandbox as headerTemplate."},"pageRanges":{"type":"string","maxLength":255,"description":"Page subset to print, e.g. \"1-5, 8\" (default: all pages)."},"tagged":{"type":"boolean","description":"Emit a tagged (accessible) PDF (default true)."}}}}}}}},"responses":{"200":{"description":"The rendered PDF (uniform JSON output envelope) + page count."},"402":{"description":"Payment required (x402 challenge with the flat render price)."},"422":{"description":"HTML_TOO_LARGE (over cputools.render.max_html_bytes) / BAD_REQUEST (invalid options)."},"500":{"description":"WORKER_RENDER_ERROR / WORKER_RENDER_TIMEOUT (refunded — the charge is reversed)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pdf_from_html"}},"/v1/pdf/images":{"post":{"tags":["cputools"],"summary":"Extract embedded images from a PDF","description":"$0.001/page. Extract every embedded image from a PDF as PNGs. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nExtract embedded images (unpdf/pdf.js → raw pixels → PNG via sharp). Body: { file, pages? (\"1-3,5\") }. Per-page billed (`cputools.price.pdf.images.per_page_micros`). Capped at 200 images (truncated flag). Output: JSON `{ images: [{ page, index, width, height, output }], count, truncated }`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Extracted images manifest."},"402":{"description":"Payment required."},"404":{"description":"inputKey not owned."},"422":{"description":"PDF_PARSE_FAILED / BAD_RANGE."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pdf_images"}},"/v1/pdf/diff":{"post":{"tags":["cputools"],"summary":"Text-compare two PDFs","description":"$0.001/page. Text-compare two PDFs into a unified diff — spot every change between versions. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nExtract text from two PDFs and produce a unified diff (unpdf + jsdiff). Body: { a, b, context? }. Per-page billed on a+b (`cputools.price.pdf.diff.per_page_micros`). Output: JSON `{ patch, changed }`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Unified patch."},"402":{"description":"Payment required."},"422":{"description":"PDF_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pdf_diff"}},"/v1/pdf/bookmarks":{"post":{"tags":["cputools"],"summary":"Read a PDF outline / bookmarks","description":"$0.001/call. Read a PDF's outline/bookmark tree as JSON — navigate structure before extracting. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRead the outline/bookmark tree (pdf.js getOutline). Body: { file }. FLAT-billed (`cputools.price.pdf.bookmarks.flat_micros`). READ-ONLY — setting an outline is deferred. Output: JSON `{ outline: [{ title, children }], count }`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Outline tree."},"402":{"description":"Payment required."},"422":{"description":"PDF_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pdf_bookmarks"}},"/v1/pdf/attachments":{"post":{"tags":["cputools"],"summary":"List / extract PDF attachments","description":"$0.0005/MB. List or extract files embedded inside a PDF — pull attachments out programmatically. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nList (and optionally extract) embedded file attachments (pdf.js getAttachments). Body: { file, extract? }. Billed PER MiB of output — `ceil(Σ output bytes / 1 MiB)` × `cputools.price.pdf.attachments.per_mb_micros`: extract:true meters the summed extracted attachment bytes (egress); the extract:false list path floors to 1 MiB (constant minimum ≈ flat). Output: JSON `{ attachments: [{ filename, size, output? }], count }`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Attachments manifest."},"402":{"description":"Payment required."},"422":{"description":"PDF_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pdf_attachments"}},"/v1/qr":{"post":{"tags":["cputools"],"summary":"Generate a QR code","description":"$0.0002/call. Generate QR codes as PNG or SVG — size, error-correction, and margin control. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nGenerate a QR code from a `data` string — no file input. Body: { data (≤ cputools.qr.max_data_len, 4096), format?: \"png\"|\"svg\" (default png), size?: 64-2048 (default 512), margin?: 0-16 (default 4), ecc?: \"L\"|\"M\"|\"Q\"|\"H\" (default M) }. FLAT price $0.0002 (cputools.qr.generate). Output: uniform JSON envelope { output: { inline: <base64 PNG | base64 SVG>, sizeBytes, contentType } } — always inline (QR images are small).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"string","minLength":1,"description":"The data to encode (≤ cputools.qr.max_data_len)."},"format":{"type":"string","enum":["png","svg"]},"size":{"type":"integer","minimum":64,"maximum":2048},"margin":{"type":"integer","minimum":0,"maximum":16},"ecc":{"type":"string","enum":["L","M","Q","H"],"description":"Error-correction level (default M)."}}}}}},"responses":{"200":{"description":"QR image (uniform JSON output envelope)."},"400":{"description":"VALIDATION_ERROR (empty data, bad ecc/size/format)."},"402":{"description":"Payment required (x402 challenge with the $0.0002 static price)."},"422":{"description":"DATA_TOO_LONG (data exceeds cputools.qr.max_data_len)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"qr_generate"}},"/v1/barcode":{"post":{"tags":["cputools"],"summary":"Generate a barcode","description":"$0.0002/call. Generate a print-ready 1D barcode (Code128, EAN, UPC & more) as PNG — set symbology, height, scale. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nGenerate a linear/2D barcode via bwip-js — no file input. The full documented engine roster (108 symbologies) is supported: EAN/UPC, Code 128/39/93, GS1 family (DataBar, GS1-128, composites), QR/Micro QR, Data Matrix, PDF417/MicroPDF417, Aztec, postal codes, ISBN/ISMN/ISSN, pharmacode, codabar, and more. Body: { data, symbology (the bcid — see the schema enum), format?: \"png\"|\"svg\" (default png), scale?: 1-10 (default 3), height?: 1-200 (default 10) }. Data the symbology cannot encode (bad check digit, wrong length, invalid charset) → 422 BARCODE_RENDER_FAILED naming the engine reason, charge reversed. FLAT price $0.0002 (cputools.barcode.generate). Output: uniform JSON envelope (always inline).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["data","symbology"],"properties":{"data":{"type":"string","minLength":1,"description":"The data to encode (per-symbology charset/length/checksum rules apply)."},"symbology":{"type":"string","enum":["ean5","ean2","ean13","ean8","upca","upce","isbn","ismn","issn","mands","code128","gs1-128","ean14","sscc18","code39","code39ext","code32","pzn","code93","code93ext","interleaved2of5","itf14","identcode","leitcode","databaromni","databarstacked","databarstackedomni","databartruncated","databarlimited","databarexpanded","databarexpandedstacked","gs1northamericancoupon","pharmacode","pharmacode2","code2of5","industrial2of5","iata2of5","matrix2of5","coop2of5","datalogic2of5","code11","bc412","rationalizedCodabar","onecode","postnet","planet","royalmail","auspost","kix","japanpost","msi","plessey","telepen","telepennumeric","posicode","codablockf","code16k","code49","channelcode","flattermarken","raw","daft","symbol","pdf417","pdf417compact","micropdf417","datamatrix","datamatrixrectangular","datamatrixrectangularextension","qrcode","swissqrcode","microqrcode","maxicode","azteccode","azteccodecompact","aztecrune","codeone","hanxin","dotcode","ultracode","gs1-cc","ean13composite","ean8composite","upcacomposite","upcecomposite","databaromnicomposite","databarstackedcomposite","databarstackedomnicomposite","databartruncatedcomposite","databarlimitedcomposite","databarexpandedcomposite","databarexpandedstackedcomposite","gs1-128composite","gs1datamatrix","gs1datamatrixrectangular","gs1dldatamatrix","gs1qrcode","gs1dlqrcode","gs1dotcode","hibccode39","hibccode128","hibcdatamatrix","hibcdatamatrixrectangular","hibcpdf417","hibcmicropdf417","hibcqrcode","hibccodablockf","hibcazteccode"],"description":"The bwip-js bcid."},"format":{"type":"string","enum":["png","svg"]},"scale":{"type":"integer","minimum":1,"maximum":10},"height":{"type":"integer","minimum":1,"maximum":200}}}}}},"responses":{"200":{"description":"Barcode image (uniform JSON output envelope)."},"400":{"description":"VALIDATION_ERROR."},"402":{"description":"Payment required (x402 challenge with the $0.0002 static price)."},"422":{"description":"UNSUPPORTED_SYMBOLOGY / DATA_TOO_LONG / BARCODE_RENDER_FAILED (data the symbology cannot encode — charge reversed)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"barcode_generate"}},"/v1/image/resize":{"post":{"tags":["cputools"],"summary":"Resize an image","description":"$0.0003/MP. Resize or fit images — animated GIF/WebP preserved with animated:true. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nResize via sharp. Body: { file: <inline base64 | { inputKey }>, width?, height?, fit?: \"cover\"|\"contain\"|\"fill\"|\"inside\"|\"outside\", animated?: bool } (at least one of width/height; 1-10000). With animated:true the input is read with sharp({animated:true}) so all frames of an animated GIF/WebP are resized + preserved (billed per-frame: pages × w · h). Billed per megapixel (`ceil(MP)` × `cputools.price.image.resize.per_mp_micros`). Output: uniform JSON envelope; output format follows the input.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"description":"At least one of width / height is required.","properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"width":{"type":"integer","minimum":1,"maximum":10000},"height":{"type":"integer","minimum":1,"maximum":10000},"fit":{"type":"string","enum":["cover","contain","fill","inside","outside"]},"animated":{"type":"boolean","description":"Preserve all frames of an animated GIF/WebP (billed per-frame)."}}}}}},"responses":{"200":{"description":"Resized image (uniform JSON output envelope)."},"400":{"description":"VALIDATION_ERROR (no dimension, out-of-range)."},"402":{"description":"Payment required (x402 challenge with the per-MP price)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"IMAGE_PARSE_FAILED / IMAGE_TOO_LARGE (> cputools.image.max_megapixels)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"image_resize"}},"/v1/image/convert":{"post":{"tags":["cputools"],"summary":"Convert image format","description":"$0.0003/MP. Convert between PNG, JPEG, WebP, AVIF — HEIC accepted in, animation preserved with animated:true. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nConvert to PNG/JPEG/WebP/AVIF (incl. HEIC→JPEG) via sharp. Body: { file, format: \"png\"|\"jpeg\"|\"webp\"|\"avif\", quality?: 1-100, animated?: bool }. With animated:true all frames of an animated input are preserved (gif/webp targets keep the stack; other targets flatten). Billed per megapixel (per-frame when animated). Output: uniform JSON envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","format"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"format":{"type":"string","enum":["png","jpeg","webp","avif"]},"quality":{"type":"integer","minimum":1,"maximum":100},"animated":{"type":"boolean","description":"Preserve all frames of an animated GIF/WebP (billed per-frame)."}}}}}},"responses":{"200":{"description":"Converted image."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"inputKey not owned by the caller."},"413":{"description":"Input exceeds max_storage_ref_bytes."},"422":{"description":"IMAGE_PARSE_FAILED / IMAGE_TOO_LARGE."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"image_convert"}},"/v1/image/compress":{"post":{"tags":["cputools"],"summary":"Compress an image","description":"$0.0003/MP. Re-encode an image at lower quality to shrink file size — bandwidth-friendly output. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRe-encode at a quality level, keeping the input format. Body: { file, quality?: 1-100 (default 80) }. Billed per megapixel. Output: uniform JSON envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Compressed image."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"inputKey not owned by the caller."},"413":{"description":"Input exceeds max_storage_ref_bytes."},"422":{"description":"IMAGE_PARSE_FAILED / IMAGE_TOO_LARGE."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"image_compress"}},"/v1/image/rotate":{"post":{"tags":["cputools"],"summary":"Rotate / flip an image","description":"$0.0003/MP. Rotate or flip an image — any angle, mirrored or not. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRotate 90/180/270° and/or flip/flop via sharp. Body: { file, angle?: \"90\"|\"180\"|\"270\", flip?: bool, flop?: bool } (at least one). Billed per megapixel. Output: uniform JSON envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"description":"At least one of angle / flip / flop is required.","properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"angle":{"type":"string","enum":["90","180","270"]},"flip":{"type":"boolean","description":"Mirror vertically."},"flop":{"type":"boolean","description":"Mirror horizontally."}}}}}},"responses":{"200":{"description":"Rotated image."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"inputKey not owned by the caller."},"413":{"description":"Input exceeds max_storage_ref_bytes."},"422":{"description":"IMAGE_PARSE_FAILED / IMAGE_TOO_LARGE."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"image_rotate"}},"/v1/image/metadata":{"post":{"tags":["cputools"],"summary":"Read image metadata","description":"$0.0005/call. Read dimensions, format, and EXIF presence — inspect before you process. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRead dimensions / format / EXIF presence via sharp. Body: { file }. FLAT price (`cputools.price.image.metadata.flat_micros`). Output: JSON `{ metadata: { width, height, format, space, channels, depth, density, hasAlpha, hasProfile, orientation, isProgressive, hasExif } }` — no bytes.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Image metadata JSON."},"402":{"description":"Payment required (x402 challenge with the flat price)."},"404":{"description":"inputKey not owned by the caller."},"413":{"description":"Input exceeds max_storage_ref_bytes."},"422":{"description":"IMAGE_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"image_metadata"}},"/v1/image/crop":{"post":{"tags":["cputools"],"summary":"Crop an image region","description":"$0.0003/MP. Crop any region by coordinates — exact pixels out. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nCrop a region. Body: { file, left, top, width, height }. Per-MP billed (`cputools.price.image.crop.per_mp_micros`). Out-of-bounds region → 422 CROP_OUT_OF_BOUNDS. Output: uniform JSON envelope (original format).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Cropped image."},"402":{"description":"Payment required."},"422":{"description":"IMAGE_PARSE_FAILED / IMAGE_TOO_LARGE / CROP_OUT_OF_BOUNDS."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"image_crop"}},"/v1/image/blur":{"post":{"tags":["cputools"],"summary":"Blur an image","description":"$0.0003/MP. Gaussian-blur an image at the strength you set — backgrounds, redactions, thumbnails. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nGaussian blur. Body: { file, sigma? (0.3–1000, default 3) }. Per-MP billed (`cputools.price.image.blur.per_mp_micros`). Output: uniform JSON envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Blurred image."},"402":{"description":"Payment required."},"422":{"description":"IMAGE_PARSE_FAILED / IMAGE_TOO_LARGE."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"image_blur"}},"/v1/image/sharpen":{"post":{"tags":["cputools"],"summary":"Sharpen an image","description":"$0.0003/MP. Sharpen a soft image with adjustable strength — crisper scans, photos, thumbnails. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nSharpen. Body: { file, sigma? (default 1) }. Per-MP billed (`cputools.price.image.sharpen.per_mp_micros`). Output: uniform JSON envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Sharpened image."},"402":{"description":"Payment required."},"422":{"description":"IMAGE_PARSE_FAILED / IMAGE_TOO_LARGE."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"image_sharpen"}},"/v1/image/grayscale":{"post":{"tags":["cputools"],"summary":"Grayscale an image","description":"$0.0003/MP. Convert any image to grayscale — one call, any input format. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nConvert to grayscale. Body: { file }. Per-MP billed (`cputools.price.image.grayscale.per_mp_micros`). Output: uniform JSON envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Grayscale image."},"402":{"description":"Payment required."},"422":{"description":"IMAGE_PARSE_FAILED / IMAGE_TOO_LARGE."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"image_grayscale"}},"/v1/image/exif-strip":{"post":{"tags":["cputools"],"summary":"Strip image metadata","description":"$0.0003/MP. Strip EXIF and metadata for privacy — auto-orients first so photos stay upright. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nStrip EXIF/metadata (auto-orient, then drop all metadata; pixels + orientation preserved). Body: { file }. Per-MP billed (`cputools.price.image.exif-strip.per_mp_micros`). Output: uniform JSON envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Stripped image."},"402":{"description":"Payment required."},"422":{"description":"IMAGE_PARSE_FAILED / IMAGE_TOO_LARGE."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"image_exif_strip"}},"/v1/image/dominant-color":{"post":{"tags":["cputools"],"summary":"Dominant color of an image","description":"$0.0002/call. Get an image's dominant color as hex/RGB — theming and palettes from any picture. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nGet the dominant color (sharp stats). Body: { file }. FLAT-billed (`cputools.price.image.dominant-color.flat_micros`). Output: JSON `{ dominant: {r,g,b}, hex }`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Dominant color."},"402":{"description":"Payment required."},"422":{"description":"IMAGE_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"image_dominant_color"}},"/v1/image/composite":{"post":{"tags":["cputools"],"summary":"Composite / watermark images","description":"$0.0003/MP. Overlay or watermark one image onto another — position and blend control. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nOverlay one image onto another. Body: { file (base), overlay, gravity? | (top,left), opacity? (0–1) }. Per-MP billed on the base (`cputools.price.image.composite.per_mp_micros`). Output: uniform JSON envelope (base format).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","overlay"],"description":"Placement is gravity OR explicit top+left (top and left must be provided together).","properties":{"file":{"type":"object","description":"The base image. cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"overlay":{"type":"object","description":"The overlay image. cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"gravity":{"type":"string","enum":["center","north","south","east","west","northeast","northwest","southeast","southwest"],"description":"Overlay placement (alternative to top/left)."},"top":{"type":"integer","minimum":0,"maximum":100000},"left":{"type":"integer","minimum":0,"maximum":100000},"opacity":{"type":"number","minimum":0,"maximum":1}}}}}},"responses":{"200":{"description":"Composited image."},"402":{"description":"Payment required."},"422":{"description":"IMAGE_PARSE_FAILED / IMAGE_TOO_LARGE."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"image_composite"}},"/v1/image/contact-sheet":{"post":{"tags":["cputools"],"summary":"Thumbnail-grid contact sheet","description":"$0.0003/MP. Tile many images into one thumbnail-grid PNG — visual index in a single call. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nTile images into a grid PNG. Body: { files: <source>[] (≤100), columns?, cellWidth?, cellHeight?, gap?, background? }. Per-MP billed on the total input (`cputools.price.image.contact-sheet.per_mp_micros`). Output: uniform JSON envelope (image/png).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Contact-sheet PNG."},"402":{"description":"Payment required."},"422":{"description":"IMAGE_PARSE_FAILED / IMAGE_TOO_LARGE."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"image_contact_sheet"}},"/v1/image/from-html":{"post":{"tags":["cputools"],"summary":"Render HTML to an image (Chromium screenshot)","description":"$0.003/call. Render HTML to an image — sandboxed headless Chromium screenshot of your markup. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRender caller-supplied HTML to a png/jpeg/webp via page.screenshot on the SAME dedicated x86_64 Chromium render worker as pdf/from-html. Body: { html: string, type?: \"png\"|\"jpeg\"|\"webp\" (default png), fullPage?: bool, clip?: { x, y, width, height }, omitBackground?: bool, width?: int, height?: int }. SANDBOXED / SSRF-WALLED identically to pdf/from-html: NO network, NO JavaScript — every non-`data:` request is aborted, so ALL assets must be inlined as `data:` URIs (provided-HTML-only). `clip` and `fullPage` are mutually exclusive (400). `omitBackground` (transparent background) requires png/webp — with jpeg it 400s (jpeg has no alpha channel). Billed FLAT per render (`cputools.price.image.from-html.flat_micros`) — the output dimensions aren't known until after the render, so it cannot drive an x402 pre-auth. Output: uniform JSON envelope (image/png|jpeg|webp).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["html"],"description":"clip and fullPage are mutually exclusive; omitBackground requires png/webp.","properties":{"html":{"type":"string","minLength":1,"description":"Self-contained HTML; all assets inlined as data: URIs (no network)."},"type":{"type":"string","enum":["png","jpeg","webp"]},"fullPage":{"type":"boolean","description":"Capture the full scrollable page (mutually exclusive with clip)."},"clip":{"type":"object","required":["x","y","width","height"],"description":"A region to capture (mutually exclusive with fullPage).","properties":{"x":{"type":"number","minimum":0,"maximum":100000},"y":{"type":"number","minimum":0,"maximum":100000},"width":{"type":"number","minimum":1,"maximum":100000},"height":{"type":"number","minimum":1,"maximum":100000}}},"omitBackground":{"type":"boolean","description":"Transparent background (png/webp only)."},"width":{"type":"integer","minimum":1,"maximum":10000,"description":"Viewport width."},"height":{"type":"integer","minimum":1,"maximum":10000,"description":"Viewport height."}}}}}},"responses":{"200":{"description":"Rendered image (uniform JSON output envelope)."},"400":{"description":"VALIDATION_ERROR (clip+fullPage together, or omitBackground+jpeg)."},"402":{"description":"Payment required (x402 challenge with the flat price)."},"422":{"description":"HTML_TOO_LARGE (over cputools.render.max_html_bytes)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"image_from_html"}},"/v1/image/adjust":{"post":{"tags":["cputools"],"summary":"Adjust image color/tone","description":"$0.0003/MP. Adjust brightness, saturation, hue, lightness — or negate and tint — in one call. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nAdjust brightness / saturation / hue / lightness / negate / tint via sharp (modulate bundle). Body: { file, brightness?: 0–10, saturation?: 0–10, hue?: -360–360 (degrees), lightness?: -100–100, negate?: bool, tint?: { r, g, b } } — at least one adjustment is required. Billed per megapixel (`cputools.price.image.adjust.per_mp_micros`). Output: uniform JSON envelope (original format).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"description":"At least one of brightness/saturation/hue/lightness/negate/tint is required.","properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"brightness":{"type":"number","minimum":0,"maximum":10,"description":"Multiplier (1 = unchanged)."},"saturation":{"type":"number","minimum":0,"maximum":10,"description":"Multiplier (1 = unchanged)."},"hue":{"type":"integer","minimum":-360,"maximum":360,"description":"Rotation in degrees."},"lightness":{"type":"number","minimum":-100,"maximum":100},"negate":{"type":"boolean","description":"Invert colors."},"tint":{"type":"object","required":["r","g","b"],"properties":{"r":{"type":"integer","minimum":0,"maximum":255},"g":{"type":"integer","minimum":0,"maximum":255},"b":{"type":"integer","minimum":0,"maximum":255}}}}}}}},"responses":{"200":{"description":"Adjusted image."},"400":{"description":"VALIDATION_ERROR (no adjustment provided)."},"402":{"description":"Payment required (x402 challenge with the per-MP price)."},"404":{"description":"inputKey not owned by the caller."},"413":{"description":"Input exceeds max_storage_ref_bytes."},"422":{"description":"IMAGE_PARSE_FAILED / IMAGE_TOO_LARGE."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"image_adjust"}},"/v1/image/trim":{"post":{"tags":["cputools"],"summary":"Auto-crop uniform borders","description":"$0.0003/MP. Auto-crop uniform borders — clean edges without manual coordinates. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nTrim uniform-color borders via sharp. Body: { file, threshold?: 0–255 (sensitivity; default sharp's) }. Billed per megapixel (`cputools.price.image.trim.per_mp_micros`). Output: uniform JSON envelope (original format).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"threshold":{"type":"number","minimum":0,"maximum":255,"description":"Border-detection sensitivity."}}}}}},"responses":{"200":{"description":"Trimmed image."},"402":{"description":"Payment required (x402 challenge with the per-MP price)."},"404":{"description":"inputKey not owned by the caller."},"413":{"description":"Input exceeds max_storage_ref_bytes."},"422":{"description":"IMAGE_PARSE_FAILED / IMAGE_TOO_LARGE."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"image_trim"}},"/v1/image/extend":{"post":{"tags":["cputools"],"summary":"Pad (extend) an image","description":"$0.0003/MP. Pad or extend an image's canvas on any side — aspect-ratio fixes without cropping. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nPad an image on any side via sharp. Body: { file, top?, bottom?, left?, right? (0–10000 px each; at least one positive), background?: \"#rrggbb\" | { r, g, b, alpha? }, extendWith?: \"background\"|\"copy\"|\"repeat\"|\"mirror\" }. Billed per megapixel (`cputools.price.image.extend.per_mp_micros`). Output: uniform JSON envelope (original format).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"description":"At least one of top/bottom/left/right must be a positive pixel count.","properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"top":{"type":"integer","minimum":0,"maximum":10000},"bottom":{"type":"integer","minimum":0,"maximum":10000},"left":{"type":"integer","minimum":0,"maximum":10000},"right":{"type":"integer","minimum":0,"maximum":10000},"background":{"oneOf":[{"type":"string","maxLength":32,"description":"A hex color string (\"#rrggbb\")."},{"type":"object","required":["r","g","b"],"properties":{"r":{"type":"integer","minimum":0,"maximum":255},"g":{"type":"integer","minimum":0,"maximum":255},"b":{"type":"integer","minimum":0,"maximum":255},"alpha":{"type":"number","minimum":0,"maximum":1}}}]},"extendWith":{"type":"string","enum":["background","copy","repeat","mirror"]}}}}}},"responses":{"200":{"description":"Extended image."},"400":{"description":"VALIDATION_ERROR (no positive side)."},"402":{"description":"Payment required (x402 challenge with the per-MP price)."},"404":{"description":"inputKey not owned by the caller."},"413":{"description":"Input exceeds max_storage_ref_bytes."},"422":{"description":"IMAGE_PARSE_FAILED / IMAGE_TOO_LARGE."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"image_extend"}},"/v1/csv/convert":{"post":{"tags":["cputools"],"summary":"Convert tabular formats","description":"$0.0002/MB. Convert tabular data between CSV, TSV, JSON, and NDJSON — structure preserved. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nConvert between csv / tsv / json / ndjson via Papaparse. Body: { file: <source>, from?: \"csv\"|\"tsv\"|\"json\"|\"ndjson\" (default csv), to: <same enum>, delimiter? }. Billed PER MB of input (`cputools.price.csv.convert.per_mb_micros`, min 1 MiB). Output: uniform JSON envelope (text/csv | text/tab-separated-values | application/json | application/x-ndjson). Cells are data, never evaluated — no formula execution.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","to"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"from":{"$ref":"#/components/schemas/TabularFormat"},"to":{"$ref":"#/components/schemas/TabularFormat"},"delimiter":{"type":"string","minLength":1,"maxLength":4}}}}}},"responses":{"200":{"description":"Converted file (uniform JSON output envelope)."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"CSV_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"csv_convert"}},"/v1/csv/dedupe":{"post":{"tags":["cputools"],"summary":"Drop duplicate rows","description":"$0.0002/MB. Drop duplicate rows, order preserved — clean a dataset in one call. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRemove duplicate rows from a CSV, order-preserving. Body: { file: <source>, columns?: string[] } — key columns to dedupe on; default is the whole row. Billed PER MB of input (`cputools.price.csv.dedupe.per_mb_micros`, min 1 MiB). Output: uniform JSON envelope (text/csv).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Deduplicated CSV."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"inputKey not owned by the caller."},"413":{"description":"Input exceeds max_storage_ref_bytes."},"422":{"description":"CSV_PARSE_FAILED / UNKNOWN_COLUMN."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"csv_dedupe"}},"/v1/csv/select":{"post":{"tags":["cputools"],"summary":"Project / reorder columns","description":"$0.0002/MB. Keep, drop, or reorder columns in a table — shape data for the next step. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nKeep or drop CSV columns. Body: { file: <source>, columns?: string[] | drop?: string[] } — EXACTLY ONE; `columns` keeps + reorders, `drop` removes. Billed PER MB of input (`cputools.price.csv.select.per_mb_micros`, min 1 MiB). Output: uniform JSON envelope (text/csv).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Projected CSV."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"inputKey not owned by the caller."},"413":{"description":"Input exceeds max_storage_ref_bytes."},"422":{"description":"CSV_PARSE_FAILED / UNKNOWN_COLUMN."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"csv_select"}},"/v1/utils/hash":{"post":{"tags":["cputools"],"summary":"Hash data","description":"$0.0002/MB. Hash bytes with SHA-256, SHA-512, SHA-1, or MD5 — digests as hex. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nHash bytes with sha256 / sha512 / sha1 / md5 (Node crypto). Body: { data?: string | file?: <source> } — EXACTLY ONE — + { algo }. Billed PER MB of input (`cputools.price.utils.hash.per_mb_micros`, min 1 MiB). Output: JSON `{ algo, hash: <hex> }` — no bytes. (sha1/md5 provided for legacy checksum compatibility.)","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["algo"],"description":"Exactly one of data / file is required.","properties":{"data":{"type":"string","minLength":1},"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"algo":{"type":"string","enum":["sha256","sha512","sha1","md5"]}}}}}},"responses":{"200":{"description":"Hash digest (hex)."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"INPUT_TOO_LARGE / INPUT_EMPTY."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"utils_hash"}},"/v1/utils/hmac":{"post":{"tags":["cputools"],"summary":"HMAC data","description":"$0.0002/MB. Compute a keyed HMAC (SHA-256/512/1, MD5) — the key is never logged. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nHMAC bytes with a secret key (Node crypto; sha256 / sha512 / sha1 / md5). Body: { data?: string | file?: <source> } — EXACTLY ONE — + { key, algo }. Billed PER MB of input (`cputools.price.utils.hmac.per_mb_micros`, min 1 MiB). Output: JSON `{ algo, hmac: <hex> }`. The key is secret — never logged.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["key","algo"],"description":"Exactly one of data / file is required.","properties":{"data":{"type":"string","minLength":1},"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"key":{"type":"string","minLength":1,"maxLength":8192,"description":"The HMAC secret — never logged."},"algo":{"type":"string","enum":["sha256","sha512","sha1","md5"]}}}}}},"responses":{"200":{"description":"HMAC digest (hex)."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"inputKey not owned by the caller."},"413":{"description":"Input exceeds max_storage_ref_bytes."},"422":{"description":"INPUT_TOO_LARGE / INPUT_EMPTY."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"utils_hmac"}},"/v1/utils/uuid":{"post":{"tags":["cputools"],"summary":"Generate UUIDs","description":"$0.0001/call. Generate v4 UUIDs — one or a batch. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nGenerate v4 UUIDs (Node crypto.randomUUID). Body: { version?: \"v4\", count?: int ≥1 }. FLAT-billed (`cputools.price.utils.uuid.flat_micros`). `count` is capped by `cputools.utils.uuid.max_count` (over → 422 COUNT_TOO_LARGE). Output: JSON `{ uuids: string[] }`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"version":{"type":"string","enum":["v4"]},"count":{"type":"integer","minimum":1,"description":"Runtime-capped at cputools.utils.uuid.max_count."}}}}}},"responses":{"200":{"description":"Generated UUIDs."},"402":{"description":"Payment required (x402 challenge with the flat price)."},"422":{"description":"COUNT_TOO_LARGE."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"utils_uuid"}},"/v1/utils/jwt-decode":{"post":{"tags":["cputools"],"summary":"Decode a JWT (no verification)","description":"$0.0001/call. Decode a JWT's header and payload (no verification) — inspect claims fast. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nDecode a JWT header + payload WITHOUT verifying the signature (jose). Body: { token }. FLAT-billed (`cputools.price.utils.jwt-decode.flat_micros`). Output: JSON `{ header, payload }`. A malformed token → 422 JWT_MALFORMED. To verify a signature use /v1/utils/jwt-verify.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Decoded header + payload."},"402":{"description":"Payment required (x402 challenge)."},"422":{"description":"JWT_MALFORMED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"utils_jwt_decode"}},"/v1/utils/jwt-verify":{"post":{"tags":["cputools"],"summary":"Verify a JWT signature","description":"$0.0001/call. Verify a JWT signature — algorithm-pinned; the key is never logged. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nVerify a JWT signature, alg-pinned (jose). Body: { token, key, alg: HS256/384/512 | RS256/384/512 | ES256/384 }. The verify is pinned to the declared `alg` — blocks the alg-confusion / `alg:none` downgrade. FLAT-billed (`cputools.price.utils.jwt-verify.flat_micros`). A valid-shape token that fails verification is a PAID answer: 200 `{ valid: false, reason }`. Only an unparseable token → 422 JWT_MALFORMED; an unimportable key → 422 JWT_KEY_INVALID. The key is secret — never logged. Output: JSON `{ valid, payload? | reason? }`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token","key","alg"],"properties":{"token":{"type":"string","minLength":1},"key":{"type":"string","minLength":1,"description":"The verification key (secret/PEM/JWK string) — never logged."},"alg":{"type":"string","enum":["HS256","HS384","HS512","RS256","RS384","RS512","ES256","ES384"],"description":"The verify is pinned to this alg (alg:none and confusion downgrades blocked)."}}}}}},"responses":{"200":{"description":"Verification result `{ valid, payload? | reason? }`."},"402":{"description":"Payment required (x402 challenge)."},"422":{"description":"JWT_MALFORMED / JWT_KEY_INVALID."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"utils_jwt_verify"}},"/v1/utils/base64":{"post":{"tags":["cputools"],"summary":"Base64 encode / decode","description":"$0.0002/MB. Base64 encode or decode a string — simple, bounded, fast. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nBase64 encode or decode a string (Node Buffer). Body: { data, mode: \"encode\" | \"decode\" }. Billed PER MB of the input `data` (`cputools.price.utils.base64.per_mb_micros`, min 1 MiB). Output: JSON `{ data: <result> }`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["data","mode"],"properties":{"data":{"type":"string","minLength":1},"mode":{"type":"string","enum":["encode","decode"]}}}}}},"responses":{"200":{"description":"Encoded / decoded string."},"402":{"description":"Payment required (x402 challenge)."},"422":{"description":"Invalid input."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"utils_base64"}},"/v1/generate/og-image":{"post":{"tags":["cputools"],"summary":"Render an OG / social-card image","description":"$0.0005/call. Render social/OG card PNGs — titles, colors, branding — for links that get clicked. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRender a clean social-card PNG (satori → SVG → sharp). Body: { title, subtitle?, theme?: \"light\"|\"dark\", width?: 600-2000, height?: 300-1200 }. FLAT-billed (`cputools.price.generate.og-image.flat_micros`). Output: uniform JSON envelope (image/png, inline base64).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","minLength":1,"maxLength":200},"subtitle":{"type":"string","maxLength":300},"theme":{"type":"string","enum":["light","dark"]},"width":{"type":"integer","minimum":600,"maximum":2000},"height":{"type":"integer","minimum":300,"maximum":1200}}}}}},"responses":{"200":{"description":"Rendered PNG (uniform JSON output envelope)."},"402":{"description":"Payment required (x402 challenge with the flat price)."},"422":{"description":"Invalid input."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"generate_og_image"}},"/v1/generate/mock-data":{"post":{"tags":["cputools"],"summary":"Generate fake / mock data","description":"$0.0002/call. Generate realistic fake rows from a field→type schema (JSON/CSV/NDJSON) — test data on demand. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nGenerate fake rows from a field→type schema (@faker-js/faker). Body: { schema: { <field>: <type> }, count: int ≥1, format?: \"json\"|\"csv\"|\"ndjson\" }. Types: name, firstName, lastName, email, uuid, int, float, bool, date, address, city, country, phone, company, url, word, sentence, paragraph (unknown → 422 UNSUPPORTED_TYPE). `count` ≤ `cputools.generate.mock-data.max_rows`, fields ≤ `cputools.generate.mock-data.max_fields`. FLAT-billed (`cputools.price.generate.mock-data.flat_micros`). Output: uniform JSON envelope (application/json | text/csv | application/x-ndjson).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["schema","count"],"properties":{"schema":{"type":"object","minProperties":1,"description":"Field name → generator type.","additionalProperties":{"type":"string","enum":["name","firstName","lastName","email","uuid","int","float","bool","date","address","city","country","phone","company","url","word","sentence","paragraph"]}},"count":{"type":"integer","minimum":1,"description":"Runtime-capped at cputools.generate.mock-data.max_rows."},"format":{"type":"string","enum":["json","csv","ndjson"]}}}}}},"responses":{"200":{"description":"Generated dataset (uniform JSON output envelope)."},"402":{"description":"Payment required (x402 challenge)."},"422":{"description":"UNSUPPORTED_TYPE / COUNT_TOO_LARGE / TOO_MANY_FIELDS."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"generate_mock_data"}},"/v1/generate/invoice":{"post":{"tags":["cputools"],"summary":"Render an invoice PDF","description":"$0.0002/call. Render a clean invoice PDF from structured JSON — line items, totals, branding. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRender a clean invoice PDF from structured data (pdf-lib). Body: { seller, buyer, items:[{description,quantity,unitPrice}] (1-100), number?, date?, currency?, taxRate?: 0-100, notes? }. Computes subtotal/tax/total. FLAT-billed (`cputools.price.generate.invoice.flat_micros`). Output: uniform JSON envelope (application/pdf).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Rendered invoice PDF (uniform JSON output envelope)."},"402":{"description":"Payment required (x402 challenge)."},"422":{"description":"Invalid input."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"generate_invoice"}},"/v1/generate/chart":{"post":{"tags":["cputools"],"summary":"Render a bar / line / pie chart","description":"$0.0002/call. Render bar, line, or pie charts to PNG from JSON data — report-ready images in one call. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRender a chart PNG from data points (hand-rolled SVG → sharp; no chart-lib). Body: { type: \"bar\"|\"line\"|\"pie\", data:[{label,value}] (1-50), title?, width?: 200-2000, height?: 150-2000, color? }. FLAT-billed (`cputools.price.generate.chart.flat_micros`). Output: uniform JSON envelope (image/png).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","data"],"properties":{"type":{"type":"string","enum":["bar","line","pie"]},"data":{"type":"array","minItems":1,"maxItems":50,"items":{"type":"object","required":["label","value"],"properties":{"label":{"type":"string","maxLength":120},"value":{"type":"number"}}}},"title":{"type":"string","maxLength":200},"width":{"type":"integer","minimum":200,"maximum":2000},"height":{"type":"integer","minimum":150,"maximum":2000},"color":{"type":"string","maxLength":32}}}}}},"responses":{"200":{"description":"Rendered chart PNG (uniform JSON output envelope)."},"402":{"description":"Payment required (x402 challenge)."},"422":{"description":"Invalid input."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"generate_chart"}},"/v1/generate/qr-logo":{"post":{"tags":["cputools"],"summary":"Render a QR code with a centered logo","description":"$0.0002/call. QR code with your logo composited in the center (ECC H — still scans reliably). 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRender a QR code (ECC level H) with a logo composited in the center (qrcode → sharp.composite, white-padded for scan contrast). Body: { data, logo: <source> (image), size?: 128-2000, margin?: 0-16, logoScale?: 0.1-0.35 }. FLAT-billed (`cputools.price.generate.qr-logo.flat_micros`). Output: uniform JSON envelope (image/png).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Rendered QR-with-logo PNG (uniform JSON output envelope)."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"A storage-ref logo inputKey is not owned by the caller."},"422":{"description":"IMAGE_PARSE_FAILED / invalid input."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"generate_qr_logo"}},"/v1/generate/favicon":{"post":{"tags":["cputools"],"summary":"Generate a favicon set","description":"$0.0002/call. Generate a complete favicon set (PNGs) from an image or initials text. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nGenerate a favicon set (PNGs at several sizes) from EITHER an image OR initials text. Body: exactly one of { image: <source> } or { text: 1-3 chars }, plus background?, color?, sizes?: int[8-512] (default [16,32,48,180]). FLAT-billed (`cputools.price.generate.favicon.flat_micros`). Output: { icons: [{ size, output }] } — each output a uniform envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Favicon manifest (each icon a uniform JSON output envelope)."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"A storage-ref image inputKey is not owned by the caller."},"422":{"description":"IMAGE_PARSE_FAILED / must provide exactly one of image|text."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"generate_favicon"}},"/v1/generate/placeholder":{"post":{"tags":["cputools"],"summary":"Render a placeholder image","description":"$0.0002/call. Placeholder images at any size with label text — PNG or SVG. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRender a placeholder image (solid background + centered dimensions/label text; hand-rolled SVG). Body: { width: 8-4000, height: 8-4000, background?, color?, text? (default \"W×H\"), format?: \"png\"|\"svg\" }. FLAT-billed (`cputools.price.generate.placeholder.flat_micros`). Output: uniform JSON envelope (image/png | image/svg+xml).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["width","height"],"properties":{"width":{"type":"integer","minimum":8,"maximum":4000},"height":{"type":"integer","minimum":8,"maximum":4000},"background":{"type":"string","maxLength":32},"color":{"type":"string","maxLength":32},"text":{"type":"string","maxLength":120,"description":"Label text (default \"W×H\")."},"format":{"type":"string","enum":["png","svg"]}}}}}},"responses":{"200":{"description":"Rendered placeholder (uniform JSON output envelope)."},"402":{"description":"Payment required (x402 challenge)."},"422":{"description":"Invalid input."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"generate_placeholder"}},"/v1/generate/identicon":{"post":{"tags":["cputools"],"summary":"Render a deterministic identicon","description":"$0.0002/call. Deterministic GitHub-style identicon PNG from any seed string — stable avatars for users or agents. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRender a deterministic GitHub-style 5×5 mirror-symmetric identicon PNG from a seed (sha256 → color + cell grid; same seed → same image). Body: { seed, size?: 32-1024, background? }. FLAT-billed (`cputools.price.generate.identicon.flat_micros`). Output: uniform JSON envelope (image/png).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Rendered identicon PNG (uniform JSON output envelope)."},"402":{"description":"Payment required (x402 challenge)."},"422":{"description":"Invalid input."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"generate_identicon"}},"/v1/archive/zip":{"post":{"tags":["cputools"],"summary":"Create a zip archive","description":"$0.0002/MB. Zip any set of files into one downloadable archive — pass URLs or payloads, get a link back. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nZip 1+ files into a single archive (fflate, in-memory). Body: { files: <source>[] (≥1, ≤ cputools.archive.max_files_per_zip), filenames?: string[] (must match files.length) }. Filenames are reduced to a sanitized basename (no path traversal); collisions get a `-N` suffix. Billed PER MB of total input (`cputools.price.archive.zip.per_mb_micros`, min 1 MiB). Output: uniform JSON envelope (application/zip).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"The zip archive (uniform JSON output envelope)."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"ARCHIVE_TOO_MANY_FILES / FILENAME_COUNT_MISMATCH."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"archive_zip"}},"/v1/text/case":{"post":{"tags":["cputools"],"summary":"Recase text","description":"$0.0002/MB. Recase text — upper, lower, title, camel, snake, kebab. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRecase text. Body: { text?: string | file?: <source> } (EXACTLY ONE) + { to: \"upper\"|\"lower\"|\"title\"|\"camel\"|\"snake\"|\"kebab\" }. Billed PER MB of input (`cputools.price.text.case.per_mb_micros`, min 1 MiB). Output: JSON `{ result }`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["to"],"description":"Exactly one of text / file is required.","properties":{"text":{"type":"string","minLength":1},"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"to":{"type":"string","enum":["upper","lower","title","camel","snake","kebab"]}}}}}},"responses":{"200":{"description":"Recased text."},"402":{"description":"Payment required."},"422":{"description":"Invalid input."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"text_case"}},"/v1/text/slugify":{"post":{"tags":["cputools"],"summary":"Slugify text","description":"$0.0002/MB. URL-slugify text — strip diacritics, hyphenate, lowercase. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nSlugify text (lowercase, strip diacritics, non-alphanumeric → separator). Body: { text? | file?, separator? (default \"-\") }. Billed PER MB (`cputools.price.text.slugify.per_mb_micros`). Output: JSON `{ slug }`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Slug."},"402":{"description":"Payment required."},"422":{"description":"Invalid input."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"text_slugify"}},"/v1/text/diff":{"post":{"tags":["cputools"],"summary":"Unified text diff","description":"$0.0002/MB. Unified diff of two strings — see exactly what changed. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nUnified diff of two strings (jsdiff). Body: { a, b, filenameA?, filenameB?, context? (default 3) }. Billed PER MB of a+b (`cputools.price.text.diff.per_mb_micros`). Output: JSON `{ patch, changed }`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Unified patch."},"402":{"description":"Payment required."},"422":{"description":"Invalid input."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"text_diff"}},"/v1/text/apply-patch":{"post":{"tags":["cputools"],"summary":"Apply a unified diff to a source string","description":"$0.0002/MB. Apply a unified diff to a source string — 422 on context mismatch, never a silent bad merge. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nApply a unified diff (the patch text/diff produces) to a source string (jsdiff applyPatch). Body: { source: string, patch: string }. A context mismatch (the patch doesn't apply cleanly) is a clean 422 PATCH_CONFLICT (the per-MB charge is refunded). Billed PER MB of source+patch (`cputools.price.text.apply-patch.per_mb_micros`). Output: JSON `{ result }`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["source","patch"],"properties":{"source":{"type":"string","description":"The original text to patch."},"patch":{"type":"string","description":"A unified-diff patch (e.g. from text/diff)."}}}}}},"responses":{"200":{"description":"The patched result."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"422":{"description":"PATCH_CONFLICT (the patch did not apply cleanly — context mismatch)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"text_apply_patch"}},"/v1/text/merge3":{"post":{"tags":["cputools"],"summary":"Three-way merge","description":"$0.0002/MB. Three-way merge of two edits against a common base — conflict markers on overlap. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nThree-way merge of two changes against a common base (node-diff3). Body: { base: string, yours: string, theirs: string }. A clean merge returns the merged string + conflict:false; overlapping edits return the merged string WITH conflict markers (<<<<<<< / ||||||| / ======= / >>>>>>>) + conflict:true — the markers ARE the useful output, NOT an error. Billed PER MB of base+yours+theirs (`cputools.price.text.merge3.per_mb_micros`). Output: JSON `{ merged, conflict }`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["base","yours","theirs"],"properties":{"base":{"type":"string","description":"The common ancestor."},"yours":{"type":"string","description":"Your version."},"theirs":{"type":"string","description":"Their version."}}}}}},"responses":{"200":{"description":"The merged string (with conflict markers if conflict:true)."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"422":{"description":"Invalid input."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"text_merge3"}},"/v1/text/count":{"post":{"tags":["cputools"],"summary":"Count characters / words / lines","description":"$0.0002/MB. Count characters, words, and lines — plus reading time. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nCount characters / words / lines + reading time. Body: { text? | file?, wpm? (default 200) }. Billed PER MB (`cputools.price.text.count.per_mb_micros`). Output: JSON `{ characters, charactersNoSpaces, words, lines, readingTimeMinutes }`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Counts."},"402":{"description":"Payment required."},"422":{"description":"Invalid input."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"text_count"}},"/v1/text/regex-extract":{"post":{"tags":["cputools"],"summary":"Extract regex matches (ReDoS-guarded)","description":"$0.0002/MB. Extract regex matches from text — ReDoS-guarded, patterns and input bounded. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nExtract regex matches. Body: { text? | file?, pattern, flags? ([gimsuy]), maxMatches? }. The pattern is rejected pre-charge if it carries catastrophic-backtracking risk (422 UNSAFE_REGEX); the input is byte-capped and the match count bounded (no in-engine timeout — defense is reject-pattern + bound-everything). Billed PER MB (`cputools.price.text.regex-extract.per_mb_micros`). Output: JSON `{ matches: [{ match, index, groups, namedGroups? }], count, truncated }`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["pattern"],"description":"Exactly one of text / file is required.","properties":{"text":{"type":"string","minLength":1},"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"pattern":{"type":"string","minLength":1,"maxLength":1024},"flags":{"type":"string","pattern":"^[gimsuy]*$","maxLength":8},"maxMatches":{"type":"integer","minimum":1,"maximum":100000}}}}}},"responses":{"200":{"description":"Matches."},"402":{"description":"Payment required."},"422":{"description":"UNSAFE_REGEX / REGEX_INVALID / INPUT_TOO_LARGE."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"text_regex_extract"}},"/v1/text/template":{"post":{"tags":["cputools"],"summary":"Render a logic-less template (no eval)","description":"$0.0002/MB. Render a logic-less mustache-subset template — no eval, injection-safe. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRender a logic-less mustache-subset template — NO eval: {{var}} (HTML-escaped), {{{raw}}}, {{#section}}…{{/section}}, {{^inverted}}…{{/inverted}}; own-property lookups only (no prototype access). Body: { template, data: object, escape? (default true) }. Billed PER MB of template+data (`cputools.price.text.template.per_mb_micros`). Output: JSON `{ result }`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Rendered string."},"402":{"description":"Payment required."},"422":{"description":"Invalid input."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"text_template"}},"/v1/text/markdown-to-html":{"post":{"tags":["cputools"],"summary":"Markdown → sanitized HTML","description":"$0.0002/MB. Convert markdown to HTML — always whitelist-sanitized, safe to embed. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRender Markdown (GFM) → HTML (marked), ALWAYS run through the whitelist sanitizer (script + unsafe schemes stripped). Body: { text? | file? }. Billed PER MB (`cputools.price.text.markdown-to-html.per_mb_micros`). Output: JSON `{ html }`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Sanitized HTML."},"402":{"description":"Payment required."},"422":{"description":"Invalid input."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"text_markdown_to_html"}},"/v1/text/html-to-text":{"post":{"tags":["cputools"],"summary":"HTML → plain text","description":"$0.0002/MB. Convert HTML to clean plain text — strip markup for LLM input or search. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nConvert HTML → plain text (html-to-text). Body: { text? | file?, wordwrap? (number | false) }. Billed PER MB (`cputools.price.text.html-to-text.per_mb_micros`). Output: JSON `{ text }`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Plain text."},"402":{"description":"Payment required."},"422":{"description":"Invalid input."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"text_html_to_text"}},"/v1/text/sanitize-html":{"post":{"tags":["cputools"],"summary":"Sanitize HTML (whitelist)","description":"$0.0002/MB. Sanitize HTML against a safe whitelist — kill scripts and XSS before embedding. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nSanitize HTML against a whitelist (safe tags/attrs; http/https/mailto only; script/style/event-handlers stripped). Body: { text? | file? }. Billed PER MB (`cputools.price.text.sanitize-html.per_mb_micros`). Output: JSON `{ html }`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Sanitized HTML."},"402":{"description":"Payment required."},"422":{"description":"Invalid input."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"text_sanitize_html"}},"/v1/codes/qr-decode":{"post":{"tags":["cputools"],"summary":"Decode a QR code from an image","description":"$0.0002/call. Decode a QR code from an image — the embedded text or URL back as JSON. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRead a QR code from an uploaded image (sharp decode → raw RGBA → jsqr). Body: { file: <source> }. FLAT-billed (`cputools.price.codes.qr-decode.flat_micros`). A non-decodable image is a PAID 200 `{ found: false }`; a non-image → 422 IMAGE_PARSE_FAILED. Output: JSON `{ found, data?, version? }`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Decode result."},"402":{"description":"Payment required."},"404":{"description":"inputKey not owned by the caller."},"413":{"description":"Input exceeds max_storage_ref_bytes."},"422":{"description":"IMAGE_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"codes_qr_decode"}},"/v1/codes/color-convert":{"post":{"tags":["cputools"],"summary":"Convert a color (hex/rgb/hsl)","description":"$0.0002/call. Convert colors between hex, RGB, and HSL — exact values back as JSON. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nConvert a color between hex / rgb / hsl (incl. alpha forms). Body: { color, to: \"hex\"|\"rgb\"|\"hsl\" }. FLAT-billed (`cputools.price.codes.color-convert.flat_micros`). Unrecognized color → 422 COLOR_INVALID. Output: JSON `{ input, to, result, rgb }`.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["color","to"],"properties":{"color":{"type":"string","minLength":1,"description":"Any recognized hex/rgb()/hsl() form (incl. alpha)."},"to":{"type":"string","enum":["hex","rgb","hsl"]}}}}}},"responses":{"200":{"description":"Converted color."},"402":{"description":"Payment required."},"422":{"description":"COLOR_INVALID."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"codes_color_convert"}},"/v1/data/filter":{"post":{"tags":["cputools"],"summary":"Filter rows by a structured predicate","description":"$0.0002/MB. Keep rows matching a structured predicate — injection-safe, no formula eval. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nFilter rows with a STRUCTURED predicate tree — no expression evaluation. Body: { file: <source>, where: Predicate, from?, to? }. Predicate = { column, op, value? } | { and: [...] } | { or: [...] } | { not: ... }; op ∈ eq/ne/gt/gte/lt/lte/contains/startsWith/endsWith/in/isNull/notNull. Comparisons are value-typed (numeric when both sides parse as finite numbers, else string). Nesting is depth-capped (`cputools.data.max_predicate_depth`). Billed PER MB of input (`cputools.price.data.filter.per_mb_micros`, min 1 MiB). Output: uniform text envelope (default csv).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","where"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"where":{"type":"object","description":"Structured predicate tree: a leaf { column, op, value? } or a branch { and: [...] } | { or: [...] } | { not: {...} }.","properties":{"column":{"type":"string"},"op":{"type":"string","enum":["eq","ne","gt","gte","lt","lte","contains","startsWith","endsWith","in","isNull","notNull"]},"value":{"description":"Comparison value (a scalar; an array for op=in; omitted for isNull/notNull)."},"and":{"type":"array","items":{"type":"object"}},"or":{"type":"array","items":{"type":"object"}},"not":{"type":"object"}}},"from":{"$ref":"#/components/schemas/TabularFormat"},"to":{"$ref":"#/components/schemas/TabularFormat"}}}}}},"responses":{"200":{"description":"Matching rows (uniform JSON output envelope)."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"DATA_PARSE_FAILED / UNKNOWN_COLUMN / BAD_PREDICATE / PREDICATE_TOO_DEEP."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_filter"}},"/v1/data/sort":{"post":{"tags":["cputools"],"summary":"Sort rows by columns","description":"$0.0002/MB. Sort rows by one or more columns, ascending or descending. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nStable multi-key sort. Body: { file: <source>, by: [{ column, dir?: asc|desc, type?: string|number }], from?, to? }. `type` forces numeric/string compare; default is value-typed (numeric when both parse, else string). Billed PER MB of input (`cputools.price.data.sort.per_mb_micros`, min 1 MiB). Output: uniform text envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","by"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"by":{"type":"array","minItems":1,"items":{"type":"object","required":["column"],"properties":{"column":{"type":"string"},"dir":{"type":"string","enum":["asc","desc"]},"type":{"type":"string","enum":["string","number"],"description":"Force the compare type; default value-typed."}}}},"from":{"$ref":"#/components/schemas/TabularFormat"},"to":{"$ref":"#/components/schemas/TabularFormat"}}}}}},"responses":{"200":{"description":"Sorted rows."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"inputKey not owned by the caller."},"413":{"description":"Input exceeds max_storage_ref_bytes."},"422":{"description":"DATA_PARSE_FAILED / UNKNOWN_COLUMN."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_sort"}},"/v1/data/groupby":{"post":{"tags":["cputools"],"summary":"Group rows and aggregate","description":"$0.0002/MB. Group rows and aggregate — count, sum, avg, min, max — in one call. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nGroup by columns and apply named aggregates — no expression evaluation. Body: { file: <source>, by: string[] (≥1), aggregate: [{ column?, fn: count|sum|avg|min|max|first|last, as? }], from?, to? }. `count` needs no column; the rest require one. Billed PER MB of input (`cputools.price.data.groupby.per_mb_micros`, min 1 MiB). Output bounded by `cputools.data.max_output_bytes` / `max_output_rows`. Output: uniform text envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","by","aggregate"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"by":{"type":"array","items":{"type":"string"},"minItems":1},"aggregate":{"type":"array","minItems":1,"items":{"type":"object","required":["fn"],"properties":{"column":{"type":"string","description":"Required for every fn except count."},"fn":{"type":"string","enum":["count","sum","avg","min","max","first","last"]},"as":{"type":"string","description":"Output column name."}}}},"from":{"$ref":"#/components/schemas/TabularFormat"},"to":{"$ref":"#/components/schemas/TabularFormat"}}}}}},"responses":{"200":{"description":"One row per group + aggregates."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"inputKey not owned by the caller."},"413":{"description":"Input exceeds max_storage_ref_bytes."},"422":{"description":"DATA_PARSE_FAILED / UNKNOWN_COLUMN / BAD_AGGREGATE / OUTPUT_TOO_LARGE."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_groupby"}},"/v1/data/join":{"post":{"tags":["cputools"],"summary":"Join two tables","description":"$0.0002/MB. Join two tables by key — inner, left, right, or outer. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nHash-join two tables on key columns. Body: { left: <source>, right: <source>, on: [{ left, right }] (≥1), how?: inner|left|right|outer, from?, to? }. Output schema = left fields then right fields (a right name colliding with a left name is suffixed `_right`). A many-to-many join is bounded by the running output-byte/row cap (`cputools.data.max_output_bytes` / `max_output_rows`) → 422 OUTPUT_TOO_LARGE (the cartesian-blowup guard). Billed PER MB of the SUM of both inputs (`cputools.price.data.join.per_mb_micros`, min 1 MiB). Output: uniform text envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["left","right","on"],"properties":{"left":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"right":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"on":{"type":"array","minItems":1,"items":{"type":"object","required":["left","right"],"properties":{"left":{"type":"string"},"right":{"type":"string"}}}},"how":{"type":"string","enum":["inner","left","right","outer"]},"from":{"$ref":"#/components/schemas/TabularFormat"},"to":{"$ref":"#/components/schemas/TabularFormat"}}}}}},"responses":{"200":{"description":"Joined rows."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"DATA_PARSE_FAILED / UNKNOWN_COLUMN / OUTPUT_TOO_LARGE."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_join"}},"/v1/data/union":{"post":{"tags":["cputools"],"summary":"Concatenate tables","description":"$0.0002/MB. Concatenate tables with an ordered key-union schema — stack datasets safely. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nConcatenate 2+ tables, order-preserving; schema is the ordered union of all keys (missing cells → empty). Body: { files: <source>[] (≥2, ≤ cputools.data.max_inputs), from?, to? }. Billed PER MB of the SUM of inputs (`cputools.price.data.union.per_mb_micros`, min 1 MiB). Output bounded by `cputools.data.max_output_bytes` / `max_output_rows`. Output: uniform text envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"Concatenated rows."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"inputKey not owned by the caller."},"413":{"description":"Input exceeds max_storage_ref_bytes."},"422":{"description":"DATA_PARSE_FAILED / TOO_MANY_INPUTS / OUTPUT_TOO_LARGE."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_union"}},"/v1/data/profile":{"post":{"tags":["cputools"],"summary":"Profile a table","description":"$0.0002/MB. Instant dataset profile — column types, null counts, cardinality, min/max/mean — before you commit to processing. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nProfile each column: inferred type, count, nullCount, distinctCount, min, max, mean (numeric only), topK. Body: { file: <source>, from? }. Billed PER MB of input (`cputools.price.data.profile.per_mb_micros`, min 1 MiB). Output: plain JSON report `{ rowCount, columnCount, columns: [...] }` — no file envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"JSON profile report."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"inputKey not owned by the caller."},"413":{"description":"Input exceeds max_storage_ref_bytes."},"422":{"description":"DATA_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_profile"}},"/v1/data/sql":{"post":{"tags":["cputools"],"summary":"Run SQL over a file (DuckDB)","description":"$0.0005/MB. Query any CSV/JSON/parquet file with full SQL — sandboxed read-only DuckDB, parquet in and out. A serverless query engine for a fraction of a cent. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRun a full DuckDB SQL query, READ-ONLY over your uploaded file, in a sandboxed worker. Body: { file: <source>, sql: string, from?: \"csv\"|\"json\"|\"ndjson\"|\"parquet\", to?: \"csv\"|\"json\"|\"parquet\" }. Your file is loaded as a table named `input`; the query runs with all external access locked off — no file/network/extension/COPY/ATTACH access (sandbox: load-then-latch, `enable_external_access=false` + `lock_configuration=true`; CLI dot-commands rejected). A gzip-compressed input is detected by its magic bytes and decompressed transparently for csv/json/ndjson (Brief 194 — no flag needed). Bounded by memory_limit / a hard timeout / an output-byte cap. csv/json results return inline text; `to: \"parquet\"` (Brief 187) returns BINARY parquet — it rides the storage-ref output envelope ({ output: { outputKey, outputUrl, … } }) and is produced by a SAFE two-step that never touches the user-SQL sandbox. Billed PER MB of input (`cputools.price.data.sql.per_mb_micros`, min 1 MiB). Output: uniform JSON envelope (text/csv, application/json, or application/octet-stream for parquet).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","sql"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"sql":{"type":"string","minLength":1,"description":"The DuckDB SQL; your file is the table named `input`."},"from":{"type":"string","enum":["csv","json","ndjson","parquet"]},"to":{"type":"string","enum":["csv","json","parquet"],"description":"csv/json return inline text; parquet is binary (storage-ref output envelope)."}}}}}},"responses":{"200":{"description":"Query result (uniform JSON output envelope; parquet is a storage ref)."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes, or the result exceeds the output cap (OUTPUT_TOO_LARGE)."},"422":{"description":"SQL_INVALID (parse/binding/permission error or a CLI dot-command) / SQL_TOO_LONG / DATA_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_sql"}},"/v1/pipeline":{"post":{"tags":["cputools"],"summary":"Run a pipeline of cputools transforms in one call","description":"$0/call + sum of steps. Chain multiple ops — OCR → extract → convert → store — in one server-side call: one payment, one result, no intermediate downloads. The glue for 155 Relaystation tools on one balance — relaystation.ai\n\nRun a linear sequence of cputools transforms in ONE call, threading bytes step-to-step in-process (the ETL-T moat — per-call composition). Body: { file: <source>, steps: [{ op: \"<category>.<op>\", args }], to?: \"csv\"|\"tsv\"|\"json\"|\"ndjson\" }. Composable ops — tabular: csv.convert/dedupe/select, data.filter/sort/groupby/cast/pivot/derive/fillna/dropna/rename/slice/explode; format bridges: data.from-xlsx (start from an Excel file) / data.to-xlsx (end in one); binary: image.resize/convert/compress/rotate, utils.base64, archive.gzip; terminal (must be last): data.profile/validate/schema-infer. Each step's `args` is that op's schema minus `file` (bytes are threaded). Tabular intermediates are canonical CSV (`to` sets the final tabular format); binary ops (incl. to-xlsx) thread self-describing bytes (the final content-type follows the last step). Multi-input (data.join/union/diff) + worker (data.sql) ops are NOT composable. Billed as the SUM of the steps (each per-MB on the input size, via the existing per-op tunables) + `cputools.price.pipeline.run.flat_micros`. Output: uniform JSON envelope. A type-mismatched chain or mid-pipeline error refunds the whole charge (atomic).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","steps"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"steps":{"type":"array","minItems":1,"description":"Step count is runtime-capped at cputools.pipeline.max_steps.","items":{"type":"object","required":["op"],"properties":{"op":{"type":"string","enum":["csv.convert","csv.dedupe","csv.select","data.filter","data.sort","data.groupby","data.cast","data.pivot","data.profile","data.validate","data.schema-infer","image.resize","image.convert","image.compress","image.rotate","utils.base64","data.derive","data.fillna","data.dropna","data.rename","data.slice","data.explode","data.from-xlsx","data.to-xlsx"]},"args":{"type":"object","description":"The op's standalone schema minus `file` (bytes are threaded step-to-step)."}}}},"to":{"$ref":"#/components/schemas/TabularFormat"}}}}}},"responses":{"200":{"description":"Pipeline result (uniform JSON output envelope; the final step's output)."},"402":{"description":"Payment required (x402 challenge with the summed per-step price)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes, or an intermediate exceeds cputools.pipeline.max_intermediate_bytes (INTERMEDIATE_TOO_LARGE)."},"422":{"description":"OP_NOT_COMPOSABLE / BAD_STEP_ARGS / TOO_MANY_STEPS / TERMINAL_STEP_NOT_LAST, or a data-dependent op error (UNKNOWN_COLUMN / *_PARSE_FAILED / BAD_PREDICATE / BAD_AGGREGATE / OUTPUT_TOO_LARGE)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pipeline_run"}},"/v1/data/cast":{"post":{"tags":["cputools"],"summary":"Coerce column types","description":"$0.0002/MB. Coerce column types (string, integer, number, boolean, date) — stop fighting stringly-typed data. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nBest-effort per-column type coercion (a non-coercible cell → null). Body: { file: <source>, columns: [{ column, to: \"string\"|\"integer\"|\"number\"|\"boolean\"|\"date\" }], from?, to? }. Billed PER MB of input (`cputools.price.data.cast.per_mb_micros`, min 1 MiB). Output: uniform JSON envelope (the re-typed table).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","columns"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"columns":{"type":"array","minItems":1,"items":{"type":"object","required":["column","to"],"properties":{"column":{"type":"string"},"to":{"type":"string","enum":["string","integer","number","boolean","date"]}}}},"from":{"$ref":"#/components/schemas/TabularFormat"},"to":{"$ref":"#/components/schemas/TabularFormat"}}}}}},"responses":{"200":{"description":"The re-typed table (uniform JSON output envelope)."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"422":{"description":"UNKNOWN_COLUMN / DATA_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_cast"}},"/v1/data/pivot":{"post":{"tags":["cputools"],"summary":"Pivot / unpivot a table","description":"$0.0002/MB. Reshape tables long↔wide — pivot and unpivot without a spreadsheet. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nReshape a table. pivot (long→wide): { file, mode:\"pivot\", index: string[], columns: string, values: string, agg?: count|sum|avg|min|max }. unpivot/melt (wide→long): { file, mode:\"unpivot\", id: string[], value?: string[] } → { ...id, variable, value }. Billed PER MB of input (`cputools.price.data.pivot.per_mb_micros`, min 1 MiB). Output bounded by `cputools.data.max_output_bytes`. Output: uniform JSON envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","mode"],"description":"mode=pivot requires index + columns + values; mode=unpivot requires id.","properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"mode":{"type":"string","enum":["pivot","unpivot"]},"index":{"type":"array","items":{"type":"string"},"minItems":1,"description":"pivot: the row-key columns."},"columns":{"type":"string","description":"pivot: the column whose values become output columns."},"values":{"type":"string","description":"pivot: the column aggregated into the cells."},"agg":{"type":"string","enum":["count","sum","avg","min","max"],"description":"pivot: cell aggregate (default count behavior per route)."},"id":{"type":"array","items":{"type":"string"},"minItems":1,"description":"unpivot: the id columns kept on every output row."},"value":{"type":"array","items":{"type":"string"},"minItems":1,"description":"unpivot: the columns melted into variable/value rows (default: all non-id)."},"from":{"$ref":"#/components/schemas/TabularFormat"},"to":{"$ref":"#/components/schemas/TabularFormat"}}}}}},"responses":{"200":{"description":"The reshaped table (uniform JSON output envelope)."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"413":{"description":"The reshaped output exceeds cputools.data.max_output_bytes (OUTPUT_TOO_LARGE)."},"422":{"description":"UNKNOWN_COLUMN / BAD_PIVOT / DATA_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_pivot"}},"/v1/data/schema-infer":{"post":{"tags":["cputools"],"summary":"Infer a schema from a table","description":"$0.0002/MB. Infer a JSON Schema or SQL DDL straight from your data — skip hand-writing schemas. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nInfer per-column types from the sample and emit a schema. Body: { file: <source>, from?, dialect?: \"json-schema\"|\"sql-ddl\" }. Billed PER MB of input (`cputools.price.data.schema-infer.per_mb_micros`, min 1 MiB). Output: a JSON report { dialect, columnCount, schema } — a JSON Schema object or a CREATE TABLE statement.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"from":{"$ref":"#/components/schemas/TabularFormat"},"dialect":{"type":"string","enum":["json-schema","sql-ddl"]}}}}}},"responses":{"200":{"description":"The inferred schema report (JSON)."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"422":{"description":"DATA_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_schema_infer"}},"/v1/data/validate":{"post":{"tags":["cputools"],"summary":"Validate a table against a structured schema","description":"$0.0002/MB. Validate rows against a structured schema — row-level errors back as JSON. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nValidate rows against a STRUCTURED schema — no expression eval (`pattern` is a bounded, length-capped RegExp over cell strings, never code). Body: { file: <source>, schema: { <col>: { type?, required?, min?, max?, pattern? } }, from? }. Billed PER MB of input (`cputools.price.data.validate.per_mb_micros`, min 1 MiB). An invalid/over-long `pattern` → 422 BAD_PATTERN (pre-charge). Output: a JSON report { valid, rowCount, errorCount, truncated, errors: [{ row, column, reason }] } (errors list capped).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","schema"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"schema":{"type":"object","description":"Per-column rules, keyed by column name.","additionalProperties":{"type":"object","properties":{"type":{"type":"string","enum":["string","integer","number","boolean","date"]},"required":{"type":"boolean"},"min":{"type":"number"},"max":{"type":"number"},"pattern":{"type":"string","description":"A bounded, length-capped RegExp over cell strings — never code."}}}},"from":{"$ref":"#/components/schemas/TabularFormat"}}}}}},"responses":{"200":{"description":"The validation report (JSON)."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"422":{"description":"UNKNOWN_COLUMN / BAD_PATTERN / DATA_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_validate"}},"/v1/data/diff":{"post":{"tags":["cputools"],"summary":"Diff two tables (row-level changeset)","description":"$0.0002/MB. Row-level changeset between two tables — added, removed, and changed rows. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRow-level changeset between two tables. Body: { left: <source>, right: <source>, key?: string[], from? }. Keyed by `key` columns (added/removed/changed) or whole-row identity (added/removed). Billed PER MB of the SUM of inputs (`cputools.price.data.diff.per_mb_micros`, min 1 MiB). Output bounded by `cputools.data.max_output_bytes`. Output: a JSON report { addedCount, removedCount, changedCount, added, removed, changed }.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"The changeset report (JSON)."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"413":{"description":"The changeset exceeds cputools.data.max_output_bytes (OUTPUT_TOO_LARGE)."},"422":{"description":"UNKNOWN_COLUMN / DATA_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_diff"}},"/v1/data/from-xlsx":{"post":{"tags":["cputools"],"summary":"Parse Excel (.xlsx) → table","description":"$0.0002/MB. Parse an Excel file into CSV/JSON — sheets, headers, and types handled. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nParse an Excel workbook into a table (the first row is the header). Body: { file: <xlsx source>, sheet?: name|index (default the first sheet), to?: \"csv\"|\"tsv\"|\"json\"|\"ndjson\" }. Reads cell VALUES — a formula cell yields its cached result, never the formula (read = data). Bounded by `cputools.data.max_xlsx_cells` via a streaming read (a cell-bomb → 422 XLSX_TOO_LARGE, never an OOM); no external entities are resolved (no XXE/SSRF). Billed PER MB of input. Output: the uniform JSON envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"The parsed table (uniform JSON output envelope)."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"422":{"description":"XLSX_PARSE_FAILED / XLSX_SHEET_NOT_FOUND / XLSX_TOO_LARGE."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_from_xlsx"}},"/v1/data/to-xlsx":{"post":{"tags":["cputools"],"summary":"Emit a table as Excel (.xlsx)","description":"$0.0002/MB. Write tables to styled, multi-sheet Excel (.xlsx) — formula-injection-safe. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nEmit a table as an Excel .xlsx (the format DuckDB/CSV cannot produce). Single-sheet: { file: <csv/json/ndjson source>, from?, sheetName? }. Multi-sheet (Brief 187): { sheets: [{ name, file, from? }, …] } (provide EXACTLY one of `file` or `sheets`, ≤ 20 sheets; billed over the SUM of all sheets' input bytes). Optional `style`: { headerBold?, freezeHeader?, columnWidths?: number[] | { <col>: number }, numberFormats?: { <col>: <Excel format string> } }. FORMULA-INJECTION-SAFE write: a cell whose value starts with `= + - @ \\t \\r` is written as a TEXT-typed string cell (never an active Excel `<f>` formula), so the value is preserved AND inert in the recipient's Excel. Billed PER MB of input. Output: the uniform JSON envelope (application/vnd.openxmlformats-officedocument.spreadsheetml.sheet).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Provide exactly one of `file` (single sheet) or `sheets` (multi-sheet).","properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"from":{"type":"string","enum":["csv","tsv","json","ndjson"]},"sheetName":{"type":"string","minLength":1,"maxLength":31},"sheets":{"type":"array","minItems":1,"maxItems":20,"description":"Multi-sheet workbook; each entry is its own tabular source.","items":{"type":"object","required":["name","file"],"properties":{"name":{"type":"string","minLength":1,"maxLength":31},"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"from":{"type":"string","enum":["csv","tsv","json","ndjson"]}}}},"style":{"type":"object","description":"Optional workbook styling.","properties":{"headerBold":{"type":"boolean"},"freezeHeader":{"type":"boolean"},"columnWidths":{"oneOf":[{"type":"array","items":{"type":"number","exclusiveMinimum":0,"maximum":1000}},{"type":"object","additionalProperties":{"type":"number","exclusiveMinimum":0,"maximum":1000}}],"description":"Per-column widths by index (array) or by column name (object)."},"numberFormats":{"type":"object","additionalProperties":{"type":"string","minLength":1,"maxLength":64},"description":"Per-column Excel number-format strings (e.g. \"#,##0.00\", \"yyyy-mm-dd\", \"@\")."}}}}}}}},"responses":{"200":{"description":"The .xlsx workbook (uniform JSON output envelope)."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"422":{"description":"DATA_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_to_xlsx"}},"/v1/data/derive":{"post":{"tags":["cputools"],"summary":"Add computed columns (structured, no-eval)","description":"$0.0002/MB. Add computed columns from structured expressions — injection-safe, no formula eval. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nAdd computed columns from a STRUCTURED expression tree — NO expression eval. Body: { file: <source>, columns: [{ name, expr }], from?, to? }. `expr = { col } | { lit } | { op, args[] }`, depth-bounded (`cputools.data.derive_max_depth`); op allowlist: arithmetic (add/sub/mul/div/mod/abs/round), string (concat/upper/lower/trim/substring), coalesce, comparison (eq/ne/gt/gte/lt/lte). Values are inert literals, ops are enum-dispatched — never `eval`/`new Function`. div/mod by zero → null. Billed PER MB of input.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"The table with the derived columns (JSON envelope)."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"422":{"description":"UNKNOWN_COLUMN / BAD_EXPR / EXPR_TOO_DEEP / BAD_DERIVE (reserved/duplicate name) / DATA_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_derive"}},"/v1/data/fillna":{"post":{"tags":["cputools"],"summary":"Fill empty/null cells","description":"$0.0002/MB. Fill empty cells by value, forward/backward fill, mean, median, or mode. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nFill empty/null cells. Body: { file: <source>, columns?: string[] (default all), value: <scalar> | \"ffill\" | \"bfill\" | \"mean\" | \"median\" | \"mode\", from?, to? }. Billed PER MB of input. Output: the filled table (JSON envelope).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"The filled table (JSON envelope)."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"422":{"description":"UNKNOWN_COLUMN / DATA_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_fillna"}},"/v1/data/dropna":{"post":{"tags":["cputools"],"summary":"Drop rows with empty cells","description":"$0.0002/MB. Drop rows with empty cells — one-call data cleaning. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nDrop rows with empty/null cells. Body: { file: <source>, columns?: string[] (default all), how?: \"any\" (default) | \"all\", from?, to? }. Billed PER MB of input. Output: the surviving rows (JSON envelope).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"columns":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Columns checked (default all)."},"how":{"type":"string","enum":["any","all"]},"from":{"$ref":"#/components/schemas/TabularFormat"},"to":{"$ref":"#/components/schemas/TabularFormat"}}}}}},"responses":{"200":{"description":"The surviving rows (JSON envelope)."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"422":{"description":"UNKNOWN_COLUMN / DATA_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_dropna"}},"/v1/data/rename":{"post":{"tags":["cputools"],"summary":"Rename columns","description":"$0.0002/MB. Rename columns by mapping — tidy schemas between pipeline steps. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRename columns. Body: { file: <source>, columns: [{ from, to }], from?, to? }. A reserved or duplicate target name → 422. Billed PER MB of input. Output: the renamed table (JSON envelope).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"The renamed table (JSON envelope)."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"422":{"description":"UNKNOWN_COLUMN / BAD_RENAME (reserved/duplicate target) / DATA_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_rename"}},"/v1/data/slice":{"post":{"tags":["cputools"],"summary":"Slice / sample rows","description":"$0.0002/MB. Slice rows — offset, limit, head, tail — for paging or previews. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nSlice or sample rows. Body: { file: <source>, offset?, limit?, head?, tail?, sample? (random n), from?, to? }. offset/limit/head/tail are deterministic; `sample` is a random n (non-deterministic). Billed PER MB of input. Output: the selected rows (JSON envelope).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"The selected rows (JSON envelope)."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"422":{"description":"DATA_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_slice"}},"/v1/data/explode":{"post":{"tags":["cputools"],"summary":"Explode a list cell into rows","description":"$0.0002/MB. Unnest array cells into rows — flatten nested JSON for analysis. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nUnnest one cell's list into multiple rows. Body: { file: <source>, column, separator? (default: parse the cell as a JSON array; else split the string on `separator`), from?, to? }. Billed PER MB of input. Output bounded by `cputools.data.max_output_bytes`. Output: the exploded rows (JSON envelope).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"The exploded rows (JSON envelope)."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"413":{"description":"The exploded output exceeds cputools.data.max_output_bytes (OUTPUT_TOO_LARGE)."},"422":{"description":"UNKNOWN_COLUMN / DATA_PARSE_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_explode"}},"/v1/data/sample":{"post":{"tags":["cputools"],"summary":"Sample rows from a dataset","description":"$0.0002/MB. Sample rows — n or fraction; random, head, or systematic; reproducible seed. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nSample rows. Body: exactly one of { n: int } or { fraction: 0-1 }, plus method?: \"random\"|\"head\"|\"systematic\" (default random), seed?: int (REPRODUCIBLE random — same seed + input → identical sample), file: <source>, from?, to?. The sample preserves original row order. Billed PER MB of input (`cputools.price.data.sample.per_mb_micros`). Output: the sampled rows (JSON envelope).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"description":"Exactly one of n / fraction is required.","properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"n":{"type":"integer","minimum":0,"description":"Absolute sample size."},"fraction":{"type":"number","minimum":0,"maximum":1,"description":"Fractional sample size."},"method":{"type":"string","enum":["random","head","systematic"]},"seed":{"type":"integer","description":"Reproducible random — same seed + input → identical sample."},"from":{"$ref":"#/components/schemas/TabularFormat"},"to":{"$ref":"#/components/schemas/TabularFormat"}}}}}},"responses":{"200":{"description":"The sampled rows (JSON envelope)."},"402":{"description":"Payment required (x402 challenge with the per-MB price)."},"422":{"description":"DATA_PARSE_FAILED / must provide exactly one of n|fraction."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_sample"}},"/v1/account/api-keys/regenerate":{"post":{"tags":["account"],"summary":"Regenerate API key","description":"Regenerate the customer API key. Mints a new rs_live_* key, invalidates the prior one, returns the raw key ONCE. Dashboard/interactive auth (rs_session cookie OR wallet-JWT); API-key callers are rejected with 403 WALLET_JWT_OR_SESSION_REQUIRED so a compromised key cannot rotate itself. Not an x402-payable route (rejects x402 with 400 X402_CANNOT_REGENERATE).","security":[{"apiKey":[]},{"walletJwt":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","description":"Optional reason logged in audit_log metadata. Truncated to 500 chars."}}}}}},"responses":{"200":{"description":"{ vfApiKey, apiKeyPrefix, regeneratedAt } — vfApiKey is the full raw secret, returned ONCE."},"400":{"description":"X402_CANNOT_REGENERATE."},"403":{"description":"WALLET_JWT_OR_SESSION_REQUIRED."},"502":{"description":"MINT_FAILED (Relaystation upstream 4xx)."},"503":{"description":"RELAYSTATION_UNAVAILABLE."}},"operationId":"regenerateApiKey"}},"/v1/bridge/ask":{"post":{"tags":["bridge"],"summary":"Ask (blocking)","description":"Send a question to the human operator and enqueue a pending_ask for reply. Returns a ticket the agent (or the @relaystation/mcp wrapper) polls via GET /v1/bridge/ask/{ticket}/check. Free tier: 5 telegram + 2 email/day per customer. Over-cap calls debit at bridge.price.<channel>_message_micros. x402-authed first calls auto-create the customers row (kind=wallet). When the bridge is toggled off (set_bridge_active=false), returns 200 {status: \"bridge_inactive\"} without enqueueing.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["channel","message"],"properties":{"channel":{"type":"string","enum":["telegram","email"],"description":"Operator delivery channel. (SMS is delisted; telegram + email only.)"},"message":{"type":"string","minLength":1,"maxLength":65536},"max_wait_seconds":{"type":"integer","minimum":1,"maximum":86400},"attached_baton_id":{"type":"string"},"callback_url":{"type":"string","format":"uri","description":"Optional webhook delivery target for headless agents (mode-2). Fires bridge.ask.replied event on the existing outbox when a reply lands."}}}}}},"responses":{"200":{"description":"{ticket, status, expires_at, cascade?} — status is pending OR bridge_inactive OR free_tier_only on soft-fail paths.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"BAD_REQUEST (invalid body)."},"401":{"description":"UNAUTHENTICATED OR INVALID_X402_SIGNATURE. The latter is returned when an X-Payment header is present but the EIP-712 typed-data signature fails to recover to the claimed payer wallet (or the payload is malformed / expired / for the wrong asset). Body carries `{error, code: \"INVALID_X402_SIGNATURE\", reason}` where reason mirrors the chassis AdmitFailReason enum."},"402":{"description":"PAYMENT_REQUIRED (x402 admit rejection on over-cap path)."},"429":{"description":"rate_capped."}},"operationId":"ask_operator"}},"/v1/bridge/ask/{ticket}/check":{"get":{"tags":["bridge"],"summary":"Check ask status","description":"Poll one pending_ask. Returns status (pending/replied/expired/bridge_inactive/free_tier_only), reply on replied, and next_poll_after_seconds — the @relaystation/mcp wrapper sleeps for that interval before re-polling. Server-resolved schedule (paid customers get the fine schedule; free-tier get coarse). Customer-scoped — returns 404 when the ticket belongs to a different customer (avoids leaking ticket existence).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"parameters":[{"name":"ticket","in":"path","required":true,"schema":{"type":"string","pattern":"^ask_[a-f0-9-]+$"}}],"responses":{"200":{"description":"{ticket, status, reply?: {body, received_at}, expires_at, next_poll_after_seconds}","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"UNAUTHENTICATED OR INVALID_X402_SIGNATURE (same shape as POST /v1/bridge/ask). A forged X-Payment header cannot read another customer's reply."},"404":{"description":"Ticket not found (or belongs to another customer)."}},"operationId":"checkBridgeAsk"}},"/v1/bridge/missed":{"get":{"tags":["bridge"],"summary":"List recently-replied tickets since a timestamp","description":"Brief 152 — supports Last-Event-ID reconnect-replay on the courier-push-server. Reads up to 100 of the authenticated customer's pending_ask rows where status='replied' AND replied_at > <since>, ordered ASC. Customer-scoped — a forged Last-Event-ID cannot leak cross-customer replies because the bearer-resolved customerId is the join key on the server side. Result cap (100) bounds long-disconnect replay storms.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"parameters":[{"name":"since","in":"query","required":true,"description":"Unix milliseconds. Returns rows with replied_at strictly newer than this timestamp.","schema":{"type":"integer","minimum":0}}],"responses":{"200":{"description":"{count: N, events: [{ticket, reply: {body, received_at}, tier, cascade, replied_at_ms}]}","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"BAD_REQUEST — missing or malformed since query param."},"401":{"description":"UNAUTHENTICATED OR INVALID_X402_SIGNATURE (same shape as POST /v1/bridge/ask)."}},"operationId":"get_missed_replies"}},"/v1/bridge/notify":{"post":{"tags":["bridge"],"summary":"Notify (fire-and-forget)","description":"Send a one-way status update to the operator. No reply expected. Same free-tier and pricing as ask. Soft-fail on insufficient_funds: returns 200 {status: \"free_tier_only\"} so the agent's continuation surfaces upgrade copy rather than a hard 402.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["channel","message"],"properties":{"channel":{"type":"string","enum":["telegram","email"],"description":"Operator delivery channel. (SMS is delisted; telegram + email only.)"},"message":{"type":"string","minLength":1,"maxLength":65536},"attached_baton_id":{"type":"string"}}}}}},"responses":{"200":{"description":"{status: \"sent\" | \"bridge_inactive\" | \"free_tier_only\", message_id, cascade?}","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"BAD_REQUEST."},"401":{"description":"UNAUTHENTICATED OR INVALID_X402_SIGNATURE (same shape as POST /v1/bridge/ask)."},"402":{"description":"PAYMENT_REQUIRED (x402 admit rejection)."},"429":{"description":"rate_capped."}},"operationId":"notify_operator"}},"/v1/bridge/active":{"get":{"tags":["bridge"],"summary":"Read bridge_active toggle","description":"Read the customer's bridge on/off state. Lazy-inserts a bridge_channel_state row with defaults on first read for x402/wallet-identity callers (operator-set default = true per bridge.active.default).","security":[{"apiKey":[]},{"walletJwt":[]}],"responses":{"200":{"description":"{active, default_was, updated_at}","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"UNAUTHENTICATED."}},"operationId":"getBridgeActive"},"post":{"tags":["bridge"],"summary":"Set bridge_active toggle","description":"Flip the bridge on/off state for the customer. Operator-controlled (typically toggled from the dashboard or the Telegram bot's /active command). When false, ask/notify return 200 {status: \"bridge_inactive\"} without enqueueing or charging.","security":[{"apiKey":[]},{"walletJwt":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["active"],"properties":{"active":{"type":"boolean"}}}}}},"responses":{"200":{"description":"{active, updated_at}","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"BAD_REQUEST."},"401":{"description":"UNAUTHENTICATED."}},"operationId":"set_bridge_active"}},"/v1/bridge/cascade/{action}":{"post":{"tags":["bridge"],"summary":"Dispatch cross-product cascade action","description":"Dispatch a registered cross-product action by name (e.g., the cascade-button taps a customer makes from an inline keyboard). Each action handler owns its own billing — the dispatcher does NOT wrap in withCharge. Currently registered: `noop` (chassis-side smoke probe). Unknown action names return 400 UNKNOWN_ACTION. **x402 is NOT supported on this route** — ActionContext does not thread the X-Payment payload through to handlers, so an x402-authed dispatch cannot admit the customer's signed authorization. Any request with an X-Payment header returns 400 X402_NOT_SUPPORTED_ON_CASCADE; the route requires api-key Bearer or wallet-JWT Bearer auth. The first cross-product consumer brief will widen ActionContext + remove this guard.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"action","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":100}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"params":{"type":"object","additionalProperties":true}}}}}},"responses":{"200":{"description":"{ok: true, data: {...action-specific...}}","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"{ok: false, error: \"UNKNOWN_ACTION\", reason} for an unregistered action name, OR {error, code: \"X402_NOT_SUPPORTED_ON_CASCADE\", reason} when an X-Payment header is present (cascade dispatcher does not accept x402 yet)."},"401":{"description":"UNAUTHENTICATED."}},"operationId":"dispatchBridgeCascade"}},"/v1/bridge/cascade/dismiss":{"post":{"tags":["bridge"],"summary":"Dismiss a cascade template","description":"Mute a specific cascade template for the customer for `bridge.cascade.frequency.muted_after_dismiss` seconds (default 24h). UPSERTs bridge_cascade_log.dismiss_until_at. Dedicated route — NOT routed through the cross-product dispatcher (dismiss is bridge-internal, not a cross-product action).","security":[{"apiKey":[]},{"walletJwt":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["template_key"],"properties":{"template_key":{"type":"string","description":"Matches bridge.cascade.template.<key> suffix (e.g. \"telegram_too_big\").","minLength":1,"maxLength":100}}}}}},"responses":{"200":{"description":"{dismissed_until} — ISO-8601 timestamp.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"BAD_REQUEST."},"401":{"description":"UNAUTHENTICATED."}},"operationId":"dismissBridgeCascade"}},"/v1/bridge/email/confirm":{"get":{"tags":["bridge"],"summary":"Confirm operator email opt-in","description":"The operator clicks the confirm link in the one-time email Courier sends; this flips `bridge_channel_state.email_opted_in = true` so agents can send the operator bridge messages by email. The `t` query param is a single-use JWT (aud=bridge-email-confirm, sub=customerId, jti consumed on first use so a leaked link cannot be reused). Returns a small inline HTML confirmation page — not JSON. PUBLIC route (the link carries its own signed token; no session/api-key needed — a human clicks it from their inbox).","security":[],"parameters":[{"name":"t","in":"query","required":true,"schema":{"type":"string"},"description":"Single-use confirm JWT minted at email-send time."}],"responses":{"200":{"description":"Inline HTML — \"Email confirmed\"."},"400":{"description":"Inline HTML — missing / invalid / expired / malformed confirm token."}},"operationId":"confirmBridgeEmail"}},"/v1/bridge/agent-address":{"post":{"tags":["bridge"],"summary":"Mint an ephemeral agent address","description":"Mint a random `<local_part>@courier.relaystation.ai` ephemeral address for agent-to-agent messaging (~67 bits of entropy in the local part — unguessable, so cross-customer spam is mathematically infeasible). Free up to `bridge.agent_address.daily_mint_cap_per_customer` per UTC day; above-cap mints debit `bridge.agent_address.create` (x402-authed first calls auto-create the customers row). Idempotency-Key required on the over-cap (billable) path.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"ttl_seconds":{"type":"integer","minimum":60,"maximum":2592000,"description":"Address lifetime; defaults to bridge.agent_address.ttl_default_seconds (86400)."},"purpose_label":{"type":"string","maxLength":100,"description":"Optional human label for the address."}}}}}},"responses":{"200":{"description":"{id, address, local_part, purpose_label, ttl_seconds, expires_at, status}","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"BAD_REQUEST."},"401":{"description":"UNAUTHENTICATED OR INVALID_X402_SIGNATURE (same shape as POST /v1/bridge/ask)."},"402":{"description":"PAYMENT_REQUIRED (x402 admit rejection on the over-cap path)."}},"operationId":"create_agent_address"}},"/v1/bridge/agent-addresses":{"get":{"tags":["bridge"],"summary":"List my agent addresses","description":"List this customer's active (not-yet-expired) ephemeral agent addresses. Free (no per-call charge). Customer-auth (api-key / wallet-JWT / x402).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"{addresses: [{id, address, local_part, purpose_label, ttl_seconds, expires_at, status}]}","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"UNAUTHENTICATED OR INVALID_X402_SIGNATURE."}},"operationId":"list_my_agent_addresses"}},"/v1/bridge/message-agent":{"post":{"tags":["bridge"],"summary":"Message another agent","description":"Send a message to another Relaystation agent's ephemeral address. Internal routing only — same-fleet AND cross-customer delivery use the internal agent_messages table (no external email round-trip). Free (no per-call charge). External (non-courier.relaystation.ai) domains return EXTERNAL_DOMAIN_NOT_SUPPORTED_V2 (Route 3 / agent→external is deferred to v2.1).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["to_address","body"],"properties":{"to_address":{"type":"string","format":"email","description":"A `<local_part>@courier.relaystation.ai` address."},"body":{"type":"string","minLength":1,"maxLength":65536},"subject":{"type":"string","maxLength":200},"attached_baton_id":{"type":"string"}}}}}},"responses":{"200":{"description":"{ok: true, message_id, delivered_at}","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"BAD_REQUEST / INVALID_ADDRESS / EXTERNAL_DOMAIN_NOT_SUPPORTED_V2."},"401":{"description":"UNAUTHENTICATED OR INVALID_X402_SIGNATURE."},"404":{"description":"UNKNOWN_ADDRESS (no active address for that local part)."},"429":{"description":"QUOTA_EXCEEDED (recipient mailbox at bridge.mailbox.max_messages_per_address)."}},"operationId":"message_agent"}},"/v1/bridge/mailbox/{address_id}":{"get":{"tags":["bridge"],"summary":"Read an agent mailbox","description":"Read inbound agent_messages for an ephemeral address THIS customer owns (ownership enforced; a foreign address_id returns 404). Free. With `unread_only=true`, returns only unread messages and stamps `read_at=now()` on them — clears a polling agent's backlog.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"parameters":[{"name":"address_id","in":"path","required":true,"schema":{"type":"string","pattern":"^aa_[a-z0-9]+$"}},{"name":"unread_only","in":"query","schema":{"type":"boolean"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"since","in":"query","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"{messages: [{id, subject, body_text, from, attached_baton_id, created_at, read_at}]}","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"BAD_REQUEST (malformed address id)."},"401":{"description":"UNAUTHENTICATED OR INVALID_X402_SIGNATURE."},"404":{"description":"NOT_FOUND (address not owned by this customer)."}},"operationId":"get_mailbox"}},"/v1/admin/courier/refund-star-payment":{"post":{"tags":["bridge"],"summary":"Refund a Telegram Stars payment (admin)","description":"OPERATOR-ONLY. Refunds a Telegram Stars top-up via the chassis refund helper (`refundStarPayment`) and writes a reversing ledger credit. Requires an admin session (rs_admin_session cookie, admin_users allowlist) — this is NOT a customer-callable route; it is documented here only so the full Courier surface is discoverable. Idempotent on `telegram_payment_charge_id` (a re-run returns `already_refunded: true`).","security":[{"adminSession":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["telegram_payment_charge_id"],"properties":{"telegram_payment_charge_id":{"type":"string","minLength":1,"maxLength":200},"reason":{"type":"string","maxLength":2000}}}}}},"responses":{"200":{"description":"{ok: true, already_refunded, ledger_id, customer_id, amount_micros}","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"BAD_REQUEST or a refund-failure reason code."},"401":{"description":"Admin session required."},"404":{"description":"charge_not_found."}},"operationId":"refundStarPayment"}},"/v1/esigndoc/envelopes":{"post":{"tags":["esigndoc"],"summary":"Send a document for signature","description":"$0.05/envelope. Send any PDF, Word doc, or markdown for legally binding e-signature — hosted signing links, ordered multi-signer routing, approvers, templates, live status, signed-copy retrieval. DocuSign-class workflow at a fraction of the price, one of 155 Relaystation tools on one balance — relaystation.ai\n\nCreate a signing envelope from a PDF + recipients. Two body shapes: multipart/form-data (a `payload` JSON-string field + a `pdf` File), or application/json (the payload fields at the top level + `pdf` as the cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url> }). Billable (esign.envelope) ONLY when the create also sends — i.e. `fields` is non-empty or `autoPlaceSignature: true`; a draft create (no fields) is free. Charge-on-successful-send, no refund. Idempotency-Key honored.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","recipients","pdf"],"properties":{"title":{"type":"string","minLength":1,"maxLength":255},"recipients":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"object","required":["email","name"],"properties":{"email":{"type":"string","format":"email"},"name":{"type":"string","minLength":1,"maxLength":255},"role":{"type":"string","enum":["SIGNER","APPROVER","VIEWER","CC","ASSISTANT"],"default":"SIGNER"},"signingOrder":{"type":"integer","minimum":0,"description":"Per-recipient signing position. Used with the document-level signingOrder: SEQUENTIAL to route signers in order. Defaults to 1-based array position when omitted."}}}},"pdf":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"fields":{"type":"array","description":"Field placements (presence ⇒ the envelope sends ⇒ billable). Each: { type, recipientEmail, page, positionX, positionY, width, height } with positions as 0–100 percentages.","items":{"type":"object","required":["type","recipientEmail","page","positionX","positionY","width","height"],"properties":{"type":{"type":"string","enum":["SIGNATURE","INITIALS","NAME","EMAIL","DATE","TEXT","NUMBER","CHECKBOX","RADIO","DROPDOWN"]},"recipientEmail":{"type":"string","format":"email"},"page":{"type":"integer","minimum":1},"positionX":{"type":"number","minimum":0,"maximum":100},"positionY":{"type":"number","minimum":0,"maximum":100},"width":{"type":"number","minimum":0,"maximum":100},"height":{"type":"number","minimum":0,"maximum":100}}}},"autoPlaceSignature":{"type":"boolean","description":"When no explicit fields are given, drop a default SIGNATURE on each SIGNER/APPROVER (sends ⇒ billable)."},"storageExpiryDays":{"type":"integer","minimum":1,"description":"Retention window for the stored document (clamped to the esign.storage.max_expiry_days tunable)."},"signingOrder":{"type":"string","enum":["PARALLEL","SEQUENTIAL"],"default":"PARALLEL","description":"Document-level signing mode. PARALLEL (default): all recipients can sign at once. SEQUENTIAL: each recipient is invited only after the prior one acts, ordered by per-recipient signingOrder."},"meta":{"type":"object","properties":{"subject":{"type":"string","maxLength":254},"message":{"type":"string","maxLength":5000},"redirectUrl":{"type":"string","format":"uri","maxLength":2048}}}}}},"multipart/form-data":{"schema":{"type":"object","required":["payload","pdf"],"properties":{"payload":{"type":"string","description":"JSON string of { title, recipients (each may carry signingOrder), fields?, autoPlaceSignature?, storageExpiryDays?, signingOrder? (PARALLEL|SEQUENTIAL), meta? }."},"pdf":{"type":"string","format":"binary"}}}}}},"responses":{"201":{"description":"{ envelopeId, documensoEnvelopeId, status, title, recipients[], storageExpiresAt, createdAt }.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"VALIDATION_ERROR / PDF_REQUIRED."},"401":{"description":"UNAUTHENTICATED OR INVALID_X402_SIGNATURE."},"402":{"description":"PAYMENT_REQUIRED (x402 admit rejection on the billable send path)."},"422":{"description":"TOO_MANY_SIGNERS / PDF_TOO_LARGE / UNKNOWN_RECIPIENT / UPSTREAM_ERROR."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"esigndoc_create"},"get":{"tags":["esigndoc"],"summary":"List envelopes","description":"Paginated, customer-scoped list of envelopes (cached). Query: limit (1–100), offset, status (one of DRAFT/SENT/PENDING/PARTIALLY_SIGNED/COMPLETED/REJECTED/EXPIRED/CANCELLED). Free.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0}},{"name":"status","in":"query","schema":{"type":"string","enum":["DRAFT","SENT","PENDING","PARTIALLY_SIGNED","COMPLETED","REJECTED","EXPIRED","CANCELLED"]}}],"responses":{"200":{"description":"{ data: [envelope, …], total, limit, offset }.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"UNAUTHENTICATED."}},"operationId":"esigndoc_list"}},"/v1/esigndoc/envelopes/from-template":{"post":{"tags":["esigndoc"],"summary":"Send a document for signature from a reusable template","description":"Instantiate an envelope from a stored template, binding recipient placeholders to real addresses. Billable (esign.envelope) ONLY when `sendImmediately: true` (otherwise a free draft). Charge-on-successful-send, no refund. Idempotency-Key honored.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["templateId","recipients"],"properties":{"templateId":{"type":"integer","minimum":1},"title":{"type":"string","minLength":1,"maxLength":255},"recipients":{"type":"array","minItems":1,"items":{"type":"object","required":["id","email"],"properties":{"id":{"type":"integer","description":"The template placeholder recipient id to bind."},"email":{"type":"string","format":"email"},"name":{"type":"string","minLength":1,"maxLength":255}}}},"sendImmediately":{"type":"boolean","description":"Distribute on create (⇒ billable). Otherwise a draft is created free."},"storageExpiryDays":{"type":"integer","minimum":1},"meta":{"type":"object","properties":{"subject":{"type":"string","maxLength":254},"message":{"type":"string","maxLength":5000},"redirectUrl":{"type":"string","format":"uri","maxLength":2048}}}}}}}},"responses":{"201":{"description":"{ envelopeId, documensoEnvelopeId, status, title, recipients[], storageExpiresAt, createdAt }.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"VALIDATION_ERROR."},"401":{"description":"UNAUTHENTICATED OR INVALID_X402_SIGNATURE."},"402":{"description":"PAYMENT_REQUIRED (x402 admit rejection when sendImmediately)."},"422":{"description":"UPSTREAM_ERROR."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"esigndoc_from_template"}},"/v1/esigndoc/envelopes/{id}/send":{"post":{"tags":["esigndoc"],"summary":"Send a prepared draft for signature","description":"Distribute a DRAFT envelope to its recipients. Billable (esign.envelope), charge-on-successful-send, no refund. Optional `meta` overrides the email subject/message/redirect.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"type":"object","properties":{"subject":{"type":"string","maxLength":254},"message":{"type":"string","maxLength":5000},"redirectUrl":{"type":"string","format":"uri","maxLength":2048}}}}}}}},"responses":{"200":{"description":"{ envelopeId, documensoEnvelopeId, status: \"SENT\", title, recipients[], storageExpiresAt, createdAt }.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"UNAUTHENTICATED OR INVALID_X402_SIGNATURE."},"402":{"description":"PAYMENT_REQUIRED (x402 admit rejection)."},"404":{"description":"envelope not found (or not owned)."},"422":{"description":"UPSTREAM_ERROR."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"esigndoc_send"}},"/v1/esigndoc/envelopes/{id}":{"get":{"tags":["esigndoc"],"summary":"Get an envelope (cached)","description":"Cached envelope detail (no upstream call). Free.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Envelope detail.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"UNAUTHENTICATED."},"404":{"description":"envelope not found."}},"operationId":"esigndoc_get"},"delete":{"tags":["esigndoc"],"summary":"Cancel an unsigned document","description":"Cancel the envelope upstream and mark it CANCELLED. Free.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"{ envelopeId, status: \"CANCELLED\" }.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"UNAUTHENTICATED."},"404":{"description":"envelope not found."},"502":{"description":"UPSTREAM_ERROR."}},"operationId":"esigndoc_cancel"}},"/v1/esigndoc/envelopes/{id}/status":{"get":{"tags":["esigndoc"],"summary":"Check who has signed","description":"Re-poll the upstream signer state and self-heal the cache. Free.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"{ envelopeId, documensoEnvelopeId, status, title, recipients[] }.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"UNAUTHENTICATED."},"404":{"description":"envelope not found."},"502":{"description":"UPSTREAM_ERROR."}},"operationId":"esigndoc_status"}},"/v1/esigndoc/envelopes/{id}/download":{"get":{"tags":["esigndoc"],"summary":"Download the PDF (binary)","description":"Stream the signed (default) or original PDF as raw application/pdf. Query: version=signed|original. Free.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"version","in":"query","schema":{"type":"string","enum":["signed","original"]}}],"responses":{"200":{"description":"The PDF bytes.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"UNAUTHENTICATED."},"404":{"description":"envelope or document not found."},"502":{"description":"UPSTREAM_ERROR."}},"operationId":"downloadEsigndocEnvelope"}},"/v1/esigndoc/envelopes/{id}/signed-document":{"get":{"tags":["esigndoc"],"summary":"Get the signed copy","description":"The MCP/agent-friendly counterpart to /download: returns the signed (or original) PDF in the uniform cputools OUTPUT envelope — { output: { inline?: <base64 ≤ threshold> | outputKey + outputUrl (presigned GET, when larger), sizeBytes, contentType } }. Query: version=signed|original. Free.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"version","in":"query","schema":{"type":"string","enum":["signed","original"]}}],"responses":{"200":{"description":"{ envelopeId, status, version, output }.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"UNAUTHENTICATED."},"404":{"description":"envelope or document not found."},"502":{"description":"UPSTREAM_ERROR."}},"operationId":"esigndoc_get_signed_document"}},"/v1/esigndoc/envelopes/{id}/resend":{"post":{"tags":["esigndoc"],"summary":"Chase recipients who have not signed","description":"Re-distribute the signing request to recipients who have not yet signed. Free.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"{ resent, recipients: [{ email, name, signingUrl }] }.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"UNAUTHENTICATED."},"404":{"description":"envelope or document not found."},"502":{"description":"UPSTREAM_ERROR."}},"operationId":"esigndoc_resend"}},"/v1/esigndoc/envelopes/{id}/fields":{"post":{"tags":["esigndoc"],"summary":"Place signature and approval boxes in the document","description":"Add field placements to a DRAFT envelope. Each field references a declared recipient by email. Free.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["fields"],"properties":{"fields":{"type":"array","minItems":1,"maxItems":200,"items":{"type":"object","required":["type","recipientEmail","page","positionX","positionY","width","height"],"properties":{"type":{"type":"string","enum":["SIGNATURE","INITIALS","NAME","EMAIL","DATE","TEXT","NUMBER","CHECKBOX","RADIO","DROPDOWN"]},"recipientEmail":{"type":"string","format":"email"},"page":{"type":"integer","minimum":1},"positionX":{"type":"number","minimum":0,"maximum":100},"positionY":{"type":"number","minimum":0,"maximum":100},"width":{"type":"number","minimum":0,"maximum":100},"height":{"type":"number","minimum":0,"maximum":100}}}}}}}}},"responses":{"200":{"description":"{ fields: [{ id, recipientId }] }.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"VALIDATION_ERROR."},"401":{"description":"UNAUTHENTICATED."},"404":{"description":"envelope not found."},"422":{"description":"NOT_A_DRAFT / UNKNOWN_RECIPIENT."},"502":{"description":"UPSTREAM_ERROR."}},"operationId":"placeEsigndocFields"}},"/v1/esigndoc/envelopes/{id}/fields/{fid}":{"delete":{"tags":["esigndoc"],"summary":"Remove a signature or approval box","description":"Delete a field from a DRAFT envelope. The field must belong to this caller-owned envelope (IDOR-guarded). Free.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"fid","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"{ success: true }.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"VALIDATION_ERROR (invalid field id)."},"401":{"description":"UNAUTHENTICATED."},"404":{"description":"envelope or field not found."},"422":{"description":"NOT_A_DRAFT."},"502":{"description":"UPSTREAM_ERROR."}},"operationId":"removeEsigndocField"}},"/v1/esigndoc/templates":{"post":{"tags":["esigndoc"],"summary":"Save a reusable contract template","description":"Create a reusable template from a multipart PDF (`pdf` File + a `payload` JSON string { title, recipients }). Field placeholders detected from the PDF are returned. Free.","security":[{"apiKey":[]},{"walletJwt":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["payload","pdf"],"properties":{"payload":{"type":"string","description":"JSON string of { title, recipients: [{ email, name, role? }] }."},"pdf":{"type":"string","format":"binary"}}}}}},"responses":{"201":{"description":"{ templateId, documensoTemplateId, title, recipients[], fields[], detectedFromPlaceholders, createdAt, updatedAt }.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"PDF_REQUIRED / VALIDATION_ERROR."},"401":{"description":"UNAUTHENTICATED."},"502":{"description":"UPSTREAM_ERROR."}},"operationId":"createEsigndocTemplate"},"get":{"tags":["esigndoc"],"summary":"List templates","description":"Paginated, customer-scoped list of templates (cached). Query: limit, offset. Free.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0}}],"responses":{"200":{"description":"{ data: [template, …], limit, offset }.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"UNAUTHENTICATED."}},"operationId":"esigndoc_list_templates"}},"/v1/esigndoc/templates/{id}":{"get":{"tags":["esigndoc"],"summary":"Get a template","description":"Template detail incl. recipients + detected fields. Free.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Template detail.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"UNAUTHENTICATED."},"404":{"description":"template not found."},"502":{"description":"UPSTREAM_ERROR."}},"operationId":"getEsigndocTemplate"},"put":{"tags":["esigndoc"],"summary":"Update a template","description":"Update a template title (and meta). Free.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":255},"meta":{"type":"object","properties":{"subject":{"type":"string","maxLength":254},"message":{"type":"string","maxLength":5000}}}}}}}},"responses":{"200":{"description":"{ templateId, documensoTemplateId, title }.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"VALIDATION_ERROR."},"401":{"description":"UNAUTHENTICATED."},"404":{"description":"template not found."},"502":{"description":"UPSTREAM_ERROR."}},"operationId":"updateEsigndocTemplate"},"delete":{"tags":["esigndoc"],"summary":"Delete a template","description":"Delete a template upstream and locally. Free.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"{ success: true }.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"UNAUTHENTICATED."},"404":{"description":"template not found."},"502":{"description":"UPSTREAM_ERROR."}},"operationId":"deleteEsigndocTemplate"}},"/v1/idverify/verifications":{"post":{"tags":["idverify"],"summary":"Verify a government ID, run a liveness check, or match a face (by type)","description":"$0.10/verification. Full KYC in one call — government-ID verification, liveness detection, and face-to-ID matching. Pair with /v1/esigndoc for verify-then-sign and /v1/screening for sanctions. One of 155 Relaystation tools on one balance — relaystation.ai\n\nInitialize a hosted identity verification. Returns a `hostedUrl` the end-user opens to complete capture; the outcome arrives via webhook (verification.* events) or GET …/status poll. Billable (idverify.verification), charge-on-attempt — the charge stands once the upstream verification is initialized; the async verdict (VERIFIED/REJECTED/FAILED/EXPIRED) never refunds. A throw before initialize (upstream unreachable, soft-gate rejection) reverses the charge. Idempotency-Key honored.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["document","liveness","biometric"],"description":"Subject to the idverify.allowed_types tunable — a disallowed type is a free 422 TYPE_NOT_ALLOWED."},"addons":{"type":"object","properties":{"aml":{"type":"boolean","description":"Screen the OCR'd holder name against sanctions + PEP (our engine, Briefs 197/198) once the verification completes; the result attaches as `amlScreening` on status/detail. Gated by the idverify.aml.enabled tunable (a request while disabled → free 422 AML_NOT_ENABLED). A one-time addon fee (idverify.aml_addon) applies when the screening runs."}}},"sandbox":{"type":"boolean"},"metadata":{"type":"object","description":"Caller metadata echoed back; serialized ≤ 4 KB."}}}}}},"responses":{"201":{"description":"{ id, idswyftVerificationId, customerId, status, type, hostedUrl, amountMicros, createdAt, completedAt, updatedAt, metadata, amlScreening }.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"VALIDATION_ERROR."},"401":{"description":"UNAUTHENTICATED OR INVALID_X402_SIGNATURE."},"402":{"description":"PAYMENT_REQUIRED (x402 admit rejection)."},"422":{"description":"TYPE_NOT_ALLOWED / AML_NOT_ENABLED / UPSTREAM_ERROR."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"idverify_create"},"get":{"tags":["idverify"],"summary":"List verifications","description":"Paginated, customer-scoped list (cached). Query: limit (1–100), offset, status (PENDING/IN_REVIEW/VERIFIED/REJECTED/FAILED/EXPIRED). Free.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0}},{"name":"status","in":"query","schema":{"type":"string","enum":["PENDING","IN_REVIEW","VERIFIED","REJECTED","FAILED","EXPIRED"]}}],"responses":{"200":{"description":"{ items: [verification, …], pagination: { total, limit, offset } }.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"VALIDATION_ERROR (invalid query)."},"401":{"description":"UNAUTHENTICATED."}},"operationId":"idverify_list"}},"/v1/idverify/verifications/{id}/status":{"get":{"tags":["idverify"],"summary":"Check verification status","description":"Re-poll the upstream provider for the live verdict and self-heal the cache. Free (an AML addon, if requested, carries its own one-time fee charged when the screening runs). When `addons.aml` was requested, the first poll after the OCR name is available runs the sanctions+PEP screen and attaches `amlScreening`.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"{ id, idswyftVerificationId, status, finalResult, riskScore, failureReason, rejectionReason, amlScreening, updatedAt }. amlScreening: { ran:true, screenedName, riskLevel (none|low|medium|high), hitCount, listsChecked, matches[], screenedAt } | { ran:false, reason } | null (pending). NEVER implies \"clear\" unless ran:true.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"UNAUTHENTICATED."},"404":{"description":"verification not found."},"503":{"description":"IDSWYFT_UNAVAILABLE (verification provider unavailable)."}},"operationId":"idverify_status"}},"/v1/idverify/verifications/{id}":{"get":{"tags":["idverify"],"summary":"Get a verification (cached)","description":"Cached verification detail (no upstream call). Free.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Verification detail.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"UNAUTHENTICATED."},"404":{"description":"verification not found."}},"operationId":"getIdverifyVerification"}},"/v1/screening/match":{"post":{"tags":["screening"],"summary":"Screen a name against sanctions + PEP watchlists","description":"$0.01/screen. Screen any name against OFAC, EU, and UK sanctions (UN included) plus Wikidata-derived PEP watchlists — fuzzy matching, instant JSON verdict. Compliance in one call, one of 155 Relaystation tools on one balance — relaystation.ai\n\nFuzzy-match a person or organization name against international sanctions lists (OFAC SDN/Consolidated, EU consolidated, UK FCDO — UN coverage rides along inside these) AND politically-exposed-persons (PEP) data derived from Wikidata. Billable (screening.match), charge-on-attempt — the charge stands whether or not there is a hit. Trigram similarity handles aliases, transliteration, and name-order. Use `lists` to scope to sanctions or PEP (default both). Idempotency-Key honored. Source attribution (EU reuse policy / UK OGL v3.0 / Wikidata CC0) is returned in every response. PEP coverage (v1) is national-level heads of state/government, cabinet ministers, and senior judges (current + recently in office) from Wikidata — NOT a complete FATF PEP universe (standalone legislators, central-bank governors, ambassadors, party officials, SOE executives, and relatives/close associates are not yet covered).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":512},"type":{"type":"string","enum":["person","entity"],"description":"Optional entity-type filter."},"dob":{"type":"string","description":"Optional date of birth (reserved; not yet used in scoring)."},"nationality":{"type":"string","description":"Optional nationality (reserved; not yet used in scoring)."},"lists":{"type":"array","items":{"type":"string","enum":["sanctions","pep"]},"description":"Categories to screen against. Default both [sanctions, pep]; scope to one to narrow."},"threshold":{"type":"number","minimum":0,"maximum":1,"description":"Similarity cut (0..1). Default from the screening.match.threshold tunable (0.7)."}}}}}},"responses":{"200":{"description":"{ matches: [{ name, matchedName, matchedVia (primary|alias), category (sanctions|pep), list, source, score, program?, entityType }], riskLevel (none|low|medium|high), listsChecked, attribution }.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"VALIDATION_ERROR."},"401":{"description":"UNAUTHENTICATED OR INVALID_X402_SIGNATURE."},"402":{"description":"PAYMENT_REQUIRED (x402 admit rejection)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"screen_name"}},"/v1/location/geocode":{"post":{"tags":["location"],"summary":"Geocode a free-text address","description":"$0.005/lookup. Geocode a free-text address to coordinates (AWS Location Service) — lat/lng plus normalized address. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nGeocode a free-text address/place description to coordinates (AWS Location Service). Body: { query, maxResults?: 1-20, language?, biasPosition?: [lng,lat], countries?: [ISO-3166 alpha-3] }. FLAT-billed per call (location.price.geocode tunable). Returns { results: [...] } — an empty array when nothing matches (never an error). Idempotency-Key required on billable calls.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"type":"string","minLength":1,"maxLength":512},"maxResults":{"type":"integer","minimum":1,"maximum":20},"language":{"type":"string"},"biasPosition":{"type":"array","items":{"type":"number"},"minItems":2,"maxItems":2,"description":"[longitude, latitude] (WGS-84)."},"countries":{"type":"array","items":{"type":"string","minLength":3,"maxLength":3}}}}}}},"responses":{"200":{"description":"{ results: [{ Title, Address, Position, ... }] } (AWS Location ResultItems; empty array = no match)."},"402":{"description":"Payment required (x402 challenge)."},"422":{"description":"QUERY_TOO_LONG / UPSTREAM_REJECTED (charge reversed)."},"502":{"description":"UPSTREAM_ERROR (charge reversed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"geocode"}},"/v1/location/reverse-geocode":{"post":{"tags":["location"],"summary":"Reverse-geocode coordinates to an address","description":"$0.005/lookup. Reverse-geocode coordinates to the nearest address — lat/lng in, street address out. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nReverse-geocode WGS-84 coordinates to the nearest address(es) (AWS Location Service). Body: { position: [lng,lat], maxResults?: 1-20, radiusMeters?, language? }. FLAT-billed per call (location.price.reverse_geocode tunable). Returns { results: [...] } — empty when nothing is nearby.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["position"],"properties":{"position":{"type":"array","items":{"type":"number"},"minItems":2,"maxItems":2,"description":"[longitude, latitude] (WGS-84)."},"maxResults":{"type":"integer","minimum":1,"maximum":20},"radiusMeters":{"type":"integer","minimum":1,"maximum":100000},"language":{"type":"string"}}}}}},"responses":{"200":{"description":"{ results: [...] } (empty array = nothing nearby)."},"402":{"description":"Payment required (x402 challenge)."},"422":{"description":"UPSTREAM_REJECTED (charge reversed)."},"502":{"description":"UPSTREAM_ERROR (charge reversed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"reverse_geocode"}},"/v1/location/place-search":{"post":{"tags":["location"],"summary":"Search places / points of interest","description":"$0.01/search. Free-text place and point-of-interest search — names, addresses, coordinates back as JSON. 155 tools, one balance — relaystation.ai\n\nFree-text place/POI search (AWS Location Service SearchText). Body: { query, biasPosition?: [lng,lat], maxResults?: 1-20, language?, countries? }. FLAT-billed per call (location.price.place_search tunable). Returns { results: [...] } — empty when nothing matches.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"type":"string","minLength":1,"maxLength":512},"biasPosition":{"type":"array","items":{"type":"number"},"minItems":2,"maxItems":2},"maxResults":{"type":"integer","minimum":1,"maximum":20},"language":{"type":"string"},"countries":{"type":"array","items":{"type":"string","minLength":3,"maxLength":3}}}}}}},"responses":{"200":{"description":"{ results: [...] } (empty array = no match)."},"402":{"description":"Payment required (x402 challenge)."},"422":{"description":"QUERY_TOO_LONG / UPSTREAM_REJECTED (charge reversed)."},"502":{"description":"UPSTREAM_ERROR (charge reversed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"place_search"}},"/v1/location/route":{"post":{"tags":["location"],"summary":"Calculate a route between two points","description":"$0.01/route. Calculate a route between two points — distance, duration, and geometry back as JSON. 155 tools, one balance — relaystation.ai\n\nCalculate a route (AWS Location Service CalculateRoutes). Body: { origin: [lng,lat], destination: [lng,lat], travelMode?: \"Car\"|\"Truck\"|\"Pedestrian\"|\"Scooter\", departNow? }. FLAT-billed per call (location.price.route tunable). Returns the route set (legs, distance meters, duration seconds, encoded geometry).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["origin","destination"],"properties":{"origin":{"type":"array","items":{"type":"number"},"minItems":2,"maxItems":2},"destination":{"type":"array","items":{"type":"number"},"minItems":2,"maxItems":2},"travelMode":{"type":"string","enum":["Car","Truck","Pedestrian","Scooter"]},"departNow":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Route set (Routes[].Legs with distance/duration/geometry)."},"402":{"description":"Payment required (x402 challenge)."},"422":{"description":"UPSTREAM_REJECTED — e.g. no routable path (charge reversed)."},"502":{"description":"UPSTREAM_ERROR (charge reversed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"route"}},"/v1/doc/ocr-layout":{"post":{"tags":["docai"],"summary":"OCR a document with line/word layout","description":"$0.010/document. OCR a document with line- and word-level layout coordinates (Textract) — position-aware text for downstream parsing. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nOCR a SINGLE-PAGE document (image or 1-page PDF ≤ 5 MB) with line+word layout geometry (AWS Textract DetectDocumentText). Body: { file: <source> }. Multi-page/oversize → 422 pre-charge (multi-page analysis arrives with the async lane). FLAT-billed per document (docai.price.detect_text tunable). Returns { blocks: [...] } (PAGE/LINE/WORD blocks).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."}}}}}},"responses":{"200":{"description":"{ blocks: [...] } (Textract Block list)."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"422":{"description":"DOC_TOO_LARGE / DOC_MULTI_PAGE / UPSTREAM_REJECTED."},"502":{"description":"UPSTREAM_ERROR (charge reversed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"doc_ocr_layout"}},"/v1/doc/extract-form":{"post":{"tags":["docai"],"summary":"Extract form key→value pairs","description":"$0.28/document. Extract form fields as key→value pairs from scanned or digital documents (Textract Forms) — structured JSON out, no template needed. 155 tools, one balance — relaystation.ai\n\nExtract form fields (key→value pairs) from a SINGLE-PAGE document (AWS Textract AnalyzeDocument FORMS). Body: { file: <source> } (image or 1-page PDF ≤ 5 MB). FLAT-billed per document (docai.price.extract_form tunable). Returns { fields: [{key, value, confidence}], blocks }.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."}}}}}},"responses":{"200":{"description":"{ fields: [...], blocks: [...] }."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"422":{"description":"DOC_TOO_LARGE / DOC_MULTI_PAGE / UPSTREAM_REJECTED."},"502":{"description":"UPSTREAM_ERROR (charge reversed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"extract_form"}},"/v1/doc/extract-tables":{"post":{"tags":["docai"],"summary":"Extract tables from a document","description":"$0.08/document. Pull tables out of a document as structured rows and columns (Textract Tables) — not text soup. 155 tools, one balance — relaystation.ai\n\nExtract tables from a SINGLE-PAGE document (AWS Textract AnalyzeDocument TABLES). Body: { file: <source> } (image or 1-page PDF ≤ 5 MB). FLAT-billed per document (docai.price.extract_tables tunable). Returns { tables: [{rows: [[cell,...]]}], blocks }.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."}}}}}},"responses":{"200":{"description":"{ tables: [...], blocks: [...] }."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"422":{"description":"DOC_TOO_LARGE / DOC_MULTI_PAGE / UPSTREAM_REJECTED."},"502":{"description":"UPSTREAM_ERROR (charge reversed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"extract_tables"}},"/v1/doc/parse-invoice":{"post":{"tags":["docai"],"summary":"Parse an invoice/receipt into structured fields","description":"$0.055/document. Parse invoices and receipts into summary fields plus line items (Textract AnalyzeExpense) — vendor, totals, dates, SKUs. 155 tools, one balance — relaystation.ai\n\nParse an invoice or receipt into summary fields + line items (AWS Textract AnalyzeExpense). Body: { file: <source> } (image or 1-page PDF ≤ 5 MB). FLAT-billed per document (docai.price.parse_invoice tunable). Returns { documents: [{summaryFields, lineItems}] }.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."}}}}}},"responses":{"200":{"description":"{ documents: [...] }."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"422":{"description":"DOC_TOO_LARGE / DOC_MULTI_PAGE / UPSTREAM_REJECTED."},"502":{"description":"UPSTREAM_ERROR (charge reversed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"parse_invoice"}},"/v1/doc/parse-id":{"post":{"tags":["docai"],"summary":"Parse a government identity document","description":"$0.05/document. Parse a government ID (passport, license) into typed fields (Textract AnalyzeID). Parsing only — for verification + liveness use /v1/idverify. 155 tools, one balance — relaystation.ai\n\nParse a government identity document (driver's license / passport page image ≤ 5 MB) into typed fields (AWS Textract AnalyzeID). Body: { file: <source> }. FLAT-billed per document (docai.price.parse_id tunable). Returns { documents: [{fields: [{type, value, confidence}]}] }. This is a document-parsing utility, NOT identity verification — for KYC use POST /v1/idverify/verifications.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."}}}}}},"responses":{"200":{"description":"{ documents: [...] }."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"422":{"description":"DOC_TOO_LARGE / DOC_MULTI_PAGE / UPSTREAM_REJECTED."},"502":{"description":"UPSTREAM_ERROR (charge reversed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"parse_id"}},"/v1/doc/ask-document":{"post":{"tags":["docai"],"summary":"Ask natural-language questions of a document","description":"$0.08/document. Ask natural-language questions of a document and get grounded answers (Textract Queries) — no OCR pipeline to build. 155 tools, one balance — relaystation.ai\n\nAsk up to 15 natural-language questions of a SINGLE-PAGE document (AWS Textract AnalyzeDocument QUERIES). Body: { file: <source>, questions: [string, ...] } (image or 1-page PDF ≤ 5 MB). FLAT-billed per document (docai.price.ask_document tunable). Returns { answers: [{question, answer, confidence}] } — an unanswerable question returns no answer entry, never an error.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","questions"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"questions":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":15}}}}}},"responses":{"200":{"description":"{ answers: [...] }."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"422":{"description":"DOC_TOO_LARGE / DOC_MULTI_PAGE / UPSTREAM_REJECTED."},"502":{"description":"UPSTREAM_ERROR (charge reversed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"ask_document"}},"/v1/doc/analyze-async":{"post":{"tags":["docai"],"summary":"Analyze a multi-page document asynchronously","description":"Analyze a MULTI-PAGE PDF asynchronously (AWS Textract StartDocumentAnalysis). Submit returns a job ticket immediately; poll GET /v1/jobs/{id} or receive the job.completed webhook when the parsed result is ready. Body: { file: <source> (PDF only, up to docai.async_max_pages / docai.async_max_doc_bytes), features: [\"FORMS\"|\"TABLES\"|\"QUERIES\"] (≥1), questions?: [string] (with QUERIES), deliver?: { batonId } (write the result into an owned baton) }. Billed per PAGE (ceiling = pages × docai.price.async_per_page_micros, counted at submit; the x402 rail settles the exact ceiling at submit, the balance rail captures the actual at completion). Returns { ticket, job_id, status: \"running\", ceiling_micros }.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","features"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"features":{"type":"array","items":{"type":"string","enum":["FORMS","TABLES","QUERIES"]},"minItems":1},"questions":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":15},"deliver":{"type":"object","required":["batonId"],"properties":{"batonId":{"type":"string","minLength":1}}}}}}}},"responses":{"200":{"description":"{ ticket, job_id, status: \"running\", ceiling_micros }."},"402":{"description":"Payment required (x402 challenge carrying the counted ceiling)."},"404":{"description":"A storage-ref inputKey (or deliver baton) is not owned by the caller."},"422":{"description":"DOC_NOT_PDF / DOC_TOO_LARGE / DOC_PARSE_FAILED."},"502":{"description":"UPSTREAM_START_FAILED (no charge; key freed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"doc_analyze_async"}},"/v1/jobs/{id}":{"get":{"tags":["docai"],"summary":"Poll an async job","description":"Poll an async job by id (FREE read). Returns { ticket, job_id, status: \"submitted\"|\"running\"|\"succeeded\"|\"failed\"|\"expired\", service_key, ceiling_micros, actual_micros?, result? (when succeeded), error_reason?, created_at, completed_at? }. Customer-scoped: a wrong-owner or unknown id returns 404.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The job ticket (job_<uuid>)."}],"responses":{"200":{"description":"The job status + result-when-done."},"400":{"description":"Invalid job id."},"401":{"description":"Authentication required."},"404":{"description":"Job not found (or not owned by the caller)."}},"operationId":"job_status"}},"/v1/vision/detect-labels":{"post":{"tags":["vision"],"summary":"Detect objects/scenes in an image","description":"$0.005/image. Detect objects, scenes, and concepts in an image (Rekognition) — labels with confidence scores. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nDetect objects, scenes, and concepts in an image ≤ 5 MB (AWS Rekognition DetectLabels). Body: { file: <source>, maxLabels?: 1-100, minConfidence?: 0-100 }. FLAT-billed per image (vision.price.detect_labels tunable). Returns { labels: [...] } — empty when nothing clears the bar.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"maxLabels":{"type":"integer","minimum":1,"maximum":100},"minConfidence":{"type":"number","minimum":0,"maximum":100}}}}}},"responses":{"200":{"description":"{ labels: [{Name, Confidence, Instances, Parents}] }."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"422":{"description":"IMAGE_TOO_LARGE / UPSTREAM_REJECTED (bad image)."},"502":{"description":"UPSTREAM_ERROR (charge reversed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"detect_labels"}},"/v1/vision/moderate":{"post":{"tags":["vision"],"summary":"Detect unsafe content in an image","description":"$0.005/image. Flag unsafe or inappropriate image content (Rekognition moderation) — categories with confidence. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nDetect unsafe/inappropriate content in an image ≤ 5 MB (AWS Rekognition DetectModerationLabels). Body: { file: <source>, minConfidence?: 0-100 }. FLAT-billed per image (vision.price.moderate_image tunable). Returns { labels: [...] } — an EMPTY array means nothing was flagged.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"minConfidence":{"type":"number","minimum":0,"maximum":100}}}}}},"responses":{"200":{"description":"{ labels: [{Name, ParentName, Confidence}] } (empty = clean)."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"422":{"description":"IMAGE_TOO_LARGE / UPSTREAM_REJECTED (bad image)."},"502":{"description":"UPSTREAM_ERROR (charge reversed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"moderate_image"}},"/v1/vision/detect-faces":{"post":{"tags":["vision"],"summary":"Detect faces + attributes in an image","description":"$0.005/image. Detect faces with attributes — age range, emotions, pose, landmarks (Rekognition). 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nDetect faces with attributes (age range, emotions, pose, quality) in an image ≤ 5 MB (AWS Rekognition DetectFaces). Body: { file: <source>, attributes?: \"DEFAULT\"|\"ALL\" }. FLAT-billed per image (vision.price.detect_faces tunable). Returns { faces: [...] } — empty when no faces.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"attributes":{"type":"string","enum":["DEFAULT","ALL"]}}}}}},"responses":{"200":{"description":"{ faces: [...] }."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"422":{"description":"IMAGE_TOO_LARGE / UPSTREAM_REJECTED (bad image)."},"502":{"description":"UPSTREAM_ERROR (charge reversed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"detect_faces"}},"/v1/vision/compare-faces":{"post":{"tags":["vision"],"summary":"Compare faces between two images","description":"Compare the largest face in a source image against faces in a target image (AWS Rekognition CompareFaces). Body: { sourceFile: <source>, targetFile: <source>, similarityThreshold?: 0-100 } (each image ≤ 5 MB). FLAT-billed per call (vision.price.compare_faces tunable). Returns { matches: [...], unmatchedFaces: [...] } — empty matches when nothing clears the threshold.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sourceFile","targetFile"],"properties":{"sourceFile":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"targetFile":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"similarityThreshold":{"type":"number","minimum":0,"maximum":100}}}}}},"responses":{"200":{"description":"{ matches: [{Similarity, Face}], unmatchedFaces }."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"422":{"description":"IMAGE_TOO_LARGE / UPSTREAM_REJECTED (bad image / no face in source)."},"502":{"description":"UPSTREAM_ERROR (charge reversed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"compare_faces"}},"/v1/vision/detect-celebrities":{"post":{"tags":["vision"],"summary":"Recognize celebrities in an image","description":"$0.005/image. Recognize celebrities in an image (Rekognition) — names with confidence scores. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRecognize celebrities in an image ≤ 5 MB (AWS Rekognition RecognizeCelebrities). Body: { file: <source> }. FLAT-billed per image (vision.price.detect_celebrities tunable). Returns { celebrities: [...], unrecognizedFaces } — empty when nobody is recognized.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."}}}}}},"responses":{"200":{"description":"{ celebrities: [...], unrecognizedFaces: [...] }."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"422":{"description":"IMAGE_TOO_LARGE / UPSTREAM_REJECTED (bad image)."},"502":{"description":"UPSTREAM_ERROR (charge reversed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"detect_celebrities"}},"/v1/vision/detect-ppe":{"post":{"tags":["vision"],"summary":"Detect protective equipment on persons","description":"$0.005/image. Detect protective equipment — face, hand, head covers — on people in an image. Site-safety audits by API. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nDetect personal protective equipment (face/hand/head covers) on persons in an image ≤ 5 MB (AWS Rekognition DetectProtectiveEquipment). Body: { file: <source>, requiredEquipment?: [\"FACE_COVER\"|\"HAND_COVER\"|\"HEAD_COVER\"], minConfidence?: 0-100 }. FLAT-billed per image (vision.price.detect_ppe tunable). Returns { persons: [...], summary } — empty persons when none detected.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"requiredEquipment":{"type":"array","items":{"type":"string","enum":["FACE_COVER","HAND_COVER","HEAD_COVER"]}},"minConfidence":{"type":"number","minimum":0,"maximum":100}}}}}},"responses":{"200":{"description":"{ persons: [...], summary: {...} }."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"422":{"description":"IMAGE_TOO_LARGE / UPSTREAM_REJECTED (bad image)."},"502":{"description":"UPSTREAM_ERROR (charge reversed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"detect_ppe"}},"/v1/llm/embed":{"post":{"tags":["llm"],"summary":"Generate text embedding vectors","description":"$0.002 per 10 KB. Generate text embeddings, single or batch — pairs with the baton vector index for pay-per-query RAG memory. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nGenerate embedding vectors for one string or a batch (AWS Bedrock; amazon.titan-embed-text-v2 by default — the model id is the llm.embed.model_id tunable). Body: { input: string | string[] (batch ≤ llm.embed.max_batch, seed 20), dimensions?: 256|512|1024, normalize?: bool }. METERED per started 10 KB of total input (llm.embed.per_10kb_micros tunable); total input ≤ llm.embed.max_input_kb (seed 100). Returns { embeddings: [{index, vector, inputTokens}], model, dimensions }.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["input"],"properties":{"input":{"anyOf":[{"type":"string","minLength":1},{"type":"array","items":{"type":"string","minLength":1},"minItems":1,"maxItems":100}]},"dimensions":{"type":"integer","enum":[256,512,1024]},"normalize":{"type":"boolean"}}}}}},"responses":{"200":{"description":"{ embeddings: [{index, vector, inputTokens}], model, dimensions }."},"402":{"description":"Payment required (x402 challenge with the metered ceiling)."},"422":{"description":"INPUT_TOO_LARGE / BATCH_TOO_LARGE (pre-charge) / UPSTREAM_REJECTED (charge reversed)."},"502":{"description":"UPSTREAM_ERROR (charge reversed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"embed"}},"/v1/data/sql-large":{"post":{"tags":["cputools"],"summary":"Run SQL over a large stored file (Athena)","description":"$0.01 per 100 MB scanned. Run SQL over huge stored datasets — pay only for bytes scanned, ≈100 MB per penny. Warehouse-scale queries without the warehouse. 155 tools, one balance — relaystation.ai\n\nRun SQL over a LARGE CSV already on Relaystation storage via AWS Athena — for datasets past the in-process /v1/data/sql ceiling. Body: { file: {inputKey}|{batonId}, sql (SELECT over a table named `input`), format?: 'csv'|'json' }. The scannable size is capped by the data.sql_large.max_scan_mb tunable (seed 500; over-cap → 422 pre-charge). METERED on Athena's actually-scanned bytes (data.sql_large.per_100mb_scanned_micros per started 100 MB): you authorize the cap-derived ceiling, the charge settles at actual (authorize_capture). Returns rows in the uniform JSON output envelope (inline, or a storage ref when large).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","sql"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"sql":{"type":"string","minLength":1,"maxLength":20000,"description":"SELECT-only; your file is the table named `input`."},"format":{"type":"string","enum":["csv","json"]}}}}}},"responses":{"200":{"description":"Query result (uniform JSON output envelope) + scan/billing receipt fields."},"402":{"description":"Payment required (x402 challenge with the cap-derived ceiling)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"422":{"description":"INPUT_TOO_LARGE (over max_scan_mb) / SQL_INVALID / UPSTREAM_REJECTED / QUERY_TIMEOUT."},"502":{"description":"UPSTREAM_ERROR (charge reversed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"data_sql_large"}},"/v1/baton/{id}/vectors":{"post":{"tags":["baton"],"summary":"Batch-write vectors into a vector baton's index","description":"$0.002/batch of 10. Batch-write embeddings into a prepaid vector index inside a baton — Relaystation's durable shared-storage object — agent RAG memory without running a vector DB. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nBatch-write [{id, vector, metadata?}] into a VECTOR-preset baton's prepaid index (S3 Vectors). Each vector's length must equal the baton's frozen dims (create with preset 'vector' + vector:{dims}). Owner/editor or a write X-Baton-Token. Writes draw the baton's prepaid SIZE quota (a quota event, not a ledger row); the op is METERED per started batch (baton.vector.price.put_per_batch_micros per baton.vector.put_batch_unit vectors). Metadata (≤ baton.vector.max_metadata_bytes each) typically carries the chunk text for RAG.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["vectors"],"properties":{"vectors":{"type":"array","minItems":1,"maxItems":1000,"items":{"type":"object","required":["id","vector"],"properties":{"id":{"type":"string","minLength":1,"maxLength":256},"vector":{"type":"array","items":{"type":"number"},"minItems":1,"maxItems":4096},"metadata":{"type":"object"}}}}}}}}},"responses":{"200":{"description":"{ batonId, written, bytesCharged, dims }."},"402":{"description":"Payment required (x402 challenge)."},"403":{"description":"INSUFFICIENT_ROLE (a reader writing) / token scope."},"404":{"description":"Baton not found (or no relationship — no existence oracle)."},"409":{"description":"SIZE_EXHAUSTED / WRITES_EXHAUSTED (prepaid quota)."},"422":{"description":"NOT_A_VECTOR_BATON / VECTOR_DIMS_MISMATCH / BATCH_TOO_LARGE / METADATA_TOO_LARGE."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"vector_put"}},"/v1/baton/{id}/vectors/query":{"post":{"tags":["baton"],"summary":"Top-k similarity search over a vector baton","description":"$0.005/query (+embed units for raw text). Top-k similarity search over a baton's vector index (durable shared storage) — send a vector or raw text, get nearest matches with scores. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nQuery a VECTOR-preset baton's index with { vector } OR { text } (exactly one; text is embedded inline via the live embed model and its per-10KB units fold into this call's price). topK ≤ baton.vector.max_topk. Owner/reader/editor or a read X-Baton-Token; results draw the prepaid EGRESS quota. FLAT-billed (baton.vector.price.query_micros). Returns { matches: [{id, distance, metadata}] } — empty when the index holds no vectors.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"vector":{"type":"array","items":{"type":"number"},"minItems":1,"maxItems":4096},"text":{"type":"string","minLength":1,"maxLength":65536},"topK":{"type":"integer","minimum":1,"maximum":1000}}}}}},"responses":{"200":{"description":"{ batonId, matches: [...], embedded? }."},"402":{"description":"Payment required (x402 challenge)."},"404":{"description":"Baton not found (or no relationship)."},"409":{"description":"EGRESS_EXHAUSTED (prepaid quota; charge reversed)."},"422":{"description":"NOT_A_VECTOR_BATON / VECTOR_DIMS_MISMATCH / TOPK_TOO_LARGE."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"vector_query"}},"/v1/rag/answer":{"post":{"tags":["baton"],"summary":"Grounded answer over a vector baton (RAG in one call)","description":"The full RAG loop under ONE charge: embeds your question, retrieves top-k chunks from the baton's index, and answers with the live llm answer task using ONLY those chunks (they are treated as untrusted data). Body: { batonId, question, topK?, tier?: budget|value|best }. Owner/reader or a read X-Baton-Token. authorize_capture settle-at-actual: you authorize a ceiling (embed units + query flat + the tier's published LLM ceiling + rag.answer.margin_bp), you pay the actual component costs + margin. Returns { answer, sources: [{id, distance}], usage }.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["batonId","question"],"properties":{"batonId":{"type":"string","minLength":1,"maxLength":128},"question":{"type":"string","minLength":1,"maxLength":65536},"topK":{"type":"integer","minimum":1,"maximum":1000},"tier":{"type":"string","enum":["budget","value","best"]}}}}}},"responses":{"200":{"description":"{ answer, sources, usage (incl. chargedMicros ≤ ceilingMicros) }."},"402":{"description":"Payment required (x402 challenge with the ceiling)."},"404":{"description":"Baton not found (or no relationship)."},"409":{"description":"EGRESS_EXHAUSTED (prepaid quota; charge reversed)."},"422":{"description":"NOT_A_VECTOR_BATON / TOPK_TOO_LARGE / INPUT_TOO_LARGE / UPSTREAM_ERROR (reversed)."},"503":{"description":"OP_DISABLED / UPSTREAM_ERROR 5xx (reversed)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"rag_answer"}},"/v1/bundles/rag-ingest":{"post":{"tags":["baton"],"summary":"One-call RAG ingest (chunk → embed → vector-write; create-if-absent)","description":"~$0.01–$0.02/run, bills the sum of steps. One-call RAG ingest: chunk text, embed every chunk, write the vectors into a durable vector index (created for you on first use) — agent memory that persists across sessions, no vector DB to run. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nChunk your text, embed every chunk, and write the vectors into a VECTOR-preset baton — creating one when batonId is absent (persist the returned batonId: it is the memory address across sessions). Body: { text, batonId?, chunkBytes?: 256-8192, name? }. Bills the SUM of the steps as ONE deterministic charge: embed units (llm.embed.per_10kb_micros × chunks) + vector batches (baton.vector.price.put_per_batch_micros) + the engine-quoted create when applicable. Existing batons need write access (owner/writer or a write X-Baton-Token). Returns { batonId, created, chunks, batches, dims }. Recipe: https://relaystation.ai/recipes/agent-rag-memory.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","minLength":1},"batonId":{"type":"string","minLength":1,"maxLength":128},"chunkBytes":{"type":"integer","minimum":256,"maximum":8192},"name":{"type":"string","maxLength":200}}}}}},"responses":{"200":{"description":"{ batonId, created, chunks, batches, dims }."},"402":{"description":"Payment required (x402 challenge with the computed sum)."},"404":{"description":"Baton not found (or no relationship)."},"409":{"description":"SIZE_EXHAUSTED / WRITES_EXHAUSTED (prepaid quota; charge reversed)."},"422":{"description":"INPUT_TOO_LARGE / TOO_MANY_CHUNKS / NOT_A_VECTOR_BATON / VECTOR_DIMS_MISMATCH (reversed)."},"503":{"description":"OP_DISABLED / upstream 5xx (reversed)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"bundles_rag_ingest"}},"/v1/bundles/rag-query":{"post":{"tags":["baton"],"summary":"One-call RAG query (vector query → optional grounded answer)","description":"~$0.01/run, bills the sum of steps. One-call RAG query: embed the question, similarity-search your vector memory, optionally get an answer grounded ONLY in the retrieved chunks with sources. Recipe: relaystation.ai/recipes/agent-rag-memory. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nEmbed the question, nearest-neighbor query the vector baton, and (answer: true) synthesize a grounded answer from ONLY the retrieved chunks (untrusted data; sources listed). Body: { batonId, question, topK?, answer?, tier?: budget|value|best }. Owner/reader or a read X-Baton-Token. Bills the SUM of the steps as ONE deterministic charge: embed unit(s) + the query flat + (answer) the FLAT bundles.rag_query.answer_flat_micros adder — sync_debit, so the bundle x402-exact-settles (unlike cost-plus /v1/rag/answer, which settles at actual). Returns { batonId, matches: [{id, distance, text}], answer? }. Recipe: https://relaystation.ai/recipes/agent-rag-memory.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["batonId","question"],"properties":{"batonId":{"type":"string","minLength":1,"maxLength":128},"question":{"type":"string","minLength":1,"maxLength":65536},"topK":{"type":"integer","minimum":1,"maximum":1000},"answer":{"type":"boolean"},"tier":{"type":"string","enum":["budget","value","best"]}}}}}},"responses":{"200":{"description":"{ batonId, matches, answer? }."},"402":{"description":"Payment required (x402 challenge with the computed sum)."},"404":{"description":"Baton not found (or no relationship)."},"409":{"description":"EGRESS_EXHAUSTED (prepaid quota; charge reversed)."},"422":{"description":"NOT_A_VECTOR_BATON / TOPK_TOO_LARGE / INPUT_TOO_LARGE / UPSTREAM_ERROR (reversed)."},"503":{"description":"OP_DISABLED / upstream 5xx (reversed)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"bundles_rag_query"}},"/v1/bundles/ingest-document":{"post":{"tags":["cputools"],"summary":"One-call document ingest (OCR-searchable + tables → xlsx → drops)","description":"~$0.04–$0.06/run, bills the sum of steps. One-call document ingest: OCR a scanned single-page document into a searchable PDF, extract its tables (AWS Textract), write them to xlsx, and store both artifacts at durable URLs. The full scan→spreadsheet chore in one payment. Recipe: relaystation.ai/recipes/document-chores\n\nThe cputools showcase in one payment: OCR a scanned SINGLE-PAGE document (image or 1-page PDF, ≤ docai.max_doc_bytes) into a searchable PDF, extract its tables (AWS Textract TABLES), write them to xlsx (one sheet per table), and store both artifacts in durable drop batons. Body: { file } (inline / scratch inputKey / baton ref). Bills the SUM of the steps as ONE charge: OCR per-page + docai.price.extract_tables + xlsx per-MB + two engine-quoted drop creates. Returns { searchablePdf: {batonId, url, sizeBytes}, tablesXlsx: {batonId, url, sizeBytes}, tables } — urls are 24h scratch links, batonIds the durable copies. Multi-page documents → the async lane (/v1/doc/analyze-async). Recipe: https://relaystation.ai/recipes/document-chores.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"Input source: { inline } | { inputKey } | { batonId }."}}}}}},"responses":{"200":{"description":"{ searchablePdf, tablesXlsx, tables }."},"402":{"description":"Payment required (x402 challenge with the computed sum)."},"422":{"description":"DOC_TOO_LARGE / DOC_MULTI_PAGE / PDF_PARSE_FAILED (reversed)."},"503":{"description":"OP_DISABLED / upstream 5xx (reversed)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"bundles_ingest_document"}},"/v1/web/pdf":{"post":{"tags":["cputools"],"summary":"Render a URL to PDF","description":"$0.02/render. Render any public web page to PDF — headless Chromium, SSRF-guarded egress. 155 tools, one balance — relaystation.ai\n\nRender a public web page (by URL) to PDF on a headless Chromium worker. Body: { url (http/https), viewport?: {width,height}, fullPage?, waitMs?, pdfOptions? }. SSRF-GUARDED: private/loopback/link-local/multicast/cloud-metadata and the platform's own hosts are rejected PRE-charge (422 URL_BLOCKED); the fetch runs ONLY in the worker with DNS-pinned resolution and per-redirect re-validation (≤ web.max_redirects). FLAT-billed (web.price.url_to_pdf). Per-customer rate cap (web.rate_per_min). Returns the PDF (uniform output envelope).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","minLength":1,"maxLength":4096},"viewport":{"type":"object","properties":{"width":{"type":"integer","minimum":1,"maximum":10000},"height":{"type":"integer","minimum":1,"maximum":10000}}},"fullPage":{"type":"boolean"},"waitMs":{"type":"integer","minimum":0,"maximum":20000},"pdfOptions":{"type":"object"}}}}}},"responses":{"200":{"description":"The rendered PDF (uniform JSON output envelope)."},"402":{"description":"Payment required (x402 challenge)."},"422":{"description":"URL_INVALID / URL_BLOCKED (SSRF guard; no charge)."},"429":{"description":"RATE_LIMITED (web.rate_per_min; no charge)."},"502":{"description":"URL_FETCH_FAILED — the target site failed (charge reversed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."},"504":{"description":"WORKER_RENDER_TIMEOUT (charge reversed)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"url_to_pdf"}},"/v1/web/screenshot":{"post":{"tags":["cputools"],"summary":"Screenshot a URL","description":"$0.01/screenshot. Screenshot any public web page to PNG/JPEG/WebP — headless Chromium, SSRF-guarded. 155 tools, one balance — relaystation.ai\n\nScreenshot a public web page (by URL) to PNG/JPEG/WebP on a headless Chromium worker. Body: { url (http/https), type?: png|jpeg|webp, viewport?, fullPage?, waitMs? }. Same SSRF guard as /v1/web/pdf (pre-charge URL_BLOCKED; DNS-pinned worker-only fetch). FLAT-billed (web.price.url_screenshot). Returns the image (uniform output envelope).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","minLength":1,"maxLength":4096},"type":{"type":"string","enum":["png","jpeg","webp"]},"viewport":{"type":"object","properties":{"width":{"type":"integer","minimum":1,"maximum":10000},"height":{"type":"integer","minimum":1,"maximum":10000}}},"fullPage":{"type":"boolean"},"waitMs":{"type":"integer","minimum":0,"maximum":20000}}}}}},"responses":{"200":{"description":"The screenshot image (uniform JSON output envelope)."},"402":{"description":"Payment required (x402 challenge)."},"422":{"description":"URL_INVALID / URL_BLOCKED (SSRF guard; no charge)."},"429":{"description":"RATE_LIMITED (no charge)."},"502":{"description":"URL_FETCH_FAILED (charge reversed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."},"504":{"description":"WORKER_RENDER_TIMEOUT (charge reversed)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"url_screenshot"}},"/v1/web/readability":{"post":{"tags":["cputools"],"summary":"Extract clean article text from a URL","description":"$0.005/extract. Extract the clean article text — title, byline, body — from any public URL. No boilerplate, no nav junk. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nExtract the readable article of a public web page (by URL) — title, byline, excerpt, and body text with chrome/ads/nav stripped (Mozilla Readability, run in-page on the Chromium worker). Body: { url (http/https) }. Same SSRF guard as /v1/web/pdf. FLAT-billed (web.price.url_readability). Returns { title, byline, excerpt, textContent, length } — null fields when the page is not article-shaped.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","minLength":1,"maxLength":4096}}}}}},"responses":{"200":{"description":"{ title, byline, excerpt, textContent, length }."},"402":{"description":"Payment required (x402 challenge)."},"422":{"description":"URL_INVALID / URL_BLOCKED (SSRF guard; no charge)."},"429":{"description":"RATE_LIMITED (no charge)."},"502":{"description":"URL_FETCH_FAILED (charge reversed)."},"503":{"description":"OP_DISABLED (kill-switch; no charge)."},"504":{"description":"WORKER_RENDER_TIMEOUT (charge reversed)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"url_readability"}},"/v1/contracts":{"post":{"tags":["contracts"],"summary":"Create a binding agreement (terms + parties)","description":"$0.02/contract. Agents strike binding-by-goodwill agreements: create terms, add parties, sign, check signatures — with paid arbitration when disputes arise. The trust layer for agent-to-agent work, one of 155 Relaystation tools on one balance — relaystation.ai\n\nCreate a binding-by-goodwill agreement between agents/parties. The contract IS a hash-chained, auto-witnessed record (terms = entry 1, consents append after); it is tamper-evident and permanently verifiable, but NOT legally binding, NOT court-enforceable, and involves NO escrow or money movement — enforcement rests on the good faith of the parties. Billable (contracts.create), charge-on-attempt; Idempotency-Key honored. The system appends a canonical signature block (and, if arbitration:true, an arbitration block) to your terms — your terms body must NOT itself contain those blocks (free 422 INVALID_FOR_SIGNING). Each required signer is identified by EXACTLY one method: a wallet (0x-address, satisfied only by an EIP-712 ContractConsent signature) or an account (a Relaystation customerId, satisfied only by that authenticated account). Returns per-signer read tokens for visibility. See GET /v1/contracts/disclosure for the EIP-712 domain + witness key.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["terms","requiredSigners"],"properties":{"terms":{"type":"string","minLength":1,"maxLength":900000,"description":"The agreement body. The signature/arbitration blocks are system-appended; do not include them."},"requiredSigners":{"type":"array","minItems":1,"maxItems":20,"items":{"type":"object","required":["identity","kind"],"properties":{"identity":{"type":"string"},"kind":{"type":"string","enum":["wallet","account"]}}}},"arbitration":{"type":"boolean","description":"Append the arbitration block, making the contract arbitrable (enables /arbitrate)."},"signingWindowSeconds":{"type":"integer","minimum":60,"maximum":31536000,"description":"Override the default signing window (contracts.signing_window_seconds, 72h)."}}}}}},"responses":{"201":{"description":"{ id, batonId, termsHash, status, arbitrable, requiredSigners, signatures, signingDeadline, signatureBlockVersion, arbitrationBlockVersion, createdAt, tokens: [{ identity, tokenId }] }.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"VALIDATION_ERROR / INVALID_SIGNER / DUPLICATE_SIGNER."},"401":{"description":"UNAUTHENTICATED OR INVALID_X402_SIGNATURE."},"402":{"description":"PAYMENT_REQUIRED (x402 admit rejection)."},"422":{"description":"INVALID_FOR_SIGNING / EMPTY_TERMS."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"create_contract"},"get":{"tags":["contracts"],"summary":"List your contracts","description":"Owner-scoped list of your contracts (newest first, up to 100). Free.","security":[{"apiKey":[]},{"walletJwt":[]}],"responses":{"200":{"description":"{ items: [contract, …] }.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"UNAUTHENTICATED."}},"operationId":"list_contracts"}},"/v1/contracts/disclosure":{"get":{"tags":["contracts"],"security":[],"summary":"Public offline-verification disclosure","description":"Public, unauthenticated, free. Everything needed to verify a contract OFFLINE: the witness public key (secp256k1, uncompressed) + keyId; the EIP-712 ContractConsent domain/types/primaryType + chainId (reproduce + verify a wallet consent over {contractId, termsHash}); the canonical signature + arbitration block text + versions; and the published arbiter prompt + version + model.","responses":{"200":{"description":"{ product, witness: { keyId, algorithm, publicKey, note }, consent: { scheme, domain, types, primaryType, note }, blocks: { signature, arbitration }, arbiter: { promptVersion, model, prompt, note } }.","content":{"application/json":{"schema":{"type":"object"}}}}},"operationId":"getContractsDisclosure"}},"/v1/contracts/{id}":{"get":{"tags":["contracts"],"summary":"Check whether all parties have signed","description":"Your contract: terms hash, full per-party signature record, status, signing deadline, any arbitration rulings, the witness proof (once executed), and the baton verify-chain/proof URLs. Free. Counterparties verify the terms via the baton trust endpoints using their read token.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Contract detail + arbitrations[] + proof + verify URLs.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"UNAUTHENTICATED."},"403":{"description":"FORBIDDEN (not the owner)."},"404":{"description":"contract not found."}},"operationId":"get_contract"}},"/v1/contracts/{id}/sign":{"post":{"tags":["contracts"],"summary":"Sign a binding agreement","description":"Append your consent — one entry on the hash chain. Free. Two forms, NO cross-method satisfaction: a wallet signer supplies an EIP-712 ContractConsent signature over the contract termsHash (the signature is the authentication); an account signer must be authenticated as the matching customerId. When the last required signer signs, the contract becomes executed and is witnessed. The EIP-712 typed data + domain are published at GET /v1/contracts/disclosure.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["method"],"properties":{"method":{"type":"string","enum":["wallet","account"]},"walletAddress":{"type":"string","description":"Required for method=wallet."},"signature":{"type":"string","description":"EIP-712 ContractConsent signature; required for method=wallet."}}}}}},"responses":{"200":{"description":"Updated contract view.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"VALIDATION_ERROR."},"401":{"description":"INVALID_SIGNATURE / MISSING_AUTH (account consent)."},"403":{"description":"NOT_A_SIGNER."},"404":{"description":"contract not found."},"409":{"description":"ALREADY_SIGNED / ALREADY_EXECUTED / SIGNING_CLOSED."}},"operationId":"sign_contract"}},"/v1/contracts/{id}/extend-signing":{"post":{"tags":["contracts"],"summary":"Extend the signing window","description":"Owner-only. Extend a not-yet-executed contract's signing window from its current deadline (or now, if lapsed); a lapsed contract returns to pending. Free.","security":[{"apiKey":[]},{"walletJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["additionalSeconds"],"properties":{"additionalSeconds":{"type":"integer","minimum":60,"maximum":31536000}}}}}},"responses":{"200":{"description":"Updated contract view.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"VALIDATION_ERROR."},"403":{"description":"FORBIDDEN (not the owner)."},"404":{"description":"contract not found."},"409":{"description":"ALREADY_EXECUTED."}},"operationId":"extend_contract_signing"}},"/v1/contracts/{id}/arbitrate":{"post":{"tags":["contracts"],"summary":"Open a dispute and get a decision","description":"Submit a dispute on an arbitrable, fully-executed contract you own; get back a transparent AI ruling. Billable (contracts.arbitrate = a flat fee + the LLM cost + markup); Idempotency-Key honored. The arbiter is a PURE FUNCTION — it reads the published arbitration prompt + the full contract chain + your issue and returns a TEXT decision; it moves no money and takes no action (binding by goodwill, not law). The submitted issue, the exact prompt + model, and the decision are appended to the contract permanently. The arbiter treats the contract + issue as UNTRUSTED DATA and flags any embedded instruction aimed at it (injectionDetected). A provider failure reverses the charge and appends nothing.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["issue"],"properties":{"issue":{"type":"string","minLength":1,"maxLength":40000},"evidence":{"type":"string","maxLength":200000}}}}}},"responses":{"200":{"description":"{ contractId, ruling: { decision, reasoning, inFavorOf, injectionDetected, injectionDetail }, model, promptVersion, usage }.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"VALIDATION_ERROR."},"401":{"description":"UNAUTHENTICATED OR INVALID_X402_SIGNATURE."},"402":{"description":"PAYMENT_REQUIRED (x402 admit rejection)."},"404":{"description":"contract not found (or not owned)."},"409":{"description":"NOT_EXECUTED."},"422":{"description":"NOT_ARBITRABLE / PROVIDER_UNAVAILABLE."},"503":{"description":"UPSTREAM_ERROR (arbiter provider; charge reversed)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"arbitrate_contract"}},"/v1/pdf/from-office":{"post":{"tags":["cputools"],"summary":"Convert an Office document to PDF","description":"$0.005/call. Convert Word, Excel, or PowerPoint to PDF via LibreOffice — layout preserved. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nConvert an Office document (docx, xlsx, pptx, odt, ods, odp, rtf, txt, csv) to PDF via LibreOffice. Body: { file: <source>, filename, options? } where <source> is { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> } and `filename` carries the source extension (drives both the format allowlist guard and the conversion). Optional `options` (export controls passed to the engine): pageRanges (e.g. \"1-5,8\"), quality (JPEG quality 1–100 for embedded images), losslessImageCompression, reduceImageResolution + maxImageResolution (DPI ceiling — one of 75/150/300/600/1200; reduceImageResolution must be on to take effect), landscape (force orientation). FLAT price (cputools.pdf.from-office) — the output page count is not known pre-convert. Charge-on-attempt, no refund: a convert that reaches the engine and succeeds stands; an engine 4xx/5xx/timeout reverses the charge. Output: the uniform cputools JSON output envelope. Idempotency-Key required.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","filename"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key, ≤ 50 MB> }."},"filename":{"type":"string","minLength":1,"maxLength":255,"description":"Source filename; its extension must be a supported office format."},"options":{"type":"object","description":"Optional LibreOffice export controls; absent fields take the engine default.","additionalProperties":false,"properties":{"pageRanges":{"type":"string","minLength":1,"maxLength":255,"description":"Page subset, e.g. \"1-5,8\"."},"quality":{"type":"integer","minimum":1,"maximum":100,"description":"JPEG quality (1–100) for embedded images."},"losslessImageCompression":{"type":"boolean"},"reduceImageResolution":{"type":"boolean","description":"Gate for maxImageResolution; must be true for maxImageResolution to apply."},"maxImageResolution":{"type":"integer","enum":[75,150,300,600,1200],"description":"DPI ceiling for embedded images (closed ladder)."},"landscape":{"type":"boolean","description":"Force landscape orientation."}}}}}}}},"responses":{"200":{"description":"The converted PDF (uniform JSON output envelope).","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge with the flat price)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"UNSUPPORTED_FORMAT / INPUT_TOO_LARGE / CONVERT_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"pdf_from_office"}},"/v1/office/rescue":{"post":{"tags":["cputools"],"summary":"Rescue an exotic/legacy office file to PDF (best-effort)","description":"$0.008/call. Rescue exotic or legacy Office files into PDF via LibreOffice — opens what modern Office won't. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nBest-effort convert an exotic/legacy office format to PDF via LibreOffice — Apple iWork (pages/numbers/key), Visio (vsd/vsdx), MS Publisher (pub), CorelDRAW (cdr), flat-ODF (fodt), legacy MS binaries (doc/xls/ppt), WordPerfect (wpd) — plus the everyday from-office roster. Body: { file: <source>, filename, options? } ({ inline } ≤ 4 MiB / { inputKey } ≤ 50 MB); `filename` carries the source extension (drives the rescue-roster allowlist + the conversion); `options` mirrors /v1/pdf/from-office (pageRanges/quality/losslessImageCompression/reduceImageResolution/maxImageResolution/landscape). Fidelity is honest best-effort — output is ALWAYS PDF (office↔office is not offered). An extension outside the roster → free 422 UNSUPPORTED_FORMAT naming GET /v1/office/formats. Premium FLAT price (cputools.office.rescue). Charge-on-attempt, no refund: a convert that reaches the engine and succeeds stands; an engine 4xx/5xx/timeout reverses. Output: the uniform cputools JSON output envelope. Idempotency-Key required.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","filename"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key, ≤ 50 MB> }."},"filename":{"type":"string","minLength":1,"maxLength":255,"description":"Source filename; its extension must be in the rescue roster (GET /v1/office/formats)."},"options":{"type":"object","description":"Optional LibreOffice export controls (mirrors /v1/pdf/from-office); absent fields take the engine default.","additionalProperties":false,"properties":{"pageRanges":{"type":"string","minLength":1,"maxLength":255,"description":"Page subset, e.g. \"1-5,8\"."},"quality":{"type":"integer","minimum":1,"maximum":100,"description":"JPEG quality (1–100) for embedded images."},"losslessImageCompression":{"type":"boolean"},"reduceImageResolution":{"type":"boolean","description":"Gate for maxImageResolution; must be true for maxImageResolution to apply."},"maxImageResolution":{"type":"integer","enum":[75,150,300,600,1200],"description":"DPI ceiling for embedded images (closed ladder)."},"landscape":{"type":"boolean","description":"Force landscape orientation."}}}}}}}},"responses":{"200":{"description":"The converted PDF (uniform JSON output envelope).","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge with the flat price)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"UNSUPPORTED_FORMAT (names GET /v1/office/formats) / INPUT_TOO_LARGE / CONVERT_FAILED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"office_rescue"}},"/v1/office/formats":{"get":{"tags":["cputools"],"summary":"Office conversion roster","description":"The office → PDF input rosters — the exotic/legacy rescue set (POST /v1/office/rescue) and the everyday from-office set (POST /v1/pdf/from-office), both always → PDF. FREE, read-only, no auth (mirrors GET /v1/doc/formats; the rescue roster reads the cputools.office.rescue_formats tunable so it flexes without a redeploy). Returns { rescue: { inputs: [...], output: \"pdf\" }, fromOffice: { inputs: [...], output: \"pdf\" }, note }. Honest capability note: office↔office conversion (e.g. docx↔odt) is not offered — all office routes output PDF.","security":[],"responses":{"200":{"description":"The office conversion roster.","content":{"application/json":{"schema":{"type":"object"}}}}},"operationId":"office_formats"}},"/v1/media/probe":{"post":{"tags":["cputools"],"summary":"Probe media metadata (ffprobe)","description":"$0.0002/call. Inspect any media file via ffprobe — duration, container, streams, codecs, dimensions, bitrate as JSON. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nInspect a media file via ffprobe — duration, container, streams, codecs, dimensions, bitrate. Body: { file: <source> } ({ inputKey } ≤ 50 MB primary / { inline } ≤ 4 MB). Cheap, read-only — FLAT price (cputools.media.probe). Returns { probe: { formatName, durationSeconds, hasVideo, hasAudio, width, height, videoCodec, audioCodec, bitRate, sizeBytes, streams } }.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB> }. The input container must sniff (ffprobe) to the live allowlist (operator-tunable cputools.media.input_formats): 3g2, 3gp, aac, asf, avi, flac, flv, gif, m4a, m4v, matroska, mj2, mov, mov,mp4,m4a,3gp,3g2,mj2, mp3, mp4, mpeg, mpegts, mpegvideo, ogg, wav, webm, wmv. An off-list input is a free 422 UNSUPPORTED_FORMAT."}}}}}},"responses":{"200":{"description":"{ probe: {...} } media metadata.","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge with the flat price)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"MEDIA_PARSE_FAILED / OP_DISABLED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"media_probe"}},"/v1/media/trim":{"post":{"tags":["cputools"],"summary":"Trim a media time range (stream-copy)","description":"$0.0005/MB. Cut a time range from audio/video by fast stream-copy — codecs preserved, no quality loss. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nCut a time range from audio/video via fast stream-copy (codecs preserved; use /v1/media/convert to change them). Body: { file: <source>, start: seconds, end?: seconds | duration?: seconds, format?: mp4|mov|mkv|webm|m4a|mp3|wav|ogg|aac|flac (default: the input container) }. Per-MB-of-input price, min 1 MiB (cputools.media.trim). Input duration capped at cputools.media.max_seconds (pre-charge free 422). Output: the uniform cputools JSON output envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","start"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB> }. The input container must sniff (ffprobe) to the live allowlist (operator-tunable cputools.media.input_formats): 3g2, 3gp, aac, asf, avi, flac, flv, gif, m4a, m4v, matroska, mj2, mov, mov,mp4,m4a,3gp,3g2,mj2, mp3, mp4, mpeg, mpegts, mpegvideo, ogg, wav, webm, wmv. An off-list input is a free 422 UNSUPPORTED_FORMAT."},"start":{"type":"number","minimum":0},"end":{"type":"number","minimum":0},"duration":{"type":"number","minimum":0.01},"format":{"type":"string","enum":["mp4","mov","mkv","webm","m4a","mp3","wav","ogg","aac","flac"]}}}}}},"responses":{"200":{"description":"The trimmed media (uniform JSON output envelope).","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"UNSUPPORTED_FORMAT / DURATION_TOO_LONG / BAD_RANGE / CONVERT_FAILED / OP_DISABLED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"media_trim"}},"/v1/media/convert":{"post":{"tags":["cputools"],"summary":"Convert media container/codec (re-encode)","description":"$0.0005/MB. Re-encode video/audio to another container or codec (H.264/AAC and more) — scale, crop, rotate in the same pass. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRe-encode a media file to another container/codec. Body: { file: <source>, format: mp4|mov|mkv (H.264/AAC video) | mp3|m4a|aac|wav|ogg|flac (audio), width?, height?, crop?: { w, h, x, y }, rotate?: 90|180|270 }. Video is downscaled to cputools.media.convert.max_height (default 720) at preset cputools.media.convert.preset (default veryfast), and the input duration is capped at cputools.media.convert.max_seconds (default 60 — tight, to clear the synchronous window). Optional transforms: width/height force a scale (omit one to preserve aspect), crop selects a region (w×h at x,y), rotate turns the frame by 90/180/270°; absent → the default aspect-preserving downscale. Per-MB-of-input price, min 1 MiB (cputools.media.convert). Charge-on-attempt; a timeout/undeliverable convert reverses. Output: the uniform JSON output envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","format"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB> }. The input container must sniff (ffprobe) to the live allowlist (operator-tunable cputools.media.input_formats): 3g2, 3gp, aac, asf, avi, flac, flv, gif, m4a, m4v, matroska, mj2, mov, mov,mp4,m4a,3gp,3g2,mj2, mp3, mp4, mpeg, mpegts, mpegvideo, ogg, wav, webm, wmv. An off-list input is a free 422 UNSUPPORTED_FORMAT."},"format":{"type":"string","enum":["mp4","mov","mkv","mp3","m4a","aac","wav","ogg","flac"]},"width":{"type":"integer","minimum":1,"maximum":7680},"height":{"type":"integer","minimum":1,"maximum":7680},"crop":{"type":"object","required":["w","h","x","y"],"properties":{"w":{"type":"integer","minimum":1,"maximum":7680},"h":{"type":"integer","minimum":1,"maximum":7680},"x":{"type":"integer","minimum":0,"maximum":7680},"y":{"type":"integer","minimum":0,"maximum":7680}}},"rotate":{"type":"integer","enum":[90,180,270]}}}}}},"responses":{"200":{"description":"The converted media (uniform JSON output envelope).","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"UNSUPPORTED_FORMAT / DURATION_TOO_LONG / NO_VIDEO_STREAM / NO_AUDIO_STREAM / CONVERT_FAILED / OP_DISABLED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"media_convert"}},"/v1/media/thumbnail":{"post":{"tags":["cputools"],"summary":"Grab a video frame (thumbnail)","description":"$0.0003/call. Grab a single video frame at any timestamp — PNG or JPEG out. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nGrab a single frame from a video at a timestamp → PNG/JPEG. Body: { file: <source>, timestamp?: seconds (default cputools.media.thumbnail.default_timestamp), format?: png|jpeg (default cputools.media.thumbnail.format) }. Needs a video stream. FLAT price (cputools.media.thumbnail). Output: the image in the uniform JSON output envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB> }. The input container must sniff (ffprobe) to the live allowlist (operator-tunable cputools.media.input_formats): 3g2, 3gp, aac, asf, avi, flac, flv, gif, m4a, m4v, matroska, mj2, mov, mov,mp4,m4a,3gp,3g2,mj2, mp3, mp4, mpeg, mpegts, mpegvideo, ogg, wav, webm, wmv. An off-list input is a free 422 UNSUPPORTED_FORMAT."},"timestamp":{"type":"number","minimum":0},"format":{"type":"string","enum":["png","jpeg","jpg"]}}}}}},"responses":{"200":{"description":"The thumbnail image (uniform JSON output envelope).","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge with the flat price)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"NO_VIDEO_STREAM / BAD_TIMESTAMP / UNSUPPORTED_FORMAT / OP_DISABLED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"media_thumbnail"}},"/v1/media/audio-extract":{"post":{"tags":["cputools"],"summary":"Extract the audio track","description":"Strip + (re)encode a media file's audio track. Body: { file: <source>, format?: mp3|m4a|aac|wav|ogg|flac (default cputools.media.audio.format), bitrate?: e.g. \"192k\" (default cputools.media.audio.bitrate) }. Needs an audio stream. Per-MB-of-input price, min 1 MiB (cputools.media.audio-extract). Output: the audio file in the uniform JSON output envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB> }. The input container must sniff (ffprobe) to the live allowlist (operator-tunable cputools.media.input_formats): 3g2, 3gp, aac, asf, avi, flac, flv, gif, m4a, m4v, matroska, mj2, mov, mov,mp4,m4a,3gp,3g2,mj2, mp3, mp4, mpeg, mpegts, mpegvideo, ogg, wav, webm, wmv. An off-list input is a free 422 UNSUPPORTED_FORMAT."},"format":{"type":"string","enum":["mp3","m4a","aac","wav","ogg","flac"]},"bitrate":{"type":"string"}}}}}},"responses":{"200":{"description":"The extracted audio (uniform JSON output envelope).","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"NO_AUDIO_STREAM / DURATION_TOO_LONG / UNSUPPORTED_FORMAT / CONVERT_FAILED / OP_DISABLED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"media_audio_extract"}},"/v1/media/gif":{"post":{"tags":["cputools"],"summary":"Video segment → animated GIF","description":"$0.0005/MB. Turn a video segment into a palette-optimized animated GIF — share-ready loops. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nTurn a video segment into a palette-optimized animated GIF. Body: { file: <source>, start?: seconds (default 0), duration?: seconds (default cputools.media.gif.default_seconds, cap cputools.media.gif.max_seconds), width?: px (default 480, cap cputools.media.gif.max_width), fps?: (default cputools.media.gif.fps) }. GIFs blow up — over-cap requests are rejected pre-charge. Needs a video stream. Per-MB-of-input price, min 1 MiB (cputools.media.gif). Output: the GIF in the uniform JSON output envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB> }. The input container must sniff (ffprobe) to the live allowlist (operator-tunable cputools.media.input_formats): 3g2, 3gp, aac, asf, avi, flac, flv, gif, m4a, m4v, matroska, mj2, mov, mov,mp4,m4a,3gp,3g2,mj2, mp3, mp4, mpeg, mpegts, mpegvideo, ogg, wav, webm, wmv. An off-list input is a free 422 UNSUPPORTED_FORMAT."},"start":{"type":"number","minimum":0},"duration":{"type":"number","minimum":0.1},"width":{"type":"integer","minimum":16},"fps":{"type":"integer","minimum":1}}}}}},"responses":{"200":{"description":"The animated GIF (uniform JSON output envelope).","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"NO_VIDEO_STREAM / GIF_TOO_LONG / GIF_TOO_WIDE / BAD_RANGE / CONVERT_FAILED / OP_DISABLED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"media_gif"}},"/v1/media/loudnorm":{"post":{"tags":["cputools"],"summary":"Normalize audio loudness (EBU R128)","description":"$0.0005/MB. Normalize audio loudness to broadcast EBU R128 — LUFS, true-peak, and range targets. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nNormalize a media file's audio loudness with ffmpeg's loudnorm (EBU R128) — a consistent perceived level for podcasts/voiceover/music. Body: { file: <source>, ext?: mp3|m4a|aac|wav|ogg|flac (default cputools.media.audio.format), i?: integrated-loudness target LUFS (-70..-5, default -16), tp?: true-peak ceiling dBTP (-9..0, default -1.5), lra?: loudness-range LU (1..50, default 11) }. AUDIO output (the audio stream, re-encoded; any video is dropped). Needs an audio stream. Input duration capped at cputools.media.max_seconds (pre-charge free 422). Per-MB-of-input price, min 1 MiB (cputools.media.loudnorm). Output: the normalized audio in the uniform JSON output envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB> }. The input container must sniff (ffprobe) to the live allowlist (operator-tunable cputools.media.input_formats): 3g2, 3gp, aac, asf, avi, flac, flv, gif, m4a, m4v, matroska, mj2, mov, mov,mp4,m4a,3gp,3g2,mj2, mp3, mp4, mpeg, mpegts, mpegvideo, ogg, wav, webm, wmv. An off-list input is a free 422 UNSUPPORTED_FORMAT."},"ext":{"type":"string","enum":["mp3","m4a","aac","wav","ogg","flac"]},"i":{"type":"number","minimum":-70,"maximum":-5},"tp":{"type":"number","minimum":-9,"maximum":0},"lra":{"type":"number","minimum":1,"maximum":50}}}}}},"responses":{"200":{"description":"The loudness-normalized audio (uniform JSON output envelope).","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"NO_AUDIO_STREAM / DURATION_TOO_LONG / UNSUPPORTED_FORMAT / CONVERT_FAILED / OP_DISABLED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"media_loudnorm"}},"/v1/media/audio-convert":{"post":{"tags":["cputools"],"summary":"Transcode audio (standalone re-encode)","description":"$0.0005/MB. Re-encode audio to another format, bitrate, or sample rate — mp3, aac, wav, flac and more. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRe-encode an audio file to another audio format/bitrate/sample-rate — a standalone audio↔audio transcode (vs /v1/media/audio-extract which pulls audio off a video). Body: { file: <source>, format?: mp3|m4a|aac|wav|ogg|flac (default cputools.media.audio.format), bitrate?: e.g. \"192k\" (default cputools.media.audio.bitrate), sampleRate?: Hz (8000..192000) }. Needs an audio stream. Input duration capped at cputools.media.max_seconds. Per-MB-of-input price, min 1 MiB (cputools.media.audio-convert). Output: the audio file in the uniform JSON output envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB> }. The input container must sniff (ffprobe) to the live allowlist (operator-tunable cputools.media.input_formats): 3g2, 3gp, aac, asf, avi, flac, flv, gif, m4a, m4v, matroska, mj2, mov, mov,mp4,m4a,3gp,3g2,mj2, mp3, mp4, mpeg, mpegts, mpegvideo, ogg, wav, webm, wmv. An off-list input is a free 422 UNSUPPORTED_FORMAT."},"format":{"type":"string","enum":["mp3","m4a","aac","wav","ogg","flac"]},"bitrate":{"type":"string"},"sampleRate":{"type":"integer","minimum":8000,"maximum":192000}}}}}},"responses":{"200":{"description":"The transcoded audio (uniform JSON output envelope).","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"NO_AUDIO_STREAM / DURATION_TOO_LONG / UNSUPPORTED_FORMAT / CONVERT_FAILED / OP_DISABLED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"media_audio_convert"}},"/v1/media/speed":{"post":{"tags":["cputools"],"summary":"Change playback speed (pitch-preserving)","description":"$0.0005/MB. Speed up or slow down audio/video — pitch-preserving by default. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nSpeed up or slow down audio/video. Body: { file: <source>, factor?: 0.25..4 (default 1.0; 2 = twice as fast, 0.5 = half), ext?: output container (default: input's — video→mp4, audio→mp3), pitchCorrect?: boolean (default true — preserve pitch via atempo; set false for a tape-style pitch shift) }. Per-MB-of-input price, min 1 MiB (cputools.media.speed). Input duration capped at cputools.media.max_seconds. Output: the speed-changed media in the uniform JSON output envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB> }. The input container must sniff (ffprobe) to the live allowlist (operator-tunable cputools.media.input_formats): 3g2, 3gp, aac, asf, avi, flac, flv, gif, m4a, m4v, matroska, mj2, mov, mov,mp4,m4a,3gp,3g2,mj2, mp3, mp4, mpeg, mpegts, mpegvideo, ogg, wav, webm, wmv. An off-list input is a free 422 UNSUPPORTED_FORMAT."},"factor":{"type":"number","minimum":0.25,"maximum":4},"ext":{"type":"string","pattern":"^[a-z0-9]{1,12}$"},"pitchCorrect":{"type":"boolean"}}}}}},"responses":{"200":{"description":"The speed-adjusted media (uniform JSON output envelope).","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"UNSUPPORTED_FORMAT / DURATION_TOO_LONG / CONVERT_FAILED / OP_DISABLED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"media_speed"}},"/v1/media/subtitle-extract":{"post":{"tags":["cputools"],"summary":"Extract an embedded subtitle track","description":"Pull an embedded subtitle/caption track out of a container to a text sidecar (SRT or WebVTT). Body: { file: <source>, format?: srt|vtt (default srt), index?: subtitle-stream index ≥ 0 (default 0 — the first subtitle track) }. Returns the chosen subtitle stream as a text file. If the requested stream does not exist → free 422 NO_SUBTITLE_STREAM. FLAT price (cputools.media.subtitle-extract). Output: the subtitle text in the uniform JSON output envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB> }. The input container must sniff (ffprobe) to the live allowlist (operator-tunable cputools.media.input_formats): 3g2, 3gp, aac, asf, avi, flac, flv, gif, m4a, m4v, matroska, mj2, mov, mov,mp4,m4a,3gp,3g2,mj2, mp3, mp4, mpeg, mpegts, mpegvideo, ogg, wav, webm, wmv. An off-list input is a free 422 UNSUPPORTED_FORMAT."},"format":{"type":"string","enum":["srt","vtt"]},"index":{"type":"integer","minimum":0,"maximum":63}}}}}},"responses":{"200":{"description":"The extracted subtitle text (uniform JSON output envelope).","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge with the flat price)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes."},"422":{"description":"NO_SUBTITLE_STREAM / UNSUPPORTED_FORMAT / OP_DISABLED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"media_subtitle_extract"}},"/v1/media/concat":{"post":{"tags":["cputools"],"summary":"Concatenate clips (stream-copy)","description":"$0.0005/MB. Join 2–10 clips end-to-end by fast stream-copy — no quality loss, no re-encode. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nJoin 2–10 clips end-to-end via the ffmpeg concat demuxer (fast STREAM-COPY — no re-encode). Body: { files: [<source>, …] (2..10, each itself an input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB>}), ext?: output container (default: the first clip's) }. The inputs MUST share the same codec + container (concat-demuxer requirement) — a mismatched set is a CONVERT_FAILED (re-encode each via /v1/media/convert to a common format first). Per-MB-of-the-SUM price, min 1 MiB (cputools.media.concat). The summed size is capped at cputools.media.concat.max_total_mb and the count at cputools.media.concat.max_inputs (pre-charge free 422). Output: the joined media in the uniform JSON output envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["files"],"properties":{"files":{"type":"array","minItems":2,"maxItems":10,"items":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB> }. The input container must sniff (ffprobe) to the live allowlist (operator-tunable cputools.media.input_formats): 3g2, 3gp, aac, asf, avi, flac, flv, gif, m4a, m4v, matroska, mj2, mov, mov,mp4,m4a,3gp,3g2,mj2, mp3, mp4, mpeg, mpegts, mpegvideo, ogg, wav, webm, wmv. An off-list input is a free 422 UNSUPPORTED_FORMAT."}},"ext":{"type":"string","enum":["mp4","mov","mkv","mp3","m4a","aac","wav","ogg","flac"]}}}}}},"responses":{"200":{"description":"The concatenated media (uniform JSON output envelope).","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"An input exceeds max_storage_ref_bytes, or the summed size exceeds cputools.media.concat.max_total_mb (INPUT_TOO_LARGE)."},"422":{"description":"TOO_MANY_INPUTS / UNSUPPORTED_FORMAT / DURATION_TOO_LONG / CONVERT_FAILED (the codec/container mismatch reason) / OP_DISABLED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"media_concat"}},"/v1/media/overlay":{"post":{"tags":["cputools"],"summary":"Overlay an image onto a video (watermark)","description":"$0.0005/MB. Watermark an image or logo onto a video — position and opacity control. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nComposite an image (logo/watermark) onto a video. Body: { file: <video source>, overlay: <image source> (itself an input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB>}), position?: top-left|top-right|bottom-left|bottom-right|center (default top-left), ext?: output container (default: the video's) }. The primary input must be a video (else NO_VIDEO_STREAM, free pre-charge). Re-encodes the video. Per-MB-of-the-VIDEO price, min 1 MiB (cputools.media.overlay). Input duration capped at cputools.media.max_seconds. Output: the watermarked video in the uniform JSON output envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","overlay"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB> }. The input container must sniff (ffprobe) to the live allowlist (operator-tunable cputools.media.input_formats): 3g2, 3gp, aac, asf, avi, flac, flv, gif, m4a, m4v, matroska, mj2, mov, mov,mp4,m4a,3gp,3g2,mj2, mp3, mp4, mpeg, mpegts, mpegvideo, ogg, wav, webm, wmv. An off-list input is a free 422 UNSUPPORTED_FORMAT."},"overlay":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"position":{"type":"string","enum":["top-left","top-right","bottom-left","bottom-right","center"]},"ext":{"type":"string","enum":["mp4","mov","mkv","mp3","m4a","aac","wav","ogg","flac"]}}}}}},"responses":{"200":{"description":"The watermarked video (uniform JSON output envelope).","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge)."},"404":{"description":"A storage-ref inputKey (video or overlay image) is not owned by the caller."},"413":{"description":"An input exceeds max_storage_ref_bytes."},"422":{"description":"NO_VIDEO_STREAM / DURATION_TOO_LONG / UNSUPPORTED_FORMAT / CONVERT_FAILED / OP_DISABLED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"media_overlay"}},"/v1/media/subtitle-burn":{"post":{"tags":["cputools"],"summary":"Burn subtitles into a video (libass)","description":"$0.0005/MB. Burn subtitles permanently into video via libass — styled captions everywhere the file goes. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRender a subtitle file (SRT/WebVTT) permanently INTO the video frames via libass. Body: { file: <video source>, subtitle: <srt/vtt source> (itself an input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB>}), ext?: output container (default: the video's) }. NOTE — the subtitles are BURNED (rasterized) into the picture, NOT a soft/toggleable track; the result has no separate subtitle stream and the captions cannot be turned off. The primary input must be a video. Re-encodes the video. Per-MB-of-the-VIDEO price, min 1 MiB (cputools.media.subtitle-burn). Input duration capped at cputools.media.max_seconds. Output: the captioned video in the uniform JSON output envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","subtitle"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB> }. The input container must sniff (ffprobe) to the live allowlist (operator-tunable cputools.media.input_formats): 3g2, 3gp, aac, asf, avi, flac, flv, gif, m4a, m4v, matroska, mj2, mov, mov,mp4,m4a,3gp,3g2,mj2, mp3, mp4, mpeg, mpegts, mpegvideo, ogg, wav, webm, wmv. An off-list input is a free 422 UNSUPPORTED_FORMAT."},"subtitle":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."},"ext":{"type":"string","enum":["mp4","mov","mkv","mp3","m4a","aac","wav","ogg","flac"]}}}}}},"responses":{"200":{"description":"The subtitle-burned video (uniform JSON output envelope).","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge)."},"404":{"description":"A storage-ref inputKey (video or subtitle) is not owned by the caller."},"413":{"description":"An input exceeds max_storage_ref_bytes."},"422":{"description":"NO_VIDEO_STREAM / DURATION_TOO_LONG / UNSUPPORTED_FORMAT / CONVERT_FAILED / OP_DISABLED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"media_subtitle_burn"}},"/v1/media/waveform":{"post":{"tags":["cputools"],"summary":"Render a waveform / spectrogram PNG","description":"$0.0005/call. Render audio as a waveform or spectrogram PNG — visualize any track. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRender an audio file as a waveform OR spectrogram PNG (ffmpeg showwavespic / showspectrumpic). Body: { file: <source>, mode?: waveform|spectrogram (default waveform), width?: 100..2000 (default 640), height?: 50..1080 (default 120 waveform / 480 spectrogram), color?: blue|red|green|white|black|cyan|magenta|yellow|orange|gray (waveform only, default blue) }. Needs an audio stream. Input duration capped at cputools.media.waveform.max_seconds (default 600, pre-charge free 422). FLAT price (cputools.media.waveform). Output: the PNG in the uniform JSON output envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB> }. The input container must sniff (ffprobe) to the live allowlist (operator-tunable cputools.media.input_formats): 3g2, 3gp, aac, asf, avi, flac, flv, gif, m4a, m4v, matroska, mj2, mov, mov,mp4,m4a,3gp,3g2,mj2, mp3, mp4, mpeg, mpegts, mpegvideo, ogg, wav, webm, wmv. An off-list input is a free 422 UNSUPPORTED_FORMAT."},"mode":{"type":"string","enum":["waveform","spectrogram"]},"width":{"type":"integer","minimum":100,"maximum":2000},"height":{"type":"integer","minimum":50,"maximum":1080},"color":{"type":"string","enum":["blue","red","green","white","black","cyan","magenta","yellow","orange","gray"]}}}}}},"responses":{"200":{"description":"The waveform/spectrogram PNG (uniform JSON output envelope).","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"The input exceeds max_storage_ref_bytes."},"422":{"description":"NO_AUDIO_STREAM / DURATION_TOO_LONG / UNSUPPORTED_FORMAT / OP_DISABLED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"media_waveform"}},"/v1/media/silence":{"post":{"tags":["cputools"],"summary":"Detect or trim silence","description":"$0.0005/call. Detect silent intervals as JSON or trim the silence out — tighter podcasts and voice notes. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nDetect OR trim silence in audio (ffmpeg silencedetect / silenceremove). Body: { file: <source>, action?: detect|trim (default detect), thresholdDb?: -90..0 (default -30), minDurationSeconds?: 0.1..60 (default 0.5), format?: mp3|m4a|aac|wav|ogg|flac (trim output, default mp3) }. action=detect → JSON { silence: [{ start, end, duration }], count } (no file). action=trim → the silence-removed audio in the uniform JSON output envelope. Needs an audio stream. Input duration capped at cputools.media.silence.max_seconds (default 1800). FLAT price (cputools.media.silence).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB> }. The input container must sniff (ffprobe) to the live allowlist (operator-tunable cputools.media.input_formats): 3g2, 3gp, aac, asf, avi, flac, flv, gif, m4a, m4v, matroska, mj2, mov, mov,mp4,m4a,3gp,3g2,mj2, mp3, mp4, mpeg, mpegts, mpegvideo, ogg, wav, webm, wmv. An off-list input is a free 422 UNSUPPORTED_FORMAT."},"action":{"type":"string","enum":["detect","trim"]},"thresholdDb":{"type":"integer","minimum":-90,"maximum":0},"minDurationSeconds":{"type":"number","minimum":0.1,"maximum":60},"format":{"type":"string","enum":["mp3","m4a","aac","wav","ogg","flac"]}}}}}},"responses":{"200":{"description":"detect → { silence: [...], count }; trim → the trimmed audio (uniform JSON output envelope).","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"The input exceeds max_storage_ref_bytes."},"422":{"description":"NO_AUDIO_STREAM / DURATION_TOO_LONG / UNSUPPORTED_FORMAT / OP_DISABLED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"media_silence"}},"/v1/media/audio-mix":{"post":{"tags":["cputools"],"summary":"Mix N audio inputs → 1","description":"$0.0005/MB. Mix multiple audio tracks down to one (amix), optional loudness normalize — voiceover + music in one call. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nMix multiple audio inputs down to one track (ffmpeg amix). Body: { file: <primary source>, inputs: [<source>, …] (additional tracks, each itself an input-source { inline ≤ 4 MiB } / { inputKey ≤ 50 MB }), format?: mp3|m4a|aac|wav|ogg|flac (default mp3), normalize?: boolean (default false — true scales by 1/N to avoid clipping) }. TOTAL input count (primary + additional) capped at cputools.media.audio-mix.max_inputs (default 8 → up to 7 additional; over is a free 422 TOO_MANY_INPUTS). Primary duration capped at cputools.media.audio-mix.max_seconds (default 1800). Per-MB-of-the-PRIMARY price, min 1 MiB (cputools.media.audio-mix). Output: the mixed audio in the uniform JSON output envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","inputs"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB> }. The input container must sniff (ffprobe) to the live allowlist (operator-tunable cputools.media.input_formats): 3g2, 3gp, aac, asf, avi, flac, flv, gif, m4a, m4v, matroska, mj2, mov, mov,mp4,m4a,3gp,3g2,mj2, mp3, mp4, mpeg, mpegts, mpegvideo, ogg, wav, webm, wmv. An off-list input is a free 422 UNSUPPORTED_FORMAT."},"inputs":{"type":"array","minItems":1,"maxItems":8,"items":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key from /v1/cputools/upload-url, ≤ 50 MB> }."}},"format":{"type":"string","enum":["mp3","m4a","aac","wav","ogg","flac"]},"normalize":{"type":"boolean"}}}}}},"responses":{"200":{"description":"The mixed audio (uniform JSON output envelope).","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"An input exceeds max_storage_ref_bytes."},"422":{"description":"TOO_MANY_INPUTS / DURATION_TOO_LONG / UNSUPPORTED_FORMAT / OP_DISABLED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"media_audio_mix"}},"/v1/media/storyboard":{"post":{"tags":["cputools"],"summary":"Build a thumbnail sprite sheet","description":"$0.0008/call. Frame-sampled thumbnail sprite sheet from a video — scrubbers and previews in one call. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nBuild a frame-sampled thumbnail sprite sheet from a video (ffmpeg fps-sample + tile). Body: { file: <source>, cols?: 1..10 (default 5), rows?: 1..10 (default 5), intervalSeconds?: 0.5..3600 (overrides the auto fps; default: sample cols×rows frames evenly across the whole clip so the grid fills), tileWidth?: 32..640 px (default 160) }. Needs a video stream. Input duration capped at cputools.media.storyboard.max_seconds (default 3600). FLAT price (cputools.media.storyboard). Output: the sprite-sheet PNG in the uniform JSON output envelope.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB> }. The input container must sniff (ffprobe) to the live allowlist (operator-tunable cputools.media.input_formats): 3g2, 3gp, aac, asf, avi, flac, flv, gif, m4a, m4v, matroska, mj2, mov, mov,mp4,m4a,3gp,3g2,mj2, mp3, mp4, mpeg, mpegts, mpegvideo, ogg, wav, webm, wmv. An off-list input is a free 422 UNSUPPORTED_FORMAT."},"cols":{"type":"integer","minimum":1,"maximum":10},"rows":{"type":"integer","minimum":1,"maximum":10},"intervalSeconds":{"type":"number","minimum":0.5,"maximum":3600},"tileWidth":{"type":"integer","minimum":32,"maximum":640}}}}}},"responses":{"200":{"description":"The sprite-sheet PNG (uniform JSON output envelope).","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"The input exceeds max_storage_ref_bytes."},"422":{"description":"NO_VIDEO_STREAM / DURATION_TOO_LONG / UNSUPPORTED_FORMAT / OP_DISABLED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"media_storyboard"}},"/v1/media/metadata":{"post":{"tags":["cputools"],"summary":"Read or write container metadata","description":"$0.0002/call. Read media container tags as JSON or write them back — stream-copy, no re-encode. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nRead OR write container metadata tags (ffprobe / ffmpeg -metadata, STREAM-COPY — no re-encode). Body: { file: <source>, set?: { title?|artist?|album?|comment?|genre?|date?|track?|composer?: string|number } }. set absent → JSON { metadata: { …tags } } (read). set present → the re-tagged file in the uniform JSON output envelope (source container preserved). Unknown tag keys are ignored (only the allowlisted set is written). FLAT price (cputools.media.metadata).","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB> }. The input container must sniff (ffprobe) to the live allowlist (operator-tunable cputools.media.input_formats): 3g2, 3gp, aac, asf, avi, flac, flv, gif, m4a, m4v, matroska, mj2, mov, mov,mp4,m4a,3gp,3g2,mj2, mp3, mp4, mpeg, mpegts, mpegvideo, ogg, wav, webm, wmv. An off-list input is a free 422 UNSUPPORTED_FORMAT."},"set":{"type":"object","additionalProperties":{"type":["string","number"]}}}}}}},"responses":{"200":{"description":"read → { metadata: {...} }; write → the re-tagged file (uniform JSON output envelope).","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"The input exceeds max_storage_ref_bytes."},"422":{"description":"UNSUPPORTED_FORMAT / OP_DISABLED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"media_metadata"}},"/v1/media/frames":{"post":{"tags":["cputools"],"summary":"Extract frames → presigned manifest","description":"$0.0003/MP. Extract N evenly-sampled frames from a video — a manifest of presigned image links back. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nExtract evenly-sampled frames from a video → a manifest of presigned image refs (mirrors POST /v1/pdf/render). Body: { file: <source>, count?: 1..50 (default 10), format?: png|jpeg|jpg (default png), width?: 32..1920 px (height auto) }. Frames are sampled evenly across the whole clip. Needs a video stream. Input duration capped at cputools.media.frames.max_seconds (default 3600). Billed PER MEGAPIXEL of output — `ceil(Σ frames × outW × outH / 1e6)` × `cputools.price.media.frames.per_mp_micros` (meters output egress; large-resolution frames cost proportionally more). Output: { frames: [{ index, outputKey, outputUrl }], count } — fetch each via its presigned outputUrl (~1 h) or chain outputKey as another op's inputKey.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key ≤ 50 MB> }. The input container must sniff (ffprobe) to the live allowlist (operator-tunable cputools.media.input_formats): 3g2, 3gp, aac, asf, avi, flac, flv, gif, m4a, m4v, matroska, mj2, mov, mov,mp4,m4a,3gp,3g2,mj2, mp3, mp4, mpeg, mpegts, mpegvideo, ogg, wav, webm, wmv. An off-list input is a free 422 UNSUPPORTED_FORMAT."},"count":{"type":"integer","minimum":1,"maximum":50},"format":{"type":"string","enum":["png","jpeg","jpg"]},"width":{"type":"integer","minimum":32,"maximum":1920}}}}}},"responses":{"200":{"description":"{ frames: [{ index, outputKey, outputUrl }], count }.","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"The input exceeds max_storage_ref_bytes."},"422":{"description":"NO_VIDEO_STREAM / DURATION_TOO_LONG / UNSUPPORTED_FORMAT / OP_DISABLED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"media_frames"}},"/v1/doc/formats":{"get":{"tags":["cputools"],"summary":"Document conversion matrix","description":"The live pandoc conversion matrix — the exact from→to pairs and the pdf composition path. FREE, read-only, no auth (the operator's \"users can see + choose the paths\" surface; read from the cputools.doc.matrix tunable so pairs enable/disable without a redeploy). Returns { formats: [{ from, to: [{ to, lossy?, notes? }] }], compositions: [{ target, via, notes }] }.","security":[],"responses":{"200":{"description":"The conversion matrix.","content":{"application/json":{"schema":{"type":"object"}}}}},"operationId":"doc_formats"}},"/v1/doc/convert":{"post":{"tags":["cputools"],"summary":"Convert a document (pandoc)","description":"$0.0003/MB. Convert documents between markdown, HTML, docx, odt, rtf, epub, LaTeX, and rst via pandoc — citations supported. 1¢ x402 min; remainder auto-credits — relaystation.ai/penny\n\nConvert a markup/structured document between formats via pandoc — markdown, html, docx, odt, rtf, epub, latex, rst, org, textile, mediawiki, plain. Body: { file: <source>, filename?, from?, to, options?: { standalone? } }. `to` is required; `from` is sniffed from `filename` (extension) when omitted. An unsupported (from,to) pair → free 422 UNSUPPORTED_CONVERSION listing the valid targets for that source (the error teaches the matrix; see GET /v1/doc/formats). NO direct pdf target (pandoc→PDF needs a LaTeX engine — deferred; compose doc/convert→html then /v1/pdf/from-html). Runs sandboxed (no file/network access from within the document). Per-MB-of-input price, min 1 MiB (cputools.doc.convert). Charge-on-attempt; an undeliverable convert reverses. Output: the uniform cputools JSON output envelope. Idempotency-Key required.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file","to"],"properties":{"file":{"type":"object","description":"cputools input-source: { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key, ≤ 50 MB> }."},"filename":{"type":"string","minLength":1,"maxLength":255,"description":"Used to sniff `from` when it is omitted."},"from":{"type":"string","enum":["docx","epub","html","ipynb","latex","markdown","mediawiki","odt","org","rst","textile","typst"],"description":"Source format. Optional when sniffable from `filename`. Live matrix: GET /v1/doc/formats."},"to":{"type":"string","enum":["asciidoc","docx","epub","gfm","html","ipynb","jira","latex","markdown","mediawiki","odt","org","plain","pptx","rst","rtf","textile","typst"],"description":"Target format (required; validated per-pair against the live matrix — GET /v1/doc/formats)."},"referenceDoc":{"type":"object","description":"Optional reference-doc template (branded output styling) — cputools input-source { inline: <base64 ≤ 4 MiB> } or { inputKey: <scratch key, ≤ 50 MB> }. Only valid when `to` is docx or pptx (else 422 REFERENCE_DOC_UNSUPPORTED); the template must be an OOXML/ZIP document (else 422 REFERENCE_DOC_FORMAT_INVALID)."},"citations":{"type":"object","required":["bibliography"],"description":"Optional citation processing (pandoc --citeproc). In-document citations (`[@key]`) render against the bibliography and a bibliography section is appended. Works for any target; a document with no citation markup just gets no bibliography. Rides the doc.convert price (no extra charge). The default style (chicago-author-date) is embedded — no network needed.","properties":{"bibliography":{"type":"object","description":"The bibliography file as a cputools input-source ({ inline } / { inputKey }). BibTeX/BibLaTeX, CSL-JSON, or CSL-YAML — set `format`."},"format":{"type":"string","enum":["bib","json","yaml"],"description":"Bibliography file format (default bib). bib = BibTeX/BibLaTeX, json = CSL-JSON, yaml = CSL-YAML."},"csl":{"type":"object","description":"Optional CSL stylesheet (input-source) to style the citations/bibliography; omit for the embedded chicago-author-date default."}}},"options":{"type":"object","properties":{"standalone":{"type":"boolean","description":"Emit a complete document (default true) vs a bare fragment."}}}}}}}},"responses":{"200":{"description":"The converted document (uniform JSON output envelope).","content":{"application/json":{"schema":{"type":"object"}}}},"402":{"description":"PAYMENT_REQUIRED (x402 challenge)."},"404":{"description":"A storage-ref inputKey is not owned by the caller."},"413":{"description":"A storage-ref input exceeds max_storage_ref_bytes / cputools.doc.max_mb."},"422":{"description":"UNSUPPORTED_CONVERSION (lists valid targets) / SOURCE_FORMAT_REQUIRED / INVALID_FORMAT / REFERENCE_DOC_UNSUPPORTED / REFERENCE_DOC_FORMAT_INVALID / CONVERT_FAILED / OP_DISABLED."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"doc_convert"}},"/v1/llm/tasks":{"get":{"tags":["llm"],"summary":"See the live task catalog (tasks, tiers, prices)","description":"The live registry of LLM tasks — each task's tiers (budget/value/best), the PUBLISHED per-increment ceilings, the task-specific params, and the cost-plus terms. FREE, read-only, no auth (the source of truth a no-account agent reads to see what POST /v1/llm/{task} accepts + what it costs). Registry rows are admin-managed, so this updates without a redeploy. Returns { tasks: [{ key, label, description, params, outputCapTokens, transformClass, tiers: [{ tier, ceilingMicrosPerIncrement }] }], pricing: { model: \"cost-plus\", markupPct, incrementKb, maxInputKb } }.","security":[],"responses":{"200":{"description":"The task catalog.","content":{"application/json":{"schema":{"type":"object"}}}}},"operationId":"getLlmTasks"}},"/v1/llm/{task}":{"post":{"tags":["llm"],"summary":"Run a language task (translate, summarize, extract, classify, proofread, and more) — pick a tier","description":"Run a registry task. `{task}` is the task key (see GET /v1/llm/tasks). Body: { tier: \"budget\"|\"value\"|\"best\", input: <the text>, params?: <task-specific, validated against the task's param_schema> }. The registry maps tier→model; the admin-only instruction template wraps your `input` (which is DATA — a separate message, never the instruction). ONE OpenRouter call; chargedMicros = ceil(provider usage.cost × (1 + markup)). x402 is `upto`: the 402 advertises the published per-increment ceiling; you authorize it and pay the actual. Prepaid holds the ceiling and captures the actual. An unsupported task/tier → free 422 UNSUPPORTED listing the active ones (the error teaches the registry). Upstream 4xx/5xx/timeout → nothing delivered → reverse. Output-cap truncation → truncated:true, charge stands. Idempotency-Key required. Returns { result, truncated, usage: { model, inputTokens, outputTokens, providerCostMicros, markupPct, chargedMicros } }.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"parameters":[{"name":"task","in":"path","required":true,"schema":{"type":"string","enum":["answer","classify","extract","json_repair","keywords","proofread","rewrite","sentiment","summarize","title","translate"],"description":"The task key (e.g. summarize, translate). Live catalog: GET /v1/llm/tasks."},"description":"The task key (e.g. summarize, translate). See GET /v1/llm/tasks."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["tier","input"],"properties":{"tier":{"type":"string","enum":["budget","value","best"]},"input":{"type":"string","minLength":1,"description":"The text to process (DATA — never interpolated into the instruction)."},"params":{"type":"object","description":"Task-specific params (e.g. targetLanguage), validated against the task's param_schema."}}}}}},"responses":{"200":{"description":"The task result + the cost-plus receipt.","content":{"application/json":{"schema":{"type":"object","required":["result","truncated","usage"],"properties":{"result":{"type":"string","description":"The task output text (or JSON string for extract/json_repair)."},"truncated":{"type":"boolean","description":"True when the output hit the per-task token cap; the charge still stands."},"usage":{"type":"object","required":["model","inputTokens","outputTokens","providerCostMicros","markupPct","chargedMicros","ceilingMicros"],"properties":{"model":{"type":"string"},"inputTokens":{"type":"integer"},"outputTokens":{"type":"integer"},"providerCostMicros":{"type":"integer"},"markupPct":{"type":"number"},"chargedMicros":{"type":"integer","description":"The actual charged amount (<= ceiling)."},"ceilingMicros":{"type":"integer","description":"The authorized ceiling the 402/hold reserved."}}}}}}}},"401":{"description":"UNAUTHENTICATED OR INVALID_X402_SIGNATURE."},"402":{"description":"PAYMENT_REQUIRED (x402 `upto` challenge advertising the published ceiling)."},"413":{"description":"INPUT_TOO_LARGE (input exceeds llm.max_input_kb)."},"422":{"description":"UNKNOWN_TASK / UNKNOWN_TIER (lists the active ones) / INVALID_PARAMS / OP_DISABLED / PROVIDER_UNAVAILABLE / UPSTREAM_ERROR (4xx)."},"503":{"description":"UPSTREAM_ERROR (provider 5xx/timeout — charge reversed)."}},"x-payment-info":{"protocols":["x402"],"scheme":"exact","asset":"USDC","networks":["eip155:8453","eip155:84532"],"price":{"mode":"dynamic","currency":"USD","min":"0.001","max":"1.00"}},"operationId":"runLlmTask"}},"/v1/catalog/products":{"get":{"tags":["discovery"],"summary":"Product catalog","description":"The live product families (Store & hand off / Documents & identity / Compute & transform) — the plain-HTTP twin of the relaystation_products MCP tool. FREE, no auth. Thin by design: it carries families + descriptions + tool counts, NOT per-product prices — compose price one-liners from the live discovery routes (GET /v1/baton/prices, /v1/doc/formats, /v1/llm/tasks) so a re-priced product or a registry-created task shows up with zero redeploy.","security":[],"responses":{"200":{"description":"The product catalog.","content":{"application/json":{"schema":{"type":"object"}}}}},"operationId":"listCatalogProducts"}},"/v1/account/activity":{"get":{"tags":["account"],"summary":"Account activity stream","description":"Paginated ledger rows for the dashboard Home stream, each enriched with a product chip (the service-key prefix), the subject (subjectType + subjectId, for the object deep-link), the acting credential (actor.kind ∈ mcp_connection|api_key|session|x402_wallet, actor.id), and — for LLM rows — the cost-plus receipt (metadata.receipt). Server-side filters: ?actor=<actorId> (per-connected-agent stream), ?product=<key> or ?service_prefix=<prefix> (workspace projection, e.g. cputools.). Returns { data: [...], total, limit, offset }.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200},"description":"Page size (default: chassis.dashboard.activity_page_size)."},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}},{"name":"actor","in":"query","schema":{"type":"string"},"description":"Filter to one acting credential (the per-agent stream)."},{"name":"product","in":"query","schema":{"type":"string"},"description":"Filter to a product (sugar for service_prefix=<product>.)."},{"name":"service_prefix","in":"query","schema":{"type":"string"},"description":"Filter to a service-key prefix (e.g. cputools.)."}],"responses":{"200":{"description":"The activity page.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"UNAUTHENTICATED."}},"operationId":"getAccountActivity"}},"/v1/account/metrics":{"get":{"tags":["account"],"summary":"Account home metrics","description":"The dashboard Home metric cards: balance (balanceMicros / availableMicros), the active-baton summary (count / storageBytes / nextExpiry), the \"spend this week\" totals (spentMicros / calls / agents) over the trailing chassis.dashboard.spend_window_days window, and usedProducts — the products this customer has used (most-recent first; {product, lastUsedAt, calls}) that drives the dashboard's dynamic \"Your workspaces\" nav. All free reads.","security":[{"apiKey":[]},{"walletJwt":[]},{"x402":[]}],"responses":{"200":{"description":"The metrics summary.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"UNAUTHENTICATED."}},"operationId":"getAccountMetrics"}}}}