# How to send a fax with Codex

OpenAI's Codex CLI speaks MCP over Streamable HTTP natively, so connecting
SendFax is one command. Codex has no skills system; its standing-instructions
mechanism is `AGENTS.md`, and this guide shows what to put there.

- 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 ./invoice.pdf to +14155550123"
        |
  Codex calls send_fax (MCP)  --or--  runs curl in its shell
        |
        +--> HTTP 402: the fax is NOT sent yet
        |      MCP path:  challenge headers only  (see "The payment gap")
        |      shell path: challenge + x-payment-deposit-* address
        |
  payment happens OUTSIDE Codex:
        +-- exact USDC on Base to the deposit address
        +-- or Stripe Checkout in a browser
        |
  Codex retries the identical send --> 202 { id, statusUrl }
        |
  Codex calls get_fax_status(id) --> queued -> sending -> delivered
```

## Which payment path am I in?

Codex holds no wallet and signs nothing. What it *can* do is run commands, which
makes the shell the interesting path:

| Situation | Path |
|---|---|
| Codex with shell access and a funded key you control | Codex reads the deposit address from the 402 and sends the transfer itself. Autonomous. |
| Codex, no key | Codex prints the address and amount; you pay from any wallet; Codex retries until `202`. |
| MCP tools only, no shell | Codex cannot obtain the deposit address -- see below. Use the Stripe link. |

---

## Connect the MCP server

One command:

```sh
codex mcp add sendfax --url https://mcp.sendfax.sh/mcp
```

That writes to `~/.codex/config.toml`:

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

**No `mcp-remote` stdio bridge is needed.** Streamable HTTP is first-class in
current Codex; `url` is a native field alongside the stdio `command`/`args`
form.

Manage it:

```sh
codex mcp list
codex mcp get sendfax
codex mcp login sendfax
codex mcp logout sendfax
codex mcp remove sendfax
```

Or skip the config file entirely for a one-off run:

```sh
codex -c 'mcp_servers.sendfax.url="https://mcp.sendfax.sh/mcp"'
```

Optional fields on a remote server, if you need them:

```toml
[mcp_servers.sendfax]
url = "https://mcp.sendfax.sh/mcp"
# enabled = true
# startup_timeout_sec = 10      # default 10
# tool_timeout_sec = 60         # default 60
```

*Verified empirically on codex-cli 0.145.0 -- `codex mcp add sendfax --url
https://mcp.sendfax.sh/mcp` produced exactly the TOML above and `codex mcp list`
reported the server enabled. Docs: <https://learn.chatgpt.com/docs/extend/mcp>
(the former `developers.openai.com/codex/mcp` now redirects there).*

### Version note

Older Codex builds required an `experimental_use_rmcp_client = true` opt-in for
Streamable HTTP. Current builds do not, and the field is no longer in the docs.
The exact version where `--url` landed is not documented, so if
`codex mcp add --url` is not recognised, run `codex --version` and update.

### SSE

SendFax serves an SSE fallback at `https://mcp.sendfax.sh/sse`, but **Codex does
not document an SSE transport option** -- `url` is described as Streamable HTTP
and there is no transport selector. Assume the SSE endpoint does not apply here.

---

## Expect a browser OAuth window

When you run `codex mcp add`, a browser window will open, complete an OAuth
handshake against `https://mcp.sendfax.sh/authorize`, and report
`Successfully logged in.`

Nothing is wrong. SendFax is authless -- `/mcp` never requires a token -- but it
advertises OAuth discovery plus Dynamic Client Registration and auto-approves
instantly, so that clients which refuse to connect without OAuth still work.
Codex detects that advertisement and takes the offer. The token carries no
identity, there is no account behind it, and every tool call is still
payment-gated.

If the browser cannot open (a headless box, an SSH session), the connection
still works without the OAuth step for tool calls, since no token is required.

---

## Standing instructions with AGENTS.md

Codex has no skills mechanism. Its equivalent is `AGENTS.md`, plain Markdown
that Codex reads once per session.

- **Global**: `~/.codex/AGENTS.md` (or `AGENTS.override.md`, which wins if
  present). `CODEX_HOME` moves the directory.
- **Per project**: `AGENTS.md` at the repo root and in any directory between it
  and your working directory -- Codex walks down and concatenates.

Drop this in:

```markdown
## Sending faxes

Use the `sendfax` MCP server for fax requests. SendFax delivers only to the US,
Canada, Mexico, and Saint Pierre & Miquelon. Pricing is $0.05 per page on the
USDC rail; the Stripe card rail floors at $0.50.

Before sending:
- Confirm the destination number and the page count with me.
- Tell me the price. Never spend more than $1 on a fax without asking.

If the API returns 402, the fax has NOT been sent. Payment is a plain USDC
transfer on Base to the per-request address in the `x-payment-deposit-address`
response header -- which the MCP tool does not forward, so read it by calling
the HTTP API directly with curl. Then retry the identical request until it
returns 202.
```

