Docs — SendFax.sh
Documentation
SendFax.sh sends a real fax from an HTTP request. An agent POSTs a PDF and an E.164 number, gets back HTTP 402 Payment Required, settles per page in USDC over x402 or MPP, and polls until the fax is delivered. There are no accounts and no API keys — the payment is the credential. Humans can do the same through a drag-and-drop page paid with Stripe.
Base URL https://sendfax.sh. All responses are JSON; errors use application/problem+json (RFC 9457). Pricing is a flat $0.05 per page — see the pricing table on the home page, and a runnable end-to-end curl session in quickstart.md.
This page is the API reference. For step-by-step integration guides — paying with USDC, the MPP dialect, and working code for Next.js, macOS, iOS, React Native, the command line, and agents like Claude and Codex — see the guides.
Send a fax (agent)
POST/api/v1/faxes
A multipart/form-data body with two fields. The first request is unpaid and always returns 402.
| Field | Type | Notes |
|---|---|---|
document | file (PDF) | Unencrypted PDF, ≤ 20 MB. |
to | string (E.164) | Destination fax number, e.g. +14155550123. |
SendFax delivers to the US, Canada, Mexico, and St. Pierre & Miquelon. A number outside those countries is rejected with 422 before any payment.
The 402 challenge
The price ($0.05 × pages, in USDC) is advertised two ways at once, so any capable client can pay:
- x402 — a
PAYMENT-REQUIREDresponse header plus a JSON body with anacceptsarray (below). Pay, then retry the identical request with anX-PAYMENTheader. - MPP — a
WWW-Authenticate: Paymentresponse header describing the same charge. Pay, then retry with aPAYMENTheader.
HTTP/1.1 402 Payment Required
WWW-Authenticate: Payment
PAYMENT-REQUIRED: <x402 requirements, encoded>
x-payment-deposit-mode: stripe-deposit
x-payment-deposit-network: base
x-payment-deposit-address: 0x157a57423869bf2666d3998b1319c2263ce852bb
x-payment-deposit-token: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
x-payment-deposit-amount: 0.050000
Content-Type: application/json
{
"x402Version": 1,
"error": "payment required",
"accepts": [
{
"scheme": "exact",
"network": "base",
"asset": "USDC",
"maxAmountRequired": "50000",
"resource": "https://sendfax.sh/api/v1/faxes",
"description": "Fax transmission — $0.05/page"
}
]
}maxAmountRequired is USDC base units (6 decimals) × page count — 50000 = $0.05 for a one-page fax.
Settle by direct transfer (Stripe deposit mode)
Settlement is handled by Stripe — no facilitator, no Coinbase CDP, no signed authorization. Alongside the challenge above, the 402 carries a fresh per-request deposit address in the x-payment-deposit-* headers. Send exactly x-payment-deposit-amount USDC to x-payment-deposit-address on x-payment-deposit-network as a plain ERC-20 transfer; Stripe watches the chain, detects your transfer, and captures the payment. This is a direct transfer to the advertised address, not the signed-authorization x402 flow many wallets automate.
Then send the identical request again (same document + to, no payment header). The server polls the PaymentIntent and returns 202 Accepted the moment your transfer is detected; until then it returns the same 402 (same address), so poll every few seconds:
{ "id": "fax_9f3c2a", "statusUrl": "https://sendfax.sh/f/fax_9f3c2a" }The statusUrl is a shareable status page; poll the JSON endpoint below until the fax reaches a terminal state.
Check status
GET/api/v1/faxes/{id}
Free and unauthenticated — the fax id is an unguessable capability. The same endpoint backs the human status page at /f/{id}. Returns the public view of the fax:
{
"id": "fax_9f3c2a",
"status": "delivered",
"pageCount": 1,
"toMasked": "•••• 0123",
"failureReason": null,
"failureCategory": null,
"createdAt": 1752096000000,
"terminalAt": 1752096010000
}| Field | Type | Notes |
|---|---|---|
id | string | Fax id; also the status-page slug. |
status | enum | See the lifecycle below. |
pageCount | integer | Pages in the document. |
toMasked | string | Destination redacted to the last 4 digits. |
failureReason | string | null | Raw carrier token when status is failed. A support code, not a sentence — do not show it to an end user. |
failureCategory | string | null | vendor | destination | delivery | timeout | unknown. Always set when status is failed. Branch on this. |
createdAt | integer | Epoch milliseconds. |
terminalAt | integer | null | Epoch ms at terminal state; null until then. |
Status lifecycle
A fax advances through these values. An agent send begins at queued (payment already settled); awaiting_payment and paid belong to the human Stripe flow.
awaiting_payment → paid → queued → media_processed → sending → deliveredTerminal states: delivered, failed (see failureCategory), expired (an unpaid upload past its TTL).
Send as a human
The /human page is the no-code path: drop a PDF, enter the destination number, and the page counts pages and prices the send in the browser before you pay. Two endpoints back it (not part of the agent flow):
| Endpoint | Body | Returns |
|---|---|---|
POST/api/v1/uploads | multipart document, to | { id, pageCount, amountCents } |
POST/api/v1/checkout | { id } | { url } — the Stripe Checkout URL |
Checkout offers Stripe Link, card, and Pay-with-Crypto (USDC). Human pricing floors at Stripe’s minimum charge: max($0.50, $0.05 × pages). On completion the browser is redirected to /f/{id}, the same live status page agents get.
Errors
Errors are application/problem+json (RFC 9457):
{
"type": "about:blank",
"title": "Invalid destination",
"status": 400,
"detail": "Field `to` must be an E.164 fax number, e.g. +14155550123."
}| Status | When |
|---|---|
402 | Payment required, or the retried payment was invalid. |
400 | Missing/invalid `document` or `to`, or a PDF that is not a PDF, is encrypted, or has zero pages. |
422 | `to` is a valid E.164 number but outside the countries SendFax delivers to (US, Canada, Mexico, St. Pierre & Miquelon). |
413 | The PDF exceeds the 20 MB limit. |
404 | No fax (or pending upload) with that id. |
A fax that reaches the carrier but cannot be delivered is not an HTTP error: it lands in the failed status. Read failureCategory to know what happened — destination (busy, no answer, not a fax line), delivery, timeout, unknown, or vendor, which means our carrier account rather than anything about your request. failureReason carries the raw carrier token for support; its wording can name a "destination" when the fault is ours, so never show it to an end user as the explanation. An unpaid human upload becomes expired once its payment window closes.
If your fax fails or is not delivered within an hour, card and USDC-via-Stripe payments are refunded in full automatically — the USDC returns to your sending wallet. MPP-native payments, a raw on-chain transfer with no Stripe charge to reverse, need a manual refund: email support@sendfax.sh with your fax id.
Stuck? Email support@sendfax.sh.
Privacy & guarantees
Real-time fax — we never store your document. The PDF is streamed straight to the carrier (Telnyx POST /v2/media), sent by reference, and deleted from the carrier after transmission.
Our store keeps only process metadata: ids, statuses, page counts, and timestamps. The destination number is masked to its last 4 digits for display and nulled entirely at terminal state. Telemetry never records document content, filenames, payer details, or destination numbers — it is allowlist-filtered in code.
Delivered over a HIPAA-compliant real-time T.38 carrier (nothing stored on either end), certified SOC 2 Type II, ISO 27001, PCI DSS, and GDPR.
Machine-readable index
Discovery surfaces for agents and crawlers:
- /llms.txtllms.txt index — summary + doc list
- /llms-full.txtevery doc inlined into one file
- /openapi.jsonOpenAPI 3.1 — every endpoint & the 402
- /quickstart.mdsend a fax in four steps
- /SKILL.mdportable, paste-ready agent skill
- /auth.mdno auth — payment is the credential
- /.well-known/agent-card.jsonA2A agent card
- /.well-known/mcp/server-card.jsonMCP server card
- /.well-known/ai-plugin.jsonai-plugin manifest
- /.well-known/api-catalogRFC 9727 API linkset
Agent skills (npx skills): install the fax skill with npx skills add DanielSinclair/sendfax.sh --skill sendfax -g, or the TypeScript SDK skill with npx skills add DanielSinclair/sendfax --skill sendfax-sdk -g; the raw skill is also served at /SKILL.md.
Content negotiation: send Accept: text/markdown to /, /human, or this page to get the markdown twin. RFC 8288 Link headers cross-reference the HTML and markdown forms.
MCP server
A remote, authless MCP server fronts the same paid API — payment (x402/MPP, USDC) is still the credential, so there are no accounts or keys.
| Endpoint | https://mcp.sendfax.sh/mcp — Streamable HTTP, with an SSE fallback at /sse. |
| Tools | get_payment_instructions (teaches the x402/MPP flow), send_fax (returns the 402 challenge, then a 202 receipt once paid), get_fax_status. |
| Resources | sendfax://docs/{llms-full,skill,openapi}, fetched live. |
| Auth | none — no token required. OAuth-only clients (e.g. Perplexity) are auto-approved via OAuth discovery + Dynamic Client Registration; the token carries no identity and tools stay payment-gated. |
Connect from your agent CLI:
claude mcp add --transport http sendfax https://mcp.sendfax.sh/mcp[mcp_servers.sendfax]
url = "https://mcp.sendfax.sh/mcp"/mcp add sendfax https://mcp.sendfax.sh/mcp --global