<!--
  Markdown twin of /docs (app/docs/page.tsx). Served as the `.md` alternate and
  by Accept: text/markdown / ?format=md negotiation on /docs.
  KEEP IN SYNC with app/docs/page.tsx -- every endpoint, field, status value, and
  price here must match that page. Pure ASCII punctuation (site convention).
-->

# SendFax.sh -- API 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 (https://x402.org) or MPP (https://mpp.dev), 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 (https://sendfax.sh/), and a runnable
end-to-end curl session in the quickstart (https://sendfax.sh/quickstart.md).

## 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`.|

### The 402 challenge

The price ($0.05 x pages, in USDC) is advertised two ways at once, so any
capable client can pay:

- **x402** -- a `PAYMENT-REQUIRED` response header plus a JSON body with an
  `accepts` array (below). Pay, then retry the identical request with an
  `X-PAYMENT` header.
- **MPP** -- a `WWW-Authenticate: Payment` response header describing the same
  charge. Pay, then retry with a `PAYMENT` header.

```
HTTP/1.1 402 Payment Required
WWW-Authenticate: Payment
PAYMENT-REQUIRED: <x402 requirements, encoded>
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) x page count -- `50000` =
$0.05 for a one-page fax.

### Settle and retry

Hand the challenge to any x402- or MPP-capable client (or wallet) to produce a
payment for the advertised amount, then send the same request again with the
payment header -- `X-PAYMENT` for x402, `PAYMENT` for MPP. On success you get
`202 Accepted`:

```
{ "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,
  "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 | Set when status is failed.                  |
| `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 -> delivered
```

Terminal states: `delivered`, `failed` (see `failureReason`), `expired` (an
unpaid upload past its TTL).

## Send as a human

The /human page (https://sendfax.sh/human) 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 }` -- 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 x 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. |
| `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 with a human-readable `failureReason` (busy, no
answer, not a fax line). An unpaid human upload becomes `expired` once its
payment window closes.

Stuck? Email support@sendfax.sh.

## Privacy and 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:

- https://sendfax.sh/llms.txt -- llms.txt index (summary + doc list)
- https://sendfax.sh/llms-full.txt -- every doc inlined into one file
- https://sendfax.sh/openapi.json -- OpenAPI 3.1 (every endpoint and the 402)
- https://sendfax.sh/quickstart.md -- send a fax in four steps
- https://sendfax.sh/SKILL.md -- portable, paste-ready agent skill
- https://sendfax.sh/auth.md -- no auth (payment is the credential)
- https://sendfax.sh/.well-known/agent-card.json -- A2A agent card
- https://sendfax.sh/.well-known/mcp/server-card.json -- MCP server card
- https://sendfax.sh/.well-known/ai-plugin.json -- ai-plugin manifest
- https://sendfax.sh/.well-known/api-catalog -- RFC 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 `/docs`
(or append `?format=md`, or just add `.md` to the path) to get the markdown
twin. RFC 8288 `Link` headers cross-reference the HTML and markdown forms.

## MCP server

A remote, authless MCP server (https://modelcontextprotocol.io) 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.

Connect from Claude Code:

```
claude mcp add --transport http sendfax https://mcp.sendfax.sh/mcp
```

Connect from Codex (`~/.codex/config.toml`):

```
[mcp_servers.sendfax]
url = "https://mcp.sendfax.sh/mcp"
```

Connect from Pi:

```
/mcp add sendfax https://mcp.sendfax.sh/mcp --global
```
