How to send a fax with Codex
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 | protocol: ../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 -> deliveredWhich 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:
codex mcp add sendfax --url https://mcp.sendfax.sh/mcpThat writes to ~/.codex/config.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:
codex mcp list
codex mcp get sendfax
codex mcp login sendfax
codex mcp logout sendfax
codex mcp remove sendfaxOr skip the config file entirely for a one-off run:
codex -c 'mcp_servers.sendfax.url="https://mcp.sendfax.sh/mcp"'Optional fields on a remote server, if you need them:
[mcp_servers.sendfax]
url = "https://mcp.sendfax.sh/mcp"
# enabled = true
# startup_timeout_sec = 10 # default 10
# tool_timeout_sec = 60 # default 60Verified 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(orAGENTS.override.md, which wins if present).CODEX_HOMEmoves the directory. - Per project:
AGENTS.mdat the repo root and in any directory between it and your working directory -- Codex walks down and concatenates.
Drop this in:
## 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.
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:
# 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: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913Send exactly 50000 base units of USDC on Base to that address, then:
# 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.jsonA 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 and ../typescript.md. Pointing Codex at those files is the fastest way to make it autonomous.
Or pay by card
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. The short form:
- The 402 carries a fresh deposit address, the exact amount in USDC base units, and the network (
base). - 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.
- 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 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.
"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
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/faxesThe 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 -- the chooser and the decision matrix
- ../curl.md -- the curl implementation Codex can run
- ../typescript.md -- the Node/Bun version that pays from a local key
- ../usdc.md -- how USDC settlement works
- claude.md | hermes.md | openclaw.md
- https://sendfax.sh/quickstart.md | https://sendfax.sh/docs