Developer Docs
L402 + MCP + WebLN. One scrollable page.
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 | None | 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 retention | Not retained | Logged to your account |
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— semantic searchGET /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": "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/models/image \
-H "Content-Type: application/json" \
-d '{"prompt":"a cat","model":"auto"}'
# Response: 402 Payment Required
# X-Route-Model: nano-banana-2 ← chosen model id
# X-Route-Category: Image
# X-Error-Code: L402_AUTO_ROUTED
# IMPORTANT: on the paid retry, send the *routed* model id, NOT "auto":
curl -X POST https://sats4ai.com/api/models/image \
-H "Authorization: L402 ..." \
-d '{"prompt":"a cat","model":"nano-banana-2"}'
# Otherwise the macaroon's price-per-model caveat may reject the request.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
Identical POST bodies from the same IP within 30 seconds are returned from cache. Useful when an agent retries due to a network blip and you don't want to be charged twice.
# Identical POST within 30s returns the cached response (no new payment)
# Response headers:
# X-Dedup: hit ← cached
# X-Dedup: miss ← fresh
# Bypass the cache with X-No-Cache: true
curl -X POST https://sats4ai.com/api/models/image \
-H "X-No-Cache: true" -d '{...}'
# Note: dedup is best-effort, single-instance, 30s TTL.
# For strong idempotency use a unique paymentId per job.CORS / Browser Use
All payment + routing headers are explicitly listed in Access-Control-Expose-Headers so browser fetch() can read them.
# Headers exposed to browser fetch():
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
Access-Control-Expose-Headers: WWW-Authenticate, Payment-Receipt,
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:
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=... — semantic search
- /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).
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: ~10 sats/min US/CA, ~30 intl, ~80 rare. 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+ | 100 chars/sat | Good | Rare/underserved languages (Yoruba, Marathi, Twi, Cebuano). Widest coverage by far. |
| Inworld Premium | ~9 | 50 chars/sat | Best (ELO #1) | English and major languages where quality matters most. Highest fidelity voice. |
| Minimax Studio | ~9 | 10 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 route to the cheapest model that meets quality bar, or pick explicitly:
| Model | Maker | Price (sats) | When to Choose |
|---|---|---|---|
| Qwen3 32B | Alibaba | ~0.001 sats/char | Cheapest. 119 languages, ultra-fast. Best for translation and multilingual tasks. |
| GPT-OSS 120B | OpenAI | ~0.003 sats/char | Mid-tier. Ultra-fast, 131K context. Code gen, quick answers, everyday tasks. |
| Kimi K2.5 | Moonshot AI | ~0.01 sats/char | Smartest. 1T params, 262K context, vision, thinking mode. 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. |
| 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.
- Identical POST bodies within 30 seconds return the cached response (see Request Dedup).
- Result download URLs are temporary (~1 hour) — fetch your output promptly.
- Outbound communication (SMS, email, calls) is scam-screened after payment; flagged content is held and not auto-refunded (see Scam Screening).
| Service | Hard limits | Won't work / gotchas |
|---|---|---|
| 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 | 126 user characters max (a compliance footer uses the rest of the segment). 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 4-15 s. Resolutions: 480p, 720p, 1080p only. 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 up to 6 min. | is_instrumental=true ignores any lyrics you send. |
| 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. |
| 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. |
| 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 (min 1 sat). | 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. | Too-short or poor-quality samples fail after payment — there is no pre-payment quality check. |
| 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
Request dedup caches identical POST bodies for 30 seconds. If your agent is retrying with a different body (e.g., re-serialized JSON with different key order), dedup won't match. Use the same exact request body for retries, or pass a unique paymentId for strong idempotency. See Request Dedup.
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-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 in message bodies rejected, short service-disclosure footer appended; abuse screening runs before an invoice is created, 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.
- 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
Outbound communication services — SMS, AI Call, Automated Call, and email sending — are screened for scam content after payment, before anything is delivered.
What gets flagged
- Impersonation of a company, bank, exchange, or government
- Threats, extortion, or harassment of the recipient
- Solicitation of credentials, passwords, seed phrases, or OTP/2FA codes
- Payment-pressure scams (fake fees, fines, customs charges, prizes)
Flagged content is held: it is not sent, and the payment is not automatically refunded. If you believe your message was flagged by mistake, request a human review at /appeal (costs 1 sat, anti-spam) with your payment ID and an email address — we reply with the outcome. Approved refunds are claimed at /recover.
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