The instruction chain is built once when the session starts, so restart Codex
after editing. Codex stops reading once the combined instruction files hit 32
KiB.

*Sources: <https://developers.openai.com/codex/guides/agents-md> and
<https://github.com/openai/codex/blob/main/docs/agents_md.md>.*

---

## Ask for a fax

```
Fax ./invoice.pdf to +14155550123.
```

```
Send the signed contract at ~/Documents/lease.pdf to my landlord's fax,
+1 415 555 0123. Tell me what it costs first.
```

With the AGENTS.md block above, Codex should quote the price before spending.

---

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

## The payment gap

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

Over MCP alone, Codex gets a payment challenge it cannot act on. Fortunately,
Codex's core competence is running commands, and the headers are all present
over plain HTTP:

```sh
# Codex can run this itself.
curl -sS -D headers.txt -o body.json -w '%{http_code}\n' \
  -F document=@invoice.pdf -F to=+14155550123 \
  https://sendfax.sh/api/v1/faxes
# 402

grep -i '^x-payment-deposit' headers.txt
# x-payment-deposit-address: 0x157a57423869bf2666d3998b1319c2263ce852bb
# x-payment-deposit-amount: 0.050000
# x-payment-deposit-amount-micros: 50000
# x-payment-deposit-network: base
# x-payment-deposit-token: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
```

Send exactly `50000` base units of USDC on Base to that address, then:

```sh
# Byte-identical request, no payment header. The retry IS the claim.
until curl -sS -o body.json -w '%{http_code}' \
        -F document=@invoice.pdf -F to=+14155550123 \
        https://sendfax.sh/api/v1/faxes | grep -q 202
do sleep 5; done
jq -r .statusUrl body.json
```

A full 20-line script with a QR code, plus a Node/Bun version that pays from a
local key with viem, is in [../curl.md](/guides/curl.md) and [../typescript.md](/guides/typescript.md).
Pointing Codex at those files
is the fastest way to make it autonomous.

### Or pay by card

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

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

Codex prints the URL, you pay, the fax sends. The card rail is
`max($0.50, $0.05 x pages)` because Stripe will not charge under 50 cents
(`lib/domain/pricing.ts:16, 23`).

---

## How payment actually works

No account, no API key -- payment is the credential. Full mechanics 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. Send that **exact** amount of USDC on Base to that address as a plain ERC-20
   transfer. No signature, no approval, no facilitator, no third party.
3. Retry the **identical** request until `202`.

Two rules that bite agents:

- **Exact amount.** Use `x-payment-deposit-amount-micros` (`"50000"`), not the
  decimal display value. A mismatch cannot be auto-matched
  (`lib/payments/deposit.ts:16-18`).
- **Same request, ~15 minutes.** Identical retries reuse the same PaymentIntent
  and are not a second charge (`lib/payments/deposit.ts:50, 94-108`). Change the
  document or the number and you mint a new address.

If you let Codex hold a key, give it a spending ceiling in code, not in the
prompt -- check the network, the token, and the amount against what you expect
before signing. [../typescript.md](/guides/typescript.md) shows the check.

---

## Troubleshooting

**`codex mcp add --url` is not recognised.** Your Codex predates native remote
MCP. Run `codex --version` and update.

**`codex mcp list` shows the server but no tools appear.** Restart the session;
the tool list is fetched at startup. Check `startup_timeout_sec` if your network
is slow.

**A browser opened and asked me to log in.** Expected -- see above. The server
is authless; Codex is acting on its OAuth advertisement.

**Codex says "payment required" and stops.** The fax has not been sent. See
[the payment gap](#the-payment-gap).

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

**"Invalid PDF" (422).** Encrypted or password-protected PDFs are rejected.

**413.** Over the 20 MB limit.

**The fax says `failed`.** Read `failureReason`. Destination-related failures on
a Stripe-settled payment -- which includes every deposit-rail payment -- are
refunded automatically to the sending wallet (`lib/payments/refund.ts:1-30`).

**AGENTS.md changes are not taking effect.** The instruction chain is built once
per session. Restart Codex.

---

## Try it without spending anything

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

The 402 you get without the header is genuine -- built by the real challenge
machinery -- so you can develop the whole flow against it.

---

## Cross-references

- [../README.md](/guides/index.md) -- the chooser and the decision matrix
- [../curl.md](/guides/curl.md) -- the curl implementation Codex can run
- [../typescript.md](/guides/typescript.md) -- the Node/Bun version that pays from a local key
- [../usdc.md](/guides/usdc.md) -- how USDC settlement works
- [claude.md](/guides/agents/claude.md) | [hermes.md](/guides/agents/hermes.md) | [openclaw.md](/guides/agents/openclaw.md)
- <https://sendfax.sh/quickstart.md> | <https://sendfax.sh/docs>
