Skip to main content
Physical Mail prints and posts a letter or a 4x6 postcard to a US address. You upload the artwork, OneShot renders a proof and quotes a price, a person approves that exact piece at that exact price, and the piece is sent.
Physical Mail is built and is not yet set up in production. Until it is, every request except reading or cancelling an existing order returns 503 physical_mail_not_configured. The roadmap tracks its status.

How it works

1

Upload the artwork

POST /assets with the file as the raw body. A letter is one PDF. A postcard is a front and a back.
2

Preview

POST /preview with the two addresses and the artwork. OneShot checks both addresses, counts the pages, and starts rendering a proof. Nothing is charged.
3

Wait for the quote

Poll GET /quotes/{id} until status is ready. The quote then carries the proof and total_usdc. A quote expires 30 minutes after it is created.
4

Approve

A person looks at the proof, the addresses, and the price. POST /approve records that decision against the quote’s input_hash and total_usdc, so a changed piece or price cannot be sent under an old approval.
5

Send

Save an idempotency key first, then POST /send. The order is paid from credits, or by x402 if there are none.
6

Follow the order

GET /orders/{id} returns the order and its delivery events. If the response to /send was lost, GET /orders/recover?key= finds the order by its idempotency key.

Authentication

Every route needs a wallet session: X-Agent-ID and a signed x-agent-proof, which the SDK adds. A failed proof returns 401 invalid_agent_proof. The limit is 60 requests a minute per agent.

Endpoints

All paths are under https://win.oneshotagent.com/v1/tools/physical-mail.

Address

string
required
Recipient or sender name, up to 100 characters.
string
required
Street address, up to 200 characters.
string
Apartment, suite, or unit.
string
required
City.
string
required
Two-letter state code, for example OH.
string
required
ZIP code, five digits or ZIP+4.
string
US. No other value is accepted.

Artwork

One of:
  • A letter: { "kind": "letter", "file": "<asset_id>", "color": false, "double_sided": false }. The file must be a PDF of 1 to 60 pages.
  • A postcard: { "kind": "postcard", "front": "<asset_id>", "back": "<asset_id>" }. Each side can be a PDF, PNG, or JPEG.

Approve

string
required
The quote to approve.
string
required
The quote’s input_hash. It ties the approval to the exact addresses and artwork.
string
required
The quote’s total_usdc. It ties the approval to the exact price.
boolean
required
Must be true.

Send

string
required
A key you have saved before sending, 1 to 255 characters. It never expires. Sending again with the same key and body returns the same order with Idempotent-Replay: true.
string
Refuse the send if the quote is higher than this.
string
Releases a send that an action policy held. This is separate from the quote’s approval_id.
string
required
The approved quote.
string
required
The approval_id returned by /approve.

Response

Quote

string
The quote’s ID.
string
rendering or ready.
string
A hash of the normalized addresses and artwork. Pass it to /approve.
object
url of the proof (null while rendering) and thumbnails.
string | null
The price, null until the quote is ready. It is the print and postage cost plus a service fee.
string | null
The service fee inside the total.
string
When the quote stops being sendable.

Order

string
The order’s ID.
string
pending, submitting, accepted, canceled, failed, or needs_reconciliation.
string
Where the piece is. See delivery states.
string
The state of the payment for this order.
string
What the order cost.
object | null
The signed receipt, null until execution is final.
array
Delivery events, each with event_id, event_type, and occurred_at.
string
The time after which the piece can no longer be cancelled.
string | null
When the order was refunded, if it was.
boolean
Always false. Delivery is a postal observation and never evidence that anyone read the piece.

SDK usage

TypeScript
The Python client has the same methods on client.physical_mail: upload_artwork, validate_address, preview, get_quote, approve, send, get_order, recover, and cancel, each with an a-prefixed async twin.

Delivery states

fulfillment_status moves forward through accepted, mailed, in_transit, in_local_area, processed_for_delivery, and delivered. It can also be returned_to_sender, canceled, or render_failed. A late event never moves the state backwards. processed_for_delivery is not delivered.

Refunds

A confirmed cancellation, or a failure before the piece is produced, refunds the full amount including the service fee as credits, once. Returned mail is not refunded.

Limits

  • US addresses only.
  • First-class letters on US letter paper, and 4x6 postcards.
  • A letter is a PDF of 1 to 60 pages.
  • 20 MB per uploaded file.
  • No HTML templates, international mail, checks, or custom envelopes.

Errors