Rate-shop every carrier and buy the label in two paid calls — the label PDF comes back base64 in the response body.
POST /rates prices a parcel across every carrier service available for the route and tells you which is cheapest and which is fastest. POST /label buys one of those rates and hands you the label — a real PDF, base64-encoded, in the 200 body — along with the tracking number. There is no download link to poll and no webhook to wait on.
Shipping APIs gate on an account, a business verification and often a monthly minimum — which is fine for a warehouse and absurd for an agent that needs to mail one thing. x402 prices the two operations that matter at $0.003 and $0.02 and settles them in USDC per call, so an agent with a wallet can rate-shop and ship without anyone opening an account for it. The settlement receipt in each response is the audit trail linking the spend to the label it bought.
git clone https://github.com/nirholas/x402-shipping
cd x402-shipping
npm install
npm run dev # http://localhost:4023No configuration needed — the server ships with the suite's receive addresses and a deterministic fixture rater that quotes seven real carrier services and issues a valid PDF label, so the demo works before you have any carrier account. Set PAY_TO_ADDRESS / SOLANA_PAY_TO_ADDRESS to receive funds yourself.
# 1. Unpaid → 402 listing both rails
curl -i -X POST http://localhost:4023/rates \
-H 'Content-Type: application/json' \
-d '{"from":{"street1":"215 Clayton St","city":"San Francisco","state":"CA","zip":"94117","country":"US"},"to":{"street1":"1600 Pennsylvania Ave NW","city":"Washington","state":"DC","zip":"20500","country":"US"},"parcel":{"length":10,"width":8,"height":4,"weight":32}}'
# 2. Paid, via any x402 client
npm run client| Route | Price | What you get back |
|---|---|---|
POST /rates |
$0.003 | the full carrier rate table with prices, transit days, and cheapest / fastest flags |
POST /label |
$0.02 | the label as base64 PDF plus tracking number, tracking URL, carrier, service and the amount charged |
GET /health |
free | Liveness, active data source, configured rails |
GET /.well-known/x402 |
free | Machine-readable discovery manifest |
Every paid route returns the purchased artifact in the 200 body. Nothing is deferred to a webhook or a later fetch.
Full reference: docs/api.md · openapi.json · skill.md
agent x402-shipping facilitator
│ GET /rates │ │
├──────────────────────────────▶│ │
│ 402 + accepts[base, solana] │ │
◀──────────────────────────────┤ │
│ sign USDC authorization │ │
│ retry + X-PAYMENT │ │
├──────────────────────────────▶│ verify + settle │
│ ├───────────────────────────────▶│
│ 200 + artifact │ │
│ + X-PAYMENT-RESPONSE │ ◀── tx hash / signature ──────┤
◀──────────────────────────────┤ │
Pay in USDC on Base or Solana — your client picks the rail. The 402 challenge always lists both:
| Rail | Network | Asset | payTo |
|---|---|---|---|
| EVM | base-sepolia (base via NETWORK=base) |
USDC | 0x40252CFDF8B20Ed757D61ff157719F33Ec332402 |
| Solana | solana |
USDC (SPL) | WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW |
The Solana rail's extra.feePayer is a public facilitator sponsor account that pays the SOL network fee, so a buyer needs only USDC — no SOL for gas. Wallets that sign serialized transactions (Phantom, most agent SDKs) can use the built-in helpers at POST /api/x402-checkout?action=prepare|encode.
Two providers are supported; either is free to try.
| Env var | Unlocks |
|---|---|
SHIPPO_API_TOKEN |
Live Shippo rating + label purchase. Test tokens start shippo_test_. |
EASYPOST_API_KEY |
Live EasyPost rating + label purchase. Test keys start EZTK. |
SHIPPO_BASE_URL / EASYPOST_BASE_URL |
Point at a different host — rarely needed. |
Shippo wins if both are set. Test mode is the default and the safe path: rates are real rate-table lookups and labels are real PDFs, but nothing ships and no carrier account is charged. The testMode field in every response tells you which mode produced it — a production key flips it to false and the startup banner says LIVE — labels cost real money.
Without either key, everything still works. The fixture rater prices seven carrier services on real weight, volume and zone arithmetic, and POST /label returns a valid one-page PDF stamped TEST LABEL - NOT VALID FOR SHIPPING. Every response is marked "source": "fixture", in the JSON, in the OpenAPI schema and in skill.md.
One difference worth knowing: a live rateId references a shipment the provider already stored, so POST /label needs only the rateId. A fixture rateId references nothing, so fixture mode asks you to re-send from/to/parcel — the service will not invent an address it was never given.
For a browser-facing checkout, drop in @three-ws/x402-payment-modal — it reads the 402 challenge and drives the whole connect → sign → settle flow for both rails (Phantom on Solana, any injected wallet on EVM), with SIWX re-entry so a returning buyer skips the wallet prompt, and client-side spending caps that stop an agent or a mis-click from over-spending. Reference it from npm or the CDN; it is a separate proprietary package and is never vendored here.
skill.md— the agent-facing contract: every endpoint, its price, its response schema, and how to pay. Point your agent at this file.GET /.well-known/x402— machine-readable discovery (manifest). Lists both rails per resource.examples/mcp-tool.md— expose this service as an MCP tool for Claude in about 30 lines.examples/agent-client.ts— a complete paid call withx402-fetch, printing the artifact and the decoded settlement receipt.- Discovery/listing — indexable by x402scan.com, the x402 Bazaar, and agentic.market. Deploy, then submit your public base URL; all three read
/.well-known/x402.
https://nirholas.github.io/x402-shipping/ — tutorial · API reference · for agents
Apache-2.0 — see LICENSE.
Part of the x402 Suite.