# How to send a fax with Claude

Claude Code, Claude Desktop, and claude.ai can all reach SendFax through the
remote MCP server at `https://mcp.sendfax.sh/mcp`. Claude Code can additionally
install the fax skill, which teaches it the HTTP flow directly -- worth having,
because of the payment gap described below.

- Guide index: [../README.md](/guides/index.md) | protocol: [../usdc.md](/guides/usdc.md)
- Docs: <https://sendfax.sh/docs> | contract: <https://sendfax.sh/openapi.json>

---

## The flow

```
  you: "fax this PDF to +1 415 555 0123"
        |
  Claude calls send_fax (MCP)  --or--  runs curl (skill + shell)
        |
        +--> HTTP 402: the fax is NOT sent yet
        |      MCP path:  challenge headers only  (see "The payment gap")
        |      curl path: challenge + x-payment-deposit-* address
        |
  payment happens OUTSIDE Claude:
        +-- you or Claude send exact USDC on Base to the deposit address
        +-- or you pay by card via Stripe Checkout
        |
  Claude retries the identical send  --> 202 { id, statusUrl }
        |
  Claude calls get_fax_status(id) --> queued -> sending -> delivered
```

## Which payment path am I in?

Claude has no wallet. Nothing in Claude Code, Claude Desktop, or claude.ai holds
USDC or signs transactions. So the question is what Claude can *reach*:

| Situation | Path |
|---|---|
| Claude Code with shell access and a funded key you control | Claude runs the transfer itself (a `viem` one-liner, or `cast send`). Fully autonomous. |
| Claude Code, no key | Claude prints the deposit address and amount; you pay from any wallet; Claude retries. |
| claude.ai / Desktop (MCP only) | Claude cannot obtain the deposit address -- see below. Use the Stripe link, or move to Claude Code. |

---

## Connect the MCP server

### Claude Code

```sh
# Streamable HTTP (recommended)
claude mcp add --transport http sendfax https://mcp.sendfax.sh/mcp

# Available to every project, not just this one
claude mcp add --transport http --scope user sendfax https://mcp.sendfax.sh/mcp

# Shared with your team via .mcp.json at the repo root
claude mcp add --transport http --scope project sendfax https://mcp.sendfax.sh/mcp
```

Verify and manage:

```sh
claude mcp list
claude mcp get sendfax
claude mcp remove sendfax
# In-session, to see connection state and tools:
/mcp
```

Scopes: `local` (default -- you, this project only), `project` (shared via
`.mcp.json`), `user` (you, every project). `local` and `user` are stored in
`~/.claude.json`; `project` writes `.mcp.json` in the repo, and teammates are
prompted to approve it before it loads.

If you hand-write `.mcp.json`, the `type` field is **required** for a remote
server -- an entry with a `url` and no `type` is read as a stdio server and
fails:

```json
{
  "mcpServers": {
    "sendfax": { "type": "http", "url": "https://mcp.sendfax.sh/mcp" }
  }
}
```

SSE is available as a fallback if Streamable HTTP is blocked on your network:

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

*Verified against Claude Code 2.1.220 and
<https://code.claude.com/docs/en/mcp>.*

### claude.ai and Claude Desktop

Both use the same hosted connector infrastructure:

1. **Settings > Connectors** (the help centre labels this **Customize >
   Connectors**; the UI naming has shifted, so check both).
2. **Add custom connector**.
3. URL: `https://mcp.sendfax.sh/mcp`
4. **Add**. No client id, no secret -- the server is authless.

On Team and Enterprise plans an owner adds it under **Admin settings >
Connectors** first, then members connect it from their own Settings. Free
accounts are limited to one custom connector. Enable it per conversation from
the **+** button.

`claude_desktop_config.json` is **not** the right mechanism here -- it is for
local stdio servers, and servers configured there are not available in claude.ai.

*Sources: <https://claude.com/docs/connectors/custom/remote-mcp> and
<https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp>.
Authless servers are explicitly supported: the auth reference lists type `none`
-- "No authentication (authless server)" -- as Supported.*

