Developer Docs
L402 + MCP + WebLN. One scrollable page.
If you are an agent
Do not read this page. Everything here in machine-readable form, with live prices, is one fetch away.
- sats4ai.com/llms.txt — services, prices, a full worked example
- sats4ai.com/api/mcp — MCP endpoint, tools/list works unauthenticated
- /api/openapi.json — OpenAPI 3.1
No key is needed to read any of those, and none is needed to call a service. Pay the invoice in the 402 and retry.
If you are a human
Start with the quickstart in your language, then dip into the reference sections as you need them.
- Quickstart — curl, Python, Node, browser
- Error codes — what each one means and how to recover
- Troubleshooting — the failures people actually hit
Prefer to click rather than curl? Every service has a page under the sidebar — no docs required.
How It Works
Every paid endpoint uses the L402 protocol — a three-step challenge/response flow where the Lightning invoice is the credential. No API keys, no accounts, no signup.
Hit any endpoint without auth. You get back HTTP 402 with a Lightning invoice and a macaroon.
Pay the invoice with any Lightning wallet. The payment proof (preimage) is your credential.
Resend with Authorization: L402 macaroon:preimage. Get your result.
How this differs from a normal AI API
Payment is authorization, not authentication — there isn't even a login endpoint.
| Sats4AI | A typical hosted AI API | |
|---|---|---|
| Signup | None | Account + email |
| Credential | A paid Lightning invoice | API key / OAuth token |
| Identity / KYC | Not required | Often required |
| Billing | Per request, in sats | Monthly plan / prepaid balance |
| If a call fails | Automatic Lightning refund (lnurl_withdraw) | Credits, or a support ticket |
| Data records | Minimal operational records | Usually account-linked |
Getting Started
Follow these steps to go from zero to your first API call.
- 1Get a Lightning wallet. For the manual L402 flow, your wallet must show the payment preimage after paying — that's your credential. Tested and confirmed: Phoenix, Breez, Zeus, and Blink all display it in payment details. Some wallets (Muun, Wallet of Satoshi) hide the preimage — see the wallet matrix. Using MCP instead? Any BOLT11 wallet works — the
paymentIdflow never needs the preimage. For agents, use Lightning Wallet MCP for automated payments. - 2Pick your integration. Three options:
- L402 (HTTP) — standard REST calls with Lightning auth. Works with any language.
- MCP — add one line to your Claude/Cursor config. Payment negotiated in-protocol.
- WebLN — browser apps with Bitcoin Connect or Alby.
- 3Discover services. Browse the service catalog, or let your agent discover tools programmatically:
GET /api/discover?q=generate portrait— keyword search (relevance-ranked)GET /api/estimate-cost?service=image— pricing before paymentGET /.well-known/l402-services— full machine-readable catalog
- 4Make your first call. Copy the quickstart example below, pay the invoice, and get your result. Most services cost 5-200 sats ($0.005-$0.20).
Quickstart — pick a flow
Four ways to use Sats4AI. Pick the one that matches your client.
L402 (HTTP + Lightning)
For agents and CLI clients. 402 challenge, pay, retry. Works with any language.
# 1. Hit the endpoint without auth → get a 402 + invoice
curl -i -X POST https://sats4ai.com/api/models/image \
-H "Content-Type: application/json" \
-d '{"prompt":"a cat in a tophat"}'
# Response: 402 Payment Required
# WWW-Authenticate: L402 macaroon="...", invoice="lnbc..."
# 2. Pay the invoice with any Lightning wallet, get a preimage
# 3. Re-send with Authorization header
curl -X POST https://sats4ai.com/api/models/image \
-H "Authorization: L402 <macaroon>:<preimage>" \
-H "Content-Type: application/json" \
-d '{"prompt":"a cat in a tophat"}'Agent SDKs (zero protocol code)
Lightning Labs' L402sdk handles the full 402 flow for you — agent hits any Sats4AI endpoint, SDK pays the invoice automatically. Works with LND, CLN, or any NWC wallet (Alby, Mutiny, Phoenix).
// Lightning Labs L402sdk — Vercel AI SDK tools
// npm i @lightninglabs/l402-ai ai @ai-sdk/openai
import { createL402Tools, WasmL402Client, WasmBudgetConfig } from "@lightninglabs/l402-ai";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
// Connect the SDK to a Lightning backend (LND, CLN, NWC, or SwissKnife)
const client = WasmL402Client.withLndRest(
process.env.LND_URL!,
process.env.LND_MACAROON!,
// Budget caps: perRequest=1000 sats, daily=50_000 sats
new WasmBudgetConfig(1000, 0, 50_000, 0),
100, // request timeout (s)
);
// Gives the agent l402_fetch / l402_get_balance / l402_get_receipts
const tools = createL402Tools({ client });
const result = await generateText({
model: openai("gpt-4o"),
tools,
maxSteps: 5,
prompt: "Generate an image of a cat in a tophat using https://sats4ai.com/api/models/image (model: nano-banana-2). Return the image URL.",
});
// The agent autonomously hits the 402, pays the invoice, retries with L402 auth.MCP (Claude / Cursor)
Inline payment negotiation handled by the MCP transport. Plain HTTP can't do this.
// claude_desktop_config.json (or ~/.claude.json / Cursor)
{
"mcpServers": {
"sats4ai": {
"url": "https://sats4ai.com/api/mcp"
}
}
}
// Then in your MCP client:
// 1. tools/call create_payment → returns Lightning invoice
// 2. Pay with any Lightning wallet
// 3. tools/call <tool_name> with the paymentId → results
//
// Or use lightning-wallet MCP for fully-automated payments.Client quirks: Windsurf uses serverUrl (not url) in ~/.codeium/windsurf/mcp_config.json, and Cascade caps 100 tools across all MCP servers. Claude Code: claude mcp add sats4ai --transport http https://sats4ai.com/api/mcp. Clients without remote-HTTP support: run the stdio proxy, npx sats4ai-mcp. Restart the client after config changes.
WebLN (Browser)
For browser apps with Alby / Bitcoin Connect.
// Browser flow with Bitcoin Connect / Alby
import { requestProvider } from "@getalby/bitcoin-connect";
const res = await fetch("/api/charge?service=image&model=...");
const { invoice, paymentId } = await res.json();
const provider = await requestProvider();
await provider.sendPayment(invoice);
// Submit the work request
const fd = new FormData();
fd.set("paymentId", paymentId);
fd.set("prompt", "a cat in a tophat");
const out = await fetch("/api/models/image", { method: "POST", body: fd });Payment Protocols
- L402 — HTTP 402 + Lightning. Macaroon issued in
WWW-Authenticate; pay invoice; resend withAuthorization: L402 macaroon:preimage. /l402. - MCP — JSON-RPC 2.0 over Streamable HTTP at
/api/mcp. Tools negotiate payment in-protocol; the agent never has to parse 402 responses. /mcp. - MPP — Machine Payment Protocol. Same surface as L402, slightly different header naming. Both are accepted on every paid endpoint.
- WebLN — for browser flows. Use
/api/chargeto mint an invoice, pay via the wallet provider, then submit the work with thepaymentId. - Refunds — every post-payment failure includes an LNURL-withdraw link in the
refundfield. No support tickets needed.
Macaroon Caveats
Macaroons we issue are fail-closed: any unrecognised caveat rejects the request. Currently enforced:
RequestPath— macaroon binds to the exact endpoint path used for the 402 challenge.ExpiresAt— typically 10 minutes from issuance.PaymentHash— bound to the Lightning invoice; preimage required.Service+Model— bound to the model that priced the call. Auto-routed calls must be retried with the routed model id (see Auto-Routing below).
Error Codes
Every error response includes a machine-readable error_code in both the JSON body and the X-Error-Code header. Full catalog at /api/error-codes.
curl https://sats4ai.com/api/error-codes
# {
# "version": 1,
# "codes": {
# "TIMEOUT": "Request or upstream provider timed out. Retry later.",
# "CONTENT_FILTERED": "Output blocked by safety/content moderation. Rephrase the prompt.",
# "L402_INVALID_PARAMS": "Pre-payment validation failed. No invoice was created; no sats charged.",
# "L402_REFUND_ISSUED": "Response payload includes a refund object with an LNURL-withdraw link.",
# ...
# }
# }
# Every error response includes:
# - JSON: { "error": "...", "error_code": "TIMEOUT" }
# - Header: X-Error-Code: TIMEOUTAsync Jobs
Long-running services (audiobook, video, transcription, AI calls, 3D models) return HTTP 202 with a standard shape. Poll the poll_url at poll_interval_ms.
# 1. Pay + submit a long-running job
curl -X POST https://sats4ai.com/api/models/epub-audiobook \
-H "Authorization: L402 ..." \
-F "file=@book.epub" -F "voice=Ashley"
# Response: 202 Accepted
# {
# "status": "queued",
# "job_id": "abc123",
# "poll_url": "https://sats4ai.com/api/models/epub-audiobook/status?id=abc123",
# "poll_interval_ms": 3000,
# "estimated_completion_ms": 600000
# }
# Headers: X-Job-Id, X-Poll-Url, X-Poll-Interval-Ms
# 2. Poll the status URL until status === "COMPLETED"
curl https://sats4ai.com/api/models/epub-audiobook/status?id=abc123Webhooks / Callbacks
Skip the polling loop. Pass callback_url + callback_id on any async job and we'll POST you when it finishes. HMAC-signed, SSRF-validated, opt-in.
# OPT-IN — include callback_url + callback_id in your async request.
# Polling keeps working; the webhook is a supplement, not a replacement.
curl -X POST https://sats4ai.com/api/models/epub-audiobook \
-H "Authorization: L402 ..." \
-F "file=@book.epub" -F "voice=Ashley" \
-F "callback_url=https://your-app.example.com/hooks/sats4ai" \
-F "callback_id=user-42-job-7"
# Response: 202 Accepted
# {
# "status": "queued",
# "job_id": "abc123",
# "poll_url": "...",
# "callback_id": "user-42-job-7",
# "callback_registered": true,
# "callback_secret": "<per-job 64-hex HMAC secret>"
# }
# When the job finishes we POST your callback_url with:
# Headers: X-Sats4AI-Signature: sha256=<hex>
# Body: {
# "job_id": "abc123",
# "callback_id": "user-42-job-7",
# "status": "IN_PROGRESS" | "COMPLETED" | "FAILED",
# "result_url" | "error_code" + "error",
# "timestamp": "2026-04-13T..."
# }
# Verify the signature (Node):
# const mac = crypto.createHmac("sha256", callback_secret)
# .update(rawBody).digest("hex");
# const ok = req.headers["x-sats4ai-signature"] === "sha256=" + mac;
# Validation:
# - callback_url MUST be public HTTPS (no localhost / private ranges / IP literals in RFC1918)
# - callback_id is OPAQUE, max 128 chars, no control chars
# → do NOT embed PII or secrets; it is logged server-side
# - rejection → response includes "callback_registered": false + a reason.
# Your job still runs; poll poll_url instead.
# - retries: at 0s / 5s / 30s. 4xx aborts. Best-effort (single instance).Privacy note: callback_id is echoed back in our logs and the webhook body — treat it as an opaque correlation string, not a place to stash user data. Validation rejects http://, loopback, link-local, and RFC1918 hosts so an attacker can't point us at your metadata service.
Auto-Routing
For categories with multiple models (text, image, audio), pass "model": "auto" to let the server pick the best default. The chosen model id is returned in the X-Route-Model response header.
# Send model: "auto" — server picks the best for the category
curl -i -X POST https://sats4ai.com/api/l402/generate-image \
-H "Content-Type: application/json" \
-d '{"input":{"prompt":"a cat"},"model":"auto"}'
# Response: 402 Payment Required
# X-Route-Model: 16 ← the model we picked (numeric id, as in list_models)
# X-Route-Category: Image
# X-Error-Code: L402_AUTO_ROUTED
# The macaroon is bound to that model. Reusing "auto" on the paid call works, but
# pinning the id you were quoted is safer: if the category default changes between
# the quote and the retry, "auto" resolves elsewhere and the binding check fails.
curl -X POST https://sats4ai.com/api/l402/generate-image \
-H "Authorization: L402 <macaroon>:<preimage>" \
-H "Content-Type: application/json" \
-d '{"input":{"prompt":"a cat"},"model":16}'URL-path model selection
Convenience for clients that prefer paths over body fields:
# Convenience: model in URL path
curl -X POST https://sats4ai.com/api/m/text-generation/gpt-oss-120b \
-H "Content-Type: application/json" \
-d '{"prompt":"hello"}'
# Forwards to /api/models/text-generation with model injected.
# Body field still wins if both are set.Estimate Cost
Pre-payment quotes for budget-aware agents. No auth, no side effects. /api/estimate-cost with no params returns the catalog.
# Get a quote before paying
curl 'https://sats4ai.com/api/estimate-cost?service=text-to-speech&model=omnivoice&chars=1500'
# {
# "service": "text-to-speech",
# "amount_sats": 15,
# "currency": "BTC",
# "breakdown": { "type": "per-character", "chars": 1500, "chars_per_sat": 100, "model": "omnivoice" },
# "error_code": "L402_ESTIMATE_ONLY"
# }
# List the catalog
curl https://sats4ai.com/api/estimate-costRequest Deduplication — not supported
This page previously described a 30-second response cache and an X-Dedup header. Nothing implemented it. We have retracted the claim.
Do not treat a paid request as retry-safe. Repeating an authenticated request does not return a cached result — it returns 403 Service already used, because the charge is consumed on first execution.
For safe retries, keep the paymentId from your original call and reuse it: one payment maps to one execution, and re-presenting it is how you recover from a network blip without paying twice. If a call was charged but never delivered, claim it at /recover or POST /api/l402/refund — see error codes for the refund fields on a failed response.
CORS / Browser Use
All payment + routing headers are explicitly listed in Access-Control-Expose-Headers so browser fetch() can read them.
# Sent on EVERY /api/l402/* response — the 402, the 200, the errors, and the
# OPTIONS preflight — from one rule in next.config.js. Any origin.
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, X-No-Cache, Payment-Signature, X-Cashu,
X-Agent-ID
Access-Control-Expose-Headers: WWW-Authenticate, Payment-Receipt, X-Sats4AI-Cost-Sats, X-Sats4AI-Charge-Id,
Payment-Required, X-Route-Model, X-Route-Category, X-Dedup, X-Job-Id,
X-Poll-Url, X-Poll-Interval-Ms, X-Error-Code
# So in the browser, from any page — including one you host on Nostr:
const res = await fetch("/api/models/image", { method: "POST", body });
res.headers.get("WWW-Authenticate"); // ← visible
res.headers.get("X-Error-Code"); // ← visibleServices
30+ AI services. Per-service docs live at /l402 (curl + payment examples) and the MCP tool catalog at /mcp.
Machine-readable discovery:
- /.well-known/l402-services — L402 manifest
- /.well-known/mcp — MCP server manifest
- /api/mcp/discovery — MCP tool catalog with pricing + performance
- /api/discover?q=... — keyword search (relevance-ranked)
- /api/l402/models — model + tier listing
Compound endpoints (recipes)
Chained pipelines with a single payment and an outcome-shaped output. Cheaper than chaining primitives manually for the common case.
transcribe_translate— audio → transcript → translation (119 target languages). Perfect for WhatsApp voice messages in a language you don't speak, or reading a meeting recorded in another language. Auto-detects source.open_voice_bridge+voice_bridge_say/_poll/_end— live phone call where your LLM is the brain. PSTN + 602-lang TTS + ~100-lang STT.epub_to_audiobook— EPUB/PDF/TXT → M4B audiobook in 600+ languages with optional translation.extract_receipt— receipt image → structured JSON (merchant, total, line items).translate_rare_language— 452 languages, 251 of them unsupported by ChatGPT, Claude or Gemini (Bhojpuri, Maithili, Egyptian Arabic, Magahi…), each with a measured quality tier and 29 verified fair-or-better. 50 sats + 0.002/char.e_signature— send a PDF out for legally binding e-signature; signed copy returned by email. 1000 sats, up to 3 signers.
Capability-first GitHub repos
Per-service landing repos with runnable curl, Python, and TypeScript examples. MIT-licensed. Use as drop-in starting points or reference implementations.
- ai-caller — AI voice agent API (alternative to Vapi, Retell, Bland)
- book-to-audiobook — EPUB/PDF/TXT to audiobook in 600+ languages, with optional translation
- pay-per-use-fax — Send a fax with Lightning, no contract or monthly fee
- fax-to-email — Receive faxes to your inbox, no monthly number rental
Voice Bridge — a la carte phone calls
Live phone calls where your LLM is the brain. Sats4AI supplies composable primitives: PSTN + streaming STT + TTS. You drive each turn. Conversation context stays on your side — we never see it.
Four endpoints: /open (pay + place call) → /poll (get transcripts) → /say (text or raw audio) → /end (hangup + refund). Deposit billing, priced per destination off the carrier rate sheet and BTC-pegged — POST without Authorization for the exact deposit. Unused time auto-refunded.
For a turnkey voice agent where we run the brain, use ai_call instead. Voice Bridge is for agents that already have a brain and just need a phone line, ears, and a mouth.
- STT: Gladia primary (~100 languages, free first 10 h/mo), Deepgram Nova-3 failover.
- TTS: OmniVoice (602 languages). BYO audio supported via
audio_base64+encoding: mulaw_8000 | pcm_l16_16000. - Coverage matrix: /api/l402/voice-bridge/coverage.
- MCP tools:
open_voice_bridge,voice_bridge_say,poll_voice_bridge,end_voice_bridge.
Choosing a Voice Tier (TTS)
Three tiers optimized for different use cases:
| Tier | Languages | Price | Quality | When to Choose |
|---|---|---|---|---|
| OmniVoice Global | 602+ | ~90 chars/sat | Good | Rare/underserved languages (Yoruba, Marathi, Twi, Cebuano). Widest coverage by far. |
| Inworld Premium | ~9 | ~13 chars/sat | Best (ELO #1) | English and major languages where quality matters most. Highest fidelity voice. |
| Minimax Studio | ~9 | ~4 chars/sat | Great | Voice cloning. Use when you need a specific voice (your own, a character, a brand). |
Choosing a Text Model (LLM)
Pass model: "auto" to let us pick the best model in the category — the 402 tells you which one in X-Route-Model. Or choose explicitly:
| Model | Maker | Price (sats) | When to Choose |
|---|---|---|---|
| GPT-OSS 120B | OpenAI | ~0.001 sats/char | Cheapest. 119 languages, ultra-fast. Best for translation and everyday tasks. |
| Kimi K3 | Moonshot AI | ~0.1 sats/char | Smartest open-weight model available (Artificial Analysis Intelligence Index: 57). 1M context, vision, thinking mode. For complex reasoning and analysis. |
Recipes (Compound Outcomes)
Two kinds of services live on Sats4AI: primitives (single capability, one call — generate-image, translate-text, send-sms) and recipes (compound outcomes we assemble from multiple primitives so you don't have to — stateful, real-time, or multi-step). Both are first-class. Orchestrators that want fine control use primitives. Agents and users that want an outcome in one call use recipes.
Every discovery entry carries endpoint_type: "primitive" | "recipe". Filter on it to list only recipes:
curl https://sats4ai.com/.well-known/l402-services \
| jq '.services[] | select(.endpoint_type == "recipe") | .id'Recipes available today
| Recipe | Primitives chained | Outcome |
|---|---|---|
| extract-receipt | OCR + LLM | Receipt or invoice → structured JSON (merchant, line items, totals, tax, currency, category). |
| extract-document | pdf.js + OCR + layout analysis | PDF or image → clean Markdown. Smart routing per page: text-layer when present, OCR when scanned, hybrid when mixed. |
| boardingpass-wallet | Barcode decode + OCR field extraction + Google Wallet pass build | Airline boarding passes (PDF or screenshot) → Google Wallet passes for a whole family: one save link per passenger (up to 8) plus an add-all link, with boarding zone, gate, seat, and boarding time. Original barcode preserved byte-for-byte. |
| epub-audiobook | parse + TTS + translate (optional) + assemble | EPUB/PDF/TXT → M4B audiobook with chapter markers. Resumable, 602-language narration, optional translation before narration. |
| ai-call | PSTN + STT + LLM + TTS | Send an AI agent to make a two-way phone call. Our brain. Auto-retries, transcript + analysis returned. |
| voice-bridge | PSTN + streaming STT + TTS (session) | Real-time phone call where YOUR LLM is the brain. /open → /poll → /say → /end. Conversation context never leaves your side. |
| send-fax | Fax transport + page accounting | Send a fax (PDF URL or typed text) to any number worldwide. Optional cover page. Tiered pricing per page. |
| receive-fax | Fax receive + caller-ID match + email delivery (+ optional OCR) | Open a 24h receive window at our shared number, matched by caller ID. Delivered to your email with optional OCR add-on. |
When to prefer a recipe over chaining primitives
A recipe earns its existence when external orchestration is genuinely hard — real-time sync (voice-bridge, ai-call), cross-step state (epub-audiobook resumability, fax page accounting), regulatory bundling (fax), or a coverage/quality chain only we have end-to-end. If an agent could replicate the outcome with two independent calls, it stays a primitive. New recipes follow the naming pattern <outcome>-<input-or-format> (e.g., extract-receipt, not receiptExtractor).
Limits & Constraints
The hard boundaries of each service, stated up front — on a pay-per-call API every limit you discover by trial costs sats. Parameters are validated before an invoice is created wherever possible; the gotchas column lists the cases that can only fail after payment (those responses include a refund link).
Applies to every service:
- The L402 macaroon expires 10 minutes after the 402 challenge — pay and redeem promptly, or request a fresh invoice.
- A paid request is not retry-safe: repeating an authenticated call returns
403 Service already used, not a cached result. Reuse the originalpaymentId(see Request Dedup — not supported). - Result download URLs are temporary (~1 hour) — fetch your output promptly.
- Outbound communication (SMS, email, calls) is screened after payment; held requests are not delivered, and payment is retained during review (see Scam Screening).
| Service | Hard limits | Won't work / gotchas |
|---|---|---|
| translate-epub | EPUB only, 50 MB max, 3,000,000 visible characters max. 119 target languages. Async: minutes to tens of minutes for a full book. | Priced per VISIBLE character on the target language's engine rate (GET /api/languages), min 50 sats — markup rides free. Over MCP, create_payment needs characterCount; the real file is re-priced at execution and a short-pay is refunded with the exact amount. The download url is temporary (6h token, file kept ~12h). A chapter whose markup the engine cannot preserve fails the job and refunds — the book is never delivered half-translated. On the WEB page you can untick sections (front matter, an index) and pay only for what you keep: unticked sections are copied through in the original language, and the table of contents follows what you translated. Section selection is web-only — the L402 endpoint and MCP refuse a `skip` parameter rather than quote the whole book for it. |
| generate-image | Reference-image capacity varies by model (1 to 14 images). | A prompt can be rejected by the safety filter after payment. |
| generate-text | maxTokens default 16,384. Vision (imageData) on the Best tier only. | Raw PDFs are not accepted — run extract-document first and pass the text as fileContext. |
| send-sms | 1544 user characters max (an unverified-sender notice is prepended within the paid segment count). Billed per SMS segment, so a longer message costs proportionally more. Per-destination caps: 3/hour, 8/day — counted across ALL customers. | URLs are rejected before payment (422). Accented characters switch encoding and cost more segments. Non-+1 destinations are sent from an alphanumeric sender — the recipient cannot reply. |
| generate-video / animate-image | Duration 5-15 s. Resolutions: 768p, 2K only (480p/720p/1080p still accepted as retired aliases). animate-image needs a base64 first frame. | Async — poll the job. A low-quality first frame can fail after payment (image_quality_too_low). |
| generate-music | Style prompt ≤ 2,000 chars; lyrics ≤ 3,500 chars; songs 15-300 s (you choose, 5 sats/s). | Each structure tag must be ALONE on its line — words typed on a tag line are dropped by the model and the song comes out short. Tags with no words = instrumental. duration is the price and is bound to the invoice. |
| render-card | headline: 3 lines × 40 chars. list: 6 items × 40 chars. versus values 22 chars, labels 28. stat value 12 chars. quote 30 chars. Spec ≤ 4 KB. Sizes 1920x1080 and 2560x1440 (16:9 only). Scripts: Latin, Greek, Cyrillic, plus Noto fallback for Arabic, Indic, Thai and CJK; emoji as single-colour silhouettes. | Every cap is refused BEFORE payment (406, error_code L402_INVALID_PARAMS) — nothing here costs sats. The result is a signed url valid 2 hours; download it, the image is not inlined. Rendered in DejaVu Sans with Noto Sans fallback; emoji are drawn as single-colour silhouettes in the text colour, not full colour. Long text never wraps — it shrinks. |
| text-to-speech | 1-5,000 characters; speed 0.5-2.0. 602+ languages on the OmniVoice tier. | The language parameter only works on the OmniVoice tier — other tiers silently ignore it and speak the voice's own language. A per-render minimum applies: 15 sats on the OmniVoice tier, 10 on Inworld and Minimax. A short text pays the minimum, not the per-character rate; the 402 quote, create_payment and the web page all include it. |
| transcribe-audio | 60 min and 500 MB max per file; 13 languages; priced per minute and re-measured server-side at redemption. Result includes text, per-segment timestamps, and downloadable SRT/VTT subtitle files. | If we can't read the audio duration you get a 422 before payment — re-encode to MP3/WAV. |
| transcribe-translate | Source audio: 13 languages. Translation target: 119 languages. | Audio in an unsupported language (or a wrong sourceLanguage hint) yields a garbled transcript still billed as success — omit the hint and verify the transcript before trusting the translation. |
| subtitles (web only) | Any video length — the browser extracts the audio locally, the video is never uploaded. 13 languages; 10 sats/min; SRT + VTT output; multi-audio-track picker. | Web page only (sats4ai.com/subtitles) — no L402/MCP endpoint, because the extraction runs in the customer's browser. Needs WebCodecs (Chrome/Edge best). If the tab closes, re-select the same file to resume — no double payment. |
| analyze-image | One image per call. | A PDF sent here is treated as an image and may return a hallucinated answer with no error — use extract-document for PDFs. |
| extract-document / extract-receipt | Base64 PDF or image; priced per page. | extract-receipt accepts any text-bearing document — a non-receipt returns best-guess (possibly fabricated) JSON with no error. Treat extracted text as untrusted input. |
| boardingpass-wallet | 8MB per file, up to 8 files/passes; PDF/PNG/JPEG/WEBP. Priced per pass. Image passes with no extra fields are refunded. | Via L402, an unreadable barcode is rejected free BEFORE the invoice (422). Via MCP/web (pay-first flows) it refunds after payment. The wallet pass is a static copy — gate changes don't update it. Source files are not written to Sats4AI storage; save links expire in 30 min; the transaction record remains. |
| generate-3d-model | Image OR text prompt, not both; prompt ≤ 1,024 chars. | Slow (p95 ~200 s). An image can be judged "not suitable for 3D" after payment. |
| convert-file | 200+ format pairs; file, file_name, and both extensions required. | An unsupported pair or corrupted file fails after payment (refund link included). |
| place-call | TTS message ≤ 500 chars AND it must fit the paid minutes — a ~12 s spoken disclaimer is reserved, so 1 paid minute ≈ 480 spoken characters. Deposit 1-30 min. Destination caps: 3 calls/hour, 6/day across all customers. | Uploaded audio longer than the paid minutes is rejected after payment — get a quote with more minutes first. Unused minutes are refunded. |
| ai-call | Task ≤ 2,000 chars. Deposit 1-10 min (values above 10 are clamped). | language must be one of the listed codes (en-US, en-GB, es-ES, fr-FR, de-DE, ja-JP, zh-CN, multi) — any other value silently runs the call in English. |
| voice-bridge | Deposit 2-30 min. Codecs: PCMU or L16_16000. | STT covers ~100 languages vs 602 for TTS — check GET /api/l402/voice-bridge/coverage for your language before paying. |
| send-fax | 350 pages / 50 MB max. PDF mode needs a public HTTPS URL serving application/pdf. | The pages field sets the invoice: overpayment is not refunded, underpayment errors with a refund. A cover page adds one billable page. |
| receive-fax | 24-hour receive window, matched by the last 10 digits of the sender's caller ID. | No refund if no fax arrives. Faxes over 10 pages trigger a separate overage invoice before delivery. |
| send-email | Subject ≤ 200 chars; body ≤ 10,000 chars. | Plain text only — no attachments, no HTML. |
| translate-text | 119 languages; 1 sat per 1,000 characters on the standard engine (min 1 sat). The target language picks the engine, so a routed language costs more — GET /api/languages for the per-language price. | A wrong-but-valid sourceLanguage silently mistranslates with no error — omit it and let detection work. |
| epub-audiobook | .epub, .pdf, or .txt only; 50 MB max; 500 sats minimum. | On the default tier the spoken language follows the chosen VOICE, not the language field. The auto chapter filter can drop very short chapters — pass selectedChapterIndices to force inclusion. |
| clone-voice | Audio URL or base64 sample. MP3, M4A or WAV, 10 seconds to 5 minutes, 20 MB max. | The web form checks length before you pay. On the API there is no pre-payment check: a sample outside those limits, or one with several speakers or background music, fails after payment. |
| image utilities (upscale, restore-face, deblur, colorize, remove-object, remove-background) | upscale: factor 2 or 4 only. deblur: camera-shake/uniform blur only. | Content mismatches fail after payment: no face in restore-face, an already-color image in colorize, artistic bokeh in deblur. |
| merge-pdfs | Minimum 2 files. | The output URL is temporary (~1 hour) — download promptly. |
Wallets
Human wallets — manual L402 flow
The manual flow (pay invoice, copy preimage, send Authorization: L402 macaroon:preimage) only works if your wallet shows the preimage after paying. Verified status per wallet:
| Wallet | Shows preimage? | Notes |
|---|---|---|
| Phoenix ★ | Yes | Copyable “Preimage” row in payment details (Android + iOS). Self-custodial. Smoothest first-time path. |
| Blink ★ | Yes | “Preimage / Proof of Payment” in transaction detail. Easiest custodial on-ramp, instant setup. |
| Zeus ★ | Yes | Preimage in the payment view. Best for node-runners (works standalone or against your own node). |
| Breez | Yes | Payment-details sheet shows the preimage. |
| Alby (extension / Hub) | Yes | WebLN sendPayment() returns the preimage to your code; Alby Hub shows a copyable row. |
| lncli / lightning-cli | Yes | payment_preimage in the command output, by design. |
| Muun | No | No preimage in the UI as far as we can verify. Fine for paying, but you can't complete the manual flow. |
| Wallet of Satoshi | No | Community reports say the preimage isn't shown. Use the MCP flow instead. |
★ = recommended starting point. The MCP paymentId flow works with any BOLT11 wallet — no preimage needed. Statuses last verified July 2026; wallet UIs change, so tell us if one is wrong.
Agent wallets
AI agents need a Lightning wallet to pay invoices autonomously. These are complementary tools that give your agent a wallet to spend at Sats4AI:
| Wallet | Type | Best For |
|---|---|---|
| L402sdk | SDK (TS/Python/Go/Rust) | Vercel AI SDK + LangChain agents. Auto-pays L402, built-in budgets. Supports LND, CLN, NWC. |
| Lightning Wallet MCP | MCP tool | Claude, Cursor, any MCP client. Fully automated L402 payments. |
| Alby MCP | MCP tool | Alby Hub users. Self-custodial. |
| lnget | CLI | Shell scripts, CI pipelines. Auto-pays L402 invoices. |
| CLW Cash | CLI wallet | Bitcoin CLI wallet purpose-built for AI agents. |
| Glow Cloud | REST API | Self-deployable wallet API (Breez Spark SDK). Deploy to Vercel free tier. |
Any wallet that can pay a BOLT11 invoice can pay us; the tools above just automate the payment step so agents can operate without human intervention. For the manual L402 flow the wallet must also show the preimage — see the matrix above.
Production Checklist
Before going live, verify your integration handles these scenarios:
- ☐Handle refunds. Post-payment errors include a
refundobject with an LNURL-withdraw link. Surface this to users or redeem it programmatically. - ☐Check
error_codenot just HTTP status. Use theX-Error-Codeheader or JSONerror_codefield to decide retry vs. rephrase vs. escalate. Fetch/api/error-codesonce at startup. - ☐Respect
poll_interval_mson async jobs. Polling faster wastes requests and may trigger rate limits. - ☐Set a budget. If your agent auto-pays invoices, configure a per-call or daily spending limit in your wallet to prevent runaway costs.
- ☐Handle macaroon expiry. Macaroons expire after ~10 minutes. If payment takes longer, request a fresh invoice.
- ☐Use auto-routing carefully. When
model: "auto"returns a routed model inX-Route-Model, use that exact model id on the paid retry. See Auto-Routing. - ☐Validate callback signatures. If using webhooks, verify the
X-Sats4AI-SignatureHMAC before trusting the payload. See Webhooks. - ☐Estimate costs first. For budget-sensitive flows, call
/api/estimate-costbefore committing to payment.
Troubleshooting
I paid the invoice but got "invalid macaroon"
The macaroon expires ~10 minutes after the 402 challenge. If you took too long to pay, request a new invoice by re-sending the original request without auth. Also verify you're sending both the macaroon and the preimage separated by a colon: Authorization: L402 macaroon:preimage.
My async job is stuck in "processing"
Some jobs (audiobooks, 3D models) can take several minutes. Check estimated_completion_ms in the 202 response. If a job exceeds 2x the estimate and is still processing, the upstream provider may have failed. The job will eventually time out and the response will include a refund LNURL-withdraw link.
I paid, then my client crashed (or the tab closed) and I never got the result
You don't lose the sats. Every paid-but-undelivered charge is caught by an automatic reconciler: if a payment settles and the service is never delivered — tab closed, network dropped, your agent died before polling — the payment is queued as a refund after a grace period (roughly 15–90 minutes depending on the service).
Claim it at /recover with your Payment Hash — the 64-character hex string in your wallet's payment details (not the preimage). No email or account needed. Completed audiobooks can also be re-downloaded there.
If the grace period has clearly passed and /recover finds nothing, the service most likely did deliver — /recover will show what the charge was for.
I got L402_INVALID_PARAMS before paying
Parameters are validated before an invoice is created. No sats were charged. Check the error field for specifics — common causes: missing required field, unsupported model name, file too large, or invalid enum value. Use /api/discover or /.well-known/l402-services to see valid options.
CORS errors in the browser
All endpoints return Access-Control-Allow-Origin: * and expose payment headers. If you're seeing CORS errors, check that you're not sending custom headers that aren't in our allow list. See CORS / Browser for the full header list.
My agent keeps getting charged for retries
There is no response cache — we previously documented one, and that was wrong. A retry that mints a new payment is a second purchase, which is how agents get charged twice.
Fix it by retrying with the same paymentId instead of calling create_payment again. One payment maps to one execution: re-presenting it either delivers the result or tells you it was already used, and neither charges you again. If the charge was consumed without delivery, claim it at /recover.
“Your wallet's spending limit blocked this payment.” — what it means, and how to fix it
You saw that message because your connected wallet refused the payment and told us why: the spending limit on the wallet connection, not your balance. The page also opened a QR code for the same invoice so you can still pay. (If a QR code appeared with no message, the wallet refused without saying why — the steps below still apply, and the exact reason is in your browser console.) Wallets that connect through Nostr Wallet Connect — coinos, Alby Hub and others — attach a budget, and usually a per-payment maximum, to each connection when it is created. A 1-sat payment goes through; a 300-sat call does not.
Three things to know. The limit belongs to the connection, so changing a number in your wallet's settings after pairing may not change the connection Sats4AI is holding. It is not your balance, so topping up does nothing. And the renewal period matters more than the amount: a budget set to renew never is spent for good once you use it, so a single call can strand every call after it — the wallet then reports something like “Budget exceeded: 1112 of 600 remaining”.
Fix, in your wallet: open the Sats4AI connection, raise the budget and the per-payment maximum comfortably above what you plan to spend, and set the renewal period to monthly rather than never. If the change does not take, disconnect here and pair again with those settings: click the blue Connected button at the top right of any service page (it shows your balance), then Disconnect in the window that opens, then connect the wallet again.
Or skip the wallet connection entirely. When a connected wallet refuses, the page shows a QR code for the same invoice underneath — scan it with any Lightning wallet, including the one you just connected. No limits are involved, and it is the fastest way to get the call you were trying to make.
Nothing was charged for a payment your wallet refused. If you want to send us the exact message, your browser console holds it on a line starting [WebLN] sendPayment rejected:.
How do I get a refund?
Every post-payment error includes a refund field with an LNURL-withdraw link. Open it in any LNURL-compatible wallet to claim the refund. If a refund link is missing or expired, go to /recover and enter your Payment Hash — pending refunds are claimable there self-service, no email needed. If /recover also comes up empty, email support with the payment hash and error details.
Which model should I use?
Use model: "auto" and let the server pick. Or call /api/estimate-cost to compare pricing, and /api/l402/models for the full model list with capabilities. The /.well-known/l402-services manifest includes performance metadata (latency, reliability) per model.
API Changelog
Dated record of changes to the external surface — new tools, renamed parameters, pricing-shape changes, deprecations. Internal changes aren't listed. If you integrated a while ago, scan this before assuming your cached tool list is current.
- 2026-09-07[all] A length refusal AFTER payment now classifies INVALID_INPUT (406), not SERVICE_ERROR (500). When a request is refused because the text or audio does not fit what was paid for — a TTS call message longer than the paid minutes, an over-cap prompt, a too-long SMS, fax, email or task — it carried error_code SERVICE_ERROR and HTTP 500, which reads as our fault and advises "try a different model". The refusal was correct; only the label was wrong. It is the caller's to fix: shorten the input, or pay for more. Pre-payment length checks are unchanged — they already answered 406 or 413 with L402_INVALID_PARAMS and cost nothing. Affects the post-payment path on place-call, send-sms, send-fax, send-email, e-signature, generate-3d-model, translate-rare-language, multilingual-ask, ai-call and the MCP equivalents. Additive: branch on error_code, not on the error text. Refunds are unaffected — a length refusal after payment still returns your sats through the LNURL link in the error.
- 2026-09-06[web] text-to-speech web page: short texts were quoted below the per-render minimum, then refused after payment. The page priced from the per-character rate alone, while the server enforces a minimum per render (15 sats on OmniVoice, 10 on Inworld and Minimax). An OmniVoice text under about 1,100 characters (Inworld under about 100, Minimax under about 30) was quoted low, paid, refused as underpaid and refunded through the LNURL link in the error, so you got your sats back and no audio. Window: 2026-07-30 to 2026-09-06. Fixed 2026-09-06: the page now quotes the minimum and states it under the form. The L402 and MCP quotes applied the minimum the whole time and are unchanged. A refund link from this stays claimable for 30 days.
- 2026-09-04[all] NEW SERVICE — render-card (MCP: render_card), a primitive: a typographic card — headline, versus, list, stat or quote — rendered as PNG or JPEG at 1920x1080 or 2560x1440. Deterministic layout from structured input, no AI model, so the text you send is exactly what appears. Lists and comparisons render one beat at a time (revealed / show_right) for cuts that never shift the text. 5 sats flat. Every cap (3 lines × 40 chars, 6 items × 40 chars, 4 KB spec, hex-only theme colours, XML-renderable text) is enforced before payment with error_code L402_INVALID_PARAMS. Returns { url, width, height, format, mime, expires_in_seconds }; the url is a signed 2-hour download and the image is not inlined. Glyph coverage is Latin, Greek and Cyrillic. Also: generate_music now advertises is_instrumental (beds and underscore for narration) and convert_file names its common pairs (PDF→JPG/PNG among them) — copy only, no behaviour change.
- 2026-09-04[L402] The last four L402 routes without machine-readable errors now have them. translate-text's pre-payment 406/400s carry error_code L402_INVALID_PARAMS; job-status's 400/404s carry INVALID_INPUT and its 500 SERVICE_ERROR (all with X-Error-Code); every FAILED job-status body carries error_code, suggestion, example and, when retryable, retry_after_seconds — L402_REFUND_ISSUED when a refund is attached; POST /api/l402/refund answers INVALID_INPUT, PAYMENT_NOT_FOUND, SERVICE_ERROR or the new REFUND_NOT_AVAILABLE (delivered / already refunded / automated refund already queued — claim it at /recover). Additive: branch on error_code, not on the error text.
- 2026-09-03[discovery] Both L402 discovery documents (/.well-known/l402 under authentication, /.well-known/l402-services under features) now carry client_spend_cap: the reference client lnget refuses any invoice above 1,000 sats on ITS side, after the 402 and before paying, so the refusal is invisible to us and to your logs. The block names the default (exactly 1,000 passes), the flag to raise it, the flat services priced above it today, the metered services that can exceed it, and how our per-service tokens interact with its per-domain cache (one extra round-trip, never a failure). Additive; nothing else in either document changed.
- 2026-09-03[MCP] MCP error results now carry the same recovery fields on every path. Tool-call errors from the SDK/stdio server and the await_result FAILED final (when the status check or the result fetch itself failed) gain error_code, suggestion, example and, when the code is retryable, retry_after_seconds — the vocabulary JSON-RPC error.data on /api/mcp has carried since July. Error prose on those two paths is now sanitized like everywhere else: API-provider names replaced, URLs removed, capped at 300 characters. Additive: branch on error_code, never on the error text. Also fixed: a call_service name that is not in the registry is INVALID_INPUT (-32602, fix your request), not SERVICE_ERROR ("try a different model").
- 2026-09-03[all] NEW SERVICE — translate-epub (MCP: translate_epub), a recipe: a whole EPUB translated into another language, EPUB in and EPUB out. Every chapter keeps its markup — headings, emphasis, footnote links, images and code stay where they were — the package language is retargeted and the table of contents is translated. Priced per visible character on the target language's engine rate, the same rate translate-text charges for that language (GET /api/languages), minimum 50 sats; books over 3,000,000 characters are refused before payment. Async: 202 with a poll_url, jobType translate-epub; the result url is a temporary download (6h token, file kept ~12h). Over MCP, create_payment needs characterCount and targetLanguage — POST the file to the L402 endpoint without Authorization to have it counted — and the real file is re-priced at execution, refunding a short-pay with the exact amount. A web page is at /translate-epub.
- 2026-09-03[L402 / MCP] multilingual-ask and transcribe-translate now price their translation leg from the row the TARGET LANGUAGE routes to — the rule translate-text has followed since 2026-09-02. Until now the leg was a constant (0.001 sat/char; a flat 5-sat headroom) whatever engine ran. Nothing got cheaper and short requests are unchanged: the old constants stay as floors. A long transcript or a language served by a stronger engine now costs what that engine's row says, and the 402 (or create_payment with targetLanguage / language) is authoritative. estimate-cost's translate estimator accepts targetLanguage for the exact rate.
- 2026-08-28[discovery] endpoint_type RECLASSIFIED on three services: extract-document, extract-receipt and e-signature are now "primitive", not "recipe". They never met the bar — a recipe holds state across steps, synchronises in real time, or needs a credential you cannot get; extract-document is a single OCR call and extract-receipt is OCR then an LLM, which is the two ordinary calls the rule excludes. Nothing else changed: same endpoints, same prices, same behaviour. If you filter `endpoint_type == "recipe"` you will now get 7 services instead of 10, and the three above move into your primitive list.
- 2026-08-28[all] e-signature now states the turnaround you should expect: the signed copy arrives once every signer has signed, which is hours to days. Its performance block previously advertised p95 5,000 ms, which is the time to acknowledge the request, not to obtain a signature — that field is now labelled `latency_measures: "acknowledgement"` with a `completion_note`. Do not schedule or poll against latency_ms on this service.
- 2026-08-25[L402 / MCP] epub-audiobook results now carry annexeUrl alongside url. Content that cannot be read aloud — equations, tables, Bitcoin addresses, keys, hashes — is routed into a companion PDF and the narration speaks a numbered marker in its place ("see table 7.3"). annexeUrl is that PDF, and it is null when the book contained none of it. The field is additive and safely ignorable, BUT ignoring it now costs you something: the audio references the annex out loud, so a client that only reads result.url ships a book with unresolvable references. Previously this was built on the web surface only; L402 and MCP passed no annexe builder at all and narrated the raw hashes.
- 2026-08-21[all] NEW SERVICE — estimate-depth (MCP: estimate_depth), 5 sats per image. Per-pixel depth from a single photo using Depth Anything V2 (NeurIPS 2024). It returns TWO urls, not one: depth_map_url is the raw greyscale map you feed a ControlNet, AR or 3D reconstruction pipeline, and depth_color_url is the colour visualization for humans. Optional model_size (Small, Base, Large; default Large) changes accuracy, not price. There is deliberately no megapixel cap — the model resizes internally, so a large photo costs us the same as a small one.
- 2026-08-21[all] Text-to-speech and audiobook chars-per-sat moved with the hourly Bitcoin peg: OmniVoice 88 to 102, Inworld 13 to 15, Minimax 4 to 5 characters per sat. That is a PRICE CUT — more characters for the same sat, because Bitcoin rose. Per-character prices are re-quoted hourly and the 402 challenge is always authoritative; do not hardcode a rate.
- 2026-08-14[all] ai-call: our published price was wrong, and low. Eleven surfaces said "~150-250 sats for a 3-min US call" while the real quote was 642 — roughly a third of what we actually charge. Nothing about the charge changed and nobody was overcharged; the invoice was always correct and the documentation was not. It now reads "starting at ~200 sats/min", computed from the live Bitcoin rate at page load rather than typed in, so it cannot drift again. That figure is a FLOOR — every call pays the AI-agent rate wherever it dials, and telephony is added on top. As always, the 402 challenge (or create_payment) is the authoritative amount for your destination and duration.
- 2026-08-14[L402] Successful paid responses now carry two plain-text headers alongside Payment-Receipt: X-Sats4AI-Cost-Sats (what the call actually cost) and X-Sats4AI-Charge-Id (quote this if you report a problem — with no account it is the only identifier we can trace). Payment-Receipt already carried the settlement facts but is base64url JSON and has no amount in it. Both are additive; nothing that reads them today breaks.
- 2026-08-05[all] boardingpass-wallet is 100 sats per pass, and now actually charges it. The 2026-08-02 reprice from 150 updated the price everywhere it is published but missed the one constant the invoice is minted from. Web checkout therefore minted 150 against its own 100-sat quote and then rejected that payment as underpaid — you paid and were refunded — while L402 and MCP minted 150 against a published 100, so an agent budgeting the documented price got a 402 it could not satisfy. Fixed 2026-08-05. If you hardcoded 150 anywhere, you were overpaying by 50 per pass; quote from the 402 challenge rather than from any documented figure.
- 2026-08-02[all] PRICE CHANGE — remove-object 320 → 130 sats. Not a cost change: our house rule is roughly 2x cost and 320 was 5x. Low margin is the point — pay-per-call only stays compelling while the price is obviously fair.
- 2026-08-02[all] PRICE CHANGE — deblur-image 20 → 110 sats, remove-background 5 → 44, restore-face 5 → 25. All three were below the cost of the model behind them (NAFNet, BiRefNet and CodeFormer are billed per GPU-second). All three routes also gained a 25MP input cap, and restore-face no longer defaults to whole-frame enhancement.
- 2026-08-02[all] edit-image: the docs no longer suggest 'make it transparent'. Nano Banana 2 returns an OPAQUE image whatever the prompt says — png is a container, not an alpha channel. Use remove-background for a real cutout.
- 2026-07-31[all] PRICE CHANGE — remove-object is now 320 sats (was 10). The old price was below our upstream cost: the endpoint runs two models per request, and Bria Eraser is billed per output image, not per second of compute. Verified cost is ~$0.04/image.
- 2026-09-09[all] generate-music now runs MiniMax Music-3 and is priced PER SECOND: 5 sats/second, 15-300 s, so a 60-second song is 300 sats (was 500 flat) and a five-minute song is 1,500. Output is 44.1 kHz 16-bit stereo WAV, not MP3. The 2.6 flags is_instrumental and lyrics_optimizer are GONE — send structure tags with no words for an instrumental, or set lyricsAddon:true (+25 sats) to have the lyrics written for you. Each structure tag must be alone on its own line; words sharing a line with a tag are dropped by the model.
- 2026-07-31[all] PRICE CHANGE — generate-music is now 500 sats (was 300). Music-2.6 bills us a fixed amount in dollars per track, so a fixed sat price drifts against our cost as Bitcoin moves; 300 sats went below cost on a 20% BTC drop.
- 2026-07-31[all] remove-object FIXED: it had been failing on every request (the object-detection boxes were read in the wrong coordinate space, so nothing was ever masked). It returned a polite 'nothing found' and refunded, so it looked like a no-match rather than a bug. Now verified end to end.
- 2026-07-31[all] epub-audiobook FIXED on all three surfaces: the default voice in our own documented example was not available on the default engine, so a request copied from the docs paid and then refunded.
- 2026-07-31[all] Per-character prices (text and TTS) are now BTC-pegged and re-quoted hourly, so published chars-per-sat figures are approximate. The 402 challenge is always the authoritative price — quote from it rather than from any documented rate.
- 2026-07-31[all] Inbound fax was dead for roughly three months (the number had lost its carrier routing assignment) and is working again. Sending was unaffected.
- 2026-07-31[all] /api/estimate-cost no longer overquotes voice-cloning services by 25x — it was returning the wrong model within a shared type.
- 2026-07-30[all] PRICE CHANGE — the Best text tier (id 6) is now Kimi K3 at 10 chars/sat, up from Kimi K2.5 at 100 chars/sat. A 2,000-character prompt costs 200 sats instead of 20. If you pin model="best" or modelId=6 you will be quoted the new price automatically; nothing breaks, but re-check any hardcoded sat amount on your side. Why it costs 10x: K3 is a far more capable model and costs us ~10x more to run ($3.00/$15.00 per million tokens against K2.5's $0.45/$2.25, and it reasons by default). It scores 57 on the Artificial Analysis Intelligence Index — the highest-ranked open-weight model there — against K2.5's 35.4, and brings a 1M-token context window (up from 262K). Our margin on this tier is unchanged; you are paying for the model, not a markup increase. The Standard tier (GPT-OSS-120B, 1,000 chars/sat, id 31) is UNCHANGED — if you were using Best for everyday work to save a round-trip, Standard is now 100x cheaper and still the better choice for translation.
- 2026-07-29[all] Text tiers simplified from three to two: Standard (GPT-OSS-120B, 1,000 chars/sat, id 31) and Best (Kimi K2.5, 100 chars/sat, id 6). The middle "Better" tier (Qwen3.6 27B) is retired — Standard already outscored it in our own translation evals, so it added cost without adding capability. Requests for model="better" now return 406 INVALID_INPUT before any invoice is created, so nothing is charged for a tier we no longer serve. If you pinned modelId=1 you do NOT need to change anything: that ID keeps working and now serves GPT-OSS-120B at the Standard rate (1,000 chars/sat), which is cheaper than what it charged before. Standard and Best pricing is unchanged.
- 2026-07-20[all] boardingpass-wallet: now multi-file (whole family in one upload), priced 150 sats per pass, with a one-tap 'add all' link; self-expiring links and no persistent source-file library.
- 2026-07-19[all] boardingpass-wallet (recipe): convert airline boarding passes (PDF/screenshot) to Google Wallet passes with zone/gate/boarding time. Flat 150 sats.
- 2026-07[all] Text tiers refreshed after Qwen3-32B was retired upstream: Standard + translation now run GPT-OSS-120B (top scorer in our multilingual translation evals — zero catastrophic failures, fastest); Better now runs Qwen3.6 27B (highest-intelligence fast model, 131K context). Pricing unchanged (Standard 1,000 chars/sat, Better 333). Pinning: Standard id 31, Better id 1.
- 2026-07[all] /api/discover ranking upgraded: rare, specific keywords now outrank generic terms (IDF weighting), and plural/stem queries match short keywords (faxes → fax). relevance_score values are integers on a new scale — treat scores as relative ordering within one response, not absolute thresholds.
- 2026-07[MCP] Payment-lifecycle error codes (PAYMENT_NOT_FOUND / PAYMENT_PENDING / PAYMENT_ALREADY_USED) with recovery hints; check_payment_status returns a next field and readyToUse is false once a refund is queued; tools/list now carries MCP ToolAnnotations so clients can auto-approve free status polls.
- 2026-07[MCP] send_sms / place_call / ai_call destinations are validated as E.164 before any invoice is created — malformed numbers get a clear fix-it error instead of an opaque failure; formatted numbers like +1 (415) 555-0100 are accepted.
- 2026-06[L402] L402 macaroons now use a standards-compatible identifier — off-the-shelf L402 clients (e.g. lnget) pay any endpoint without custom handling.
- 2026-06[all] International (non-+1) SMS sends from the "Sats4AI" alphanumeric sender ID by default; UK (+44) destinations enabled.
- 2026-06[all] Video generation: new resolution parameter (480p / 720p / 1080p) replaces mode (still accepted as a legacy alias); price is now resolution × duration.
- 2026-06[all] Pricing parity: per-character TTS and per-minute STT now priced identically on MCP, L402, and web; underpaying a job is rejected instead of executed.
- 2026-06[MCP / L402] Rate limits: POST /api/mcp capped at 240 req/min/IP; unauthenticated L402 challenges rate-limited per IP (429 before an invoice is created).
- 2026-06[MCP] New tool await_result: stream an async job to completion over SSE in a single call instead of polling.
- 2026-06[all] SMS contract: per-segment pricing; links rejected before payment; private abuse screening after payment, with a 1-sat /appeal review.
- 2026-06[all] TTS and audiobook rates are BTC-pegged: characters-per-sat re-prices hourly against spot BTC.
- 2026-05[all] Call pricing is carrier-aware per destination — most international calls got cheaper.
- 2026-05[web] New /recover self-service page: claim a pending refund or re-download a finished audiobook with only your payment hash.
- 2026-05[all] Image generation prompt limit raised from 500 to 2,000 characters.
- 2026-04[discovery] Every catalog entry now carries endpoint_type: "primitive" | "recipe"; first recipe transcribe-translate launched (audio → transcript → translation in one payment).
- 2026-04[L402 / MCP] Agent-ergonomics upgrade: this docs page, pre-payment cost estimates, machine-readable error codes, standardized async responses, opt-in signed webhooks (callback_url), request dedup, CORS; translate accepts ISO codes and locale tags. CORRECTION (2026-09-05): request dedup was never implemented. Nothing cached a response and no route set X-Dedup. A paid request is not retry-safe -- reuse the original paymentId. See Retry Safety above.
- 2026-04[all] Refund model: prepaid invoices + LNURL-withdraw refund links replaced hold-invoice escrow — post-payment failures return a self-serve withdraw link.
- 2026-04[all] Fax launched: send and receive (optional OCR), per-page pricing, receive webhook with signing secret.
- 2026-04[discovery / web] New discovery surfaces: /api/openapi.json, /llms.txt, per-service health flags; the whole site became reachable over a Tor .onion address.
- 2026-04[all] Text-to-speech reorganized into 3 tiers — Global (602 languages), Premium, Studio (voice clone); music generation upgraded with BPM/key control and longer songs.
- 2026-03[all] epub-audiobook, AI phone call, and automated phone call launched; roadmap voting tools (list_planned_services / vote_on_service) added.
- 2026-03[L402] All endpoints renamed to the verb-noun convention (image → generate-image, sms → send-sms, …).
- 2026-03[all] March service wave: OCR, speech-to-text, send-email, HTML-to-PDF, receipt extraction, voice clone, image edit, PDF merge, and specialist image tools; translate expanded to 119 languages; image results moved from raw base64 to hosted URLs.
Tor Access
Every endpoint is available as a Tor hidden service. No clearnet required, no IP logged.
Hidden service address:
j5tfaz7s7osapdbry4d2wb5usyhtcvrm7kutonliqq7sjv2c47lsgoid.onioncurl
curl --socks5-hostname 127.0.0.1:9050 \ http://j5tfaz7s7osapdbry4d2wb5usyhtcvrm7kutonliqq7sjv2c47lsgoid.onion/api/discover
Node.js
import { SocksProxyAgent } from "socks-proxy-agent";
const agent = new SocksProxyAgent("socks5h://127.0.0.1:9050");
const res = await fetch(
"http://j5tfaz7s7osapdbry4d2wb5usyhtcvrm7kutonliqq7sjv2c47lsgoid.onion/api/l402/generate-image",
{ method: "POST", agent, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt: "a cat", model: "auto" }) }
);Tor provides end-to-end encryption — http:// is correct and secure over .onion. Tor Browser users visiting sats4ai.com see an automatic “.onion available” banner via the Onion-Location header. See the announcement post for more details.
Scam Screening
SMS, email, automated-call text or recordings, and AI-call instructions are screened after payment. AI calls also use runtime model instructions and post-call review; this is not a hard real-time speech filter.
Communication rules
No impersonation, scams, threats, harassment, credential theft, unsolicited bulk messages, or contact after an opt-out. Answering a call does not establish consent. Screening can make mistakes and cannot prove recipient consent.
We screen requests after payment. Held messages and calls are not sent or placed; payment is retained during review, not refunded immediately. Confirmed violations are not refunded. Unresolved communication holds become eligible for a refund after 24 hours; refund eligibility is processed by scheduled maintenance. Request human review at /appeal for 1 sat with your payment ID and an email address. Choose a refund or, where supported, delivery within 24 hours of the original request. An approved refund can be claimed at /recover.
SMS starts with an unverified-sender notice and a link to safety/stop instructions. Automated calls start with a spoken sender notice. AI agents disclose their automated role and client agency.
Recent message text and extracts of recording transcripts support recipient-scoped abuse checks for 24 hours. These context records are deleted hourly, so storage can last up to 25 hours. Original uploaded-call transcripts and raw held-request or appeal evidence enter cleanup after 30 days. Providers keep records under their own policies.
Independently of content, per-destination send limits apply: each phone number can only receive a few messages or calls per hour and per day, across all customers. Over-limit requests are rejected before payment.
Security
Sats4AI handles Bitcoin Lightning payments. We take security seriously.
Reporting Vulnerabilities
Do not open a public GitHub issue. Email sats4ai@gmail.com with a description, reproduction steps, and impact assessment. We acknowledge within 48 hours and provide a status update within 7 days.
In scope
- L402 authentication bypass or macaroon forgery
- Payment logic errors (double-spend, underpayment acceptance, invoice reuse)
- API endpoint vulnerabilities (injection, SSRF, IDOR)
- Information disclosure (API keys, wallet credentials)
- Denial of service against payment or API infrastructure
Full policy including safe harbor and out-of-scope items: /.well-known/security.txt | SECURITY.md