One operational note: Claude connects from Anthropic's cloud, not from your
device, on every surface including Desktop and mobile.

---

## Install the skill (Claude Code)

The MCP server gives Claude tools. The skill gives it the *procedure* -- when to
fax, what the 402 means, how to settle it over plain HTTP. With shell access,
the skill alone is enough; you do not need the MCP server at all.

The zero-dependency install is a file drop, which is the officially documented
layout (`~/.claude/skills/<name>/SKILL.md`):

```sh
mkdir -p ~/.claude/skills/sendfax
curl -fsSL https://sendfax.sh/SKILL.md -o ~/.claude/skills/sendfax/SKILL.md
```

Claude Code picks up new skill directories live; if you had no
`~/.claude/skills` directory before, restart the session once.

There is also a third-party installer that automates the same file drop:

```sh
# `npx skills` is vercel-labs/skills -- MIT, not an Anthropic product.
npx skills add DanielSinclair/sendfax.sh --skill sendfax -g

# The TypeScript SDK skill, for Claude writing code against the `sendfax` package:
npx skills add DanielSinclair/sendfax --skill sendfax-sdk -g
```

It writes into the same `~/.claude/skills/` path documented above. Use whichever
you prefer; the `curl` version has no dependencies and no third party.

*Skill layout: <https://code.claude.com/docs/en/skills>. `npx skills`:
<https://github.com/vercel-labs/skills>.*

---

## Ask for a fax

```
Fax ~/Documents/signed-lease.pdf to +14155550123.
```

```
I need to send this to my doctor's office at +1 415 555 0123 -- it's a
3-page PDF at ./referral.pdf. What will it cost and can you send it?
```

Claude should tell you the price before spending anything: $0.05 per page on the
wallet rail, so a three-page fax is $0.15. If it does not, ask.

A good standing instruction, in `CLAUDE.md` or as a project memory:

```markdown
## Faxing
Use the sendfax tools for fax requests. Always confirm the destination number
and the page count with me before sending. Never spend more than $1 per fax
without asking.
```

---

<a name="the-payment-gap"></a>

## The payment gap (read this before you rely on MCP)

**The MCP `send_fax` tool does not surface the deposit address.** When the API
answers `402`, the tool forwards only three headers -- `www-authenticate`,
`payment-required`, and `content-type` (`mcp/tools.ts:207-210`). The
`x-payment-deposit-*` headers, which carry the per-request address that actually
settles the payment (`lib/payments/gate.ts:219-234`), are dropped.

So over MCP alone, Claude receives a well-formed payment challenge it cannot
act on. `get_payment_instructions` explains the flow correctly in prose, but the
address it tells you to pay is minted per request and only appears in those
dropped headers.

Three ways through it:

**1. Let Claude use the HTTP API directly (Claude Code).** The headers are all
there over plain HTTP. With the skill installed and shell access, this is what
Claude will do anyway:

```sh
curl -sS -D headers.txt -o body.json -w '%{http_code}\n' \
  -F document=@referral.pdf -F to=+14155550123 \
  https://sendfax.sh/api/v1/faxes
grep -i '^x-payment-deposit' headers.txt
```

Claude reads the address and the exact amount, you (or it) send the USDC, and it
re-runs the identical curl until the status code is `202`. Full walkthrough:
[../curl.md](/guides/curl.md).

**2. Pay by card instead.** No wallet anywhere:

```sh
curl -sS -F document=@referral.pdf -F to=+14155550123 \
  https://sendfax.sh/api/v1/uploads
# -> { "id": "...", "pageCount": 3, "amountCents": 50 }

curl -sS -H 'content-type: application/json' -d '{"id":"<id>"}' \
  https://sendfax.sh/api/v1/checkout
# -> { "url": "https://checkout.stripe.com/..." }
```

Claude prints the URL, you pay in a browser, and the fax sends. Note the price:
`max($0.50, $0.05 x pages)` on the card rail, because Stripe will not charge
under 50 cents (`lib/domain/pricing.ts:16, 23`). A one-page fax is $0.05 by
wallet and $0.50 by card.

**3. Send a human to <https://sendfax.sh/human>.** Drag, drop, pay, done.

---

## How payment actually works

There is no account and no API key -- payment is the credential. The full
mechanics are in [../usdc.md](/guides/usdc.md); the short form:

1. The 402 carries a fresh deposit address, the exact amount in USDC base units,
   and the network (`base`).
2. You send that **exact** amount of USDC on Base to that address as a plain
   ERC-20 transfer. No signature, no approval, no facilitator.
3. You retry the **identical** request. The server polls Stripe, sees the
   transfer, and returns `202`.

Two rules that bite agents specifically:

- **Exact amount.** `x-payment-deposit-amount-micros` is an integer string
  (`"50000"` = $0.05). Use it directly; never parse `"0.050000"` through a
  float. A mismatched amount cannot be auto-matched
  (`lib/payments/deposit.ts:16-18`).
- **Same request, ~15 minutes.** Identical retries reuse the same PaymentIntent
  for 15 minutes and are not a second charge
  (`lib/payments/deposit.ts:50, 94-108`). Change the PDF or the number and you
  mint a new address. Go quiet past the window and your transfer is stranded.

---

## Troubleshooting

**`/mcp` shows sendfax as failed or disconnected.** Check the URL is
`https://mcp.sendfax.sh/mcp` with the `/mcp` path. Try
`claude mcp remove sendfax` and re-add with `--transport http` explicitly. If
your network blocks streaming responses, try the SSE endpoint.

**Project-scoped server shows "Pending approval".** `.mcp.json` servers require
interactive approval -- run `claude` in that directory and approve it.

**A browser window opens asking to authorize.** Expected on clients that insist
on OAuth. The server is authless, but it advertises OAuth discovery and Dynamic
Client Registration and auto-approves instantly, so a client that refuses to
connect without OAuth still works. The token carries no identity and every tool
call is still payment-gated.

**Claude says "payment required" and stops.** That is the design -- the fax has
not been sent. See [the payment gap](#the-payment-gap) above. Over MCP alone
Claude cannot get the deposit address; give it shell access, or pay by card.

**"Unsupported destination" (422).** SendFax delivers to the United States,
Canada, Mexico, and Saint Pierre & Miquelon (`lib/domain/fax.ts:70-78`). This is
checked before any payment, so nothing was charged.

**"Invalid PDF" (422).** Encrypted or password-protected PDFs are rejected. Bank
and government exports often are. Remove the protection and retry.

**413.** The PDF is over 20 MB.

**The fax says `failed`.** Check `failureReason` -- busy, no answer, not a fax
line. If the failure is destination-related and you paid through Stripe or the
deposit rail, the refund is automatic and returns USDC to your sending wallet
(`lib/payments/refund.ts:1-30`).

**Tool output looks truncated.** Claude Code caps MCP tool results (configurable
via `MAX_MCP_OUTPUT_TOKENS`); claude.ai caps around 150,000 characters. Fax
responses are small, so this should not bite -- but a 402 challenge body plus
headers is the largest thing this server returns.

---

## Try it without spending anything

Run SendFax locally in mock mode and point Claude at it. The 402 is genuine; the
bypass header is the only thing that is not.

```sh
bun run dev      # http://localhost:3001, FAX_MODE=mock
curl -sS -H 'x-mock-payment: 1' \
  -F document=@test.pdf -F to=+14155550123 \
  http://localhost:3001/api/v1/faxes
```

---

## Cross-references

- [../README.md](/guides/index.md) -- the chooser and the decision matrix
- [../curl.md](/guides/curl.md) -- the curl reference implementation Claude will use
- [../typescript.md](/guides/typescript.md) -- the Node/Bun version that pays from a local key
- [../usdc.md](/guides/usdc.md) -- how the USDC settlement actually works
- [codex.md](/guides/agents/codex.md) | [hermes.md](/guides/agents/hermes.md) | [openclaw.md](/guides/agents/openclaw.md)
- <https://sendfax.sh/SKILL.md> | <https://sendfax.sh/docs>
