Agents
PostalForm Agents Guide
PostalForm lets agents place a real print-and-mail order on behalf of their owner.
Published Feb 15, 2026 • Updated Feb 19, 2026
Machine payments overview (x402)
Machine payments use an HTTP 402 handshake:
- Optional (recommended): your agent sends
POST /api/machine/orders/validateto get a quote and validation hints before payment. - Your agent sends
POST /api/machine/orderswith the same order payload (no payment header). - PostalForm responds with
402and aPAYMENT-REQUIREDheader describing how to pay. - Your agent pays and retries the same request with a
PAYMENT-SIGNATUREheader. - PostalForm settles the payment and returns
202with aPAYMENT-RESPONSEheader.
Endpoints
- Validate and quote (no payment side effects):
POST https://postalform.com/api/machine/orders/validate - Create and pay:
POST https://postalform.com/api/machine/orders - Poll status:
GET https://postalform.com/api/machine/orders/:id
Networks and assets
- Live deployments typically settle with USDC on Base.
- Test environments may use testnets (for example Base Sepolia).
Address strategy (manual vs autocomplete)
For each party (sender + recipient), choose exactly one strategy:
- Manual address: best when you already have
line1,city,state, andzip. - Autocomplete (Loqate ID): best when you have incomplete address info and want the agent to resolve the full address autonomously.
Rules:
- US addresses only.
- Do not send both a Loqate id and a manual address for the same party. Pick one and set
*_address_typeaccordingly. - There is no automatic fallback from Loqate verification to manual later in the pipeline. If you want manual, send manual.
Option A: Manual address input (full address known)
If you already have the full address fields, you can skip autocomplete entirely and provide a manual address.
Manual addresses are US-only and must include:
line1(street address)line2(optional unit/suite)citystate(two-letter US state code, e.g.CA)zip(ZIP or ZIP+4)
In the machine order payload:
- Set
sender_address_type: "Manual"and includesender_address_manual. - Set
recipient_address_type: "Manual"and includerecipient_address_manual.
Example manual address object:
{
"line1": "123 Main St",
"line2": "Apt 4",
"city": "Springfield",
"state": "IL",
"zip": "62701"
}
Find sender and recipient addresses (Loqate IDs)
If you don't have a complete address (or want a convenience/autofill flow), use Loqate "Address" IDs
for sender_address_id and recipient_address_id.
Address suggestions can include two types:
type="Address": a deliverable mailing address you can use directly.type="Container": a building/complex (for example an apartment building). You must drill down to anAddress(suite/unit) before placing an order.
Recommended agent flow (via MCP tool):
- Call
postalform.search_addresseswithquery(min 3 chars). - If you see
type="Container"results, ask for the unit/suite and call again withcontainer=<id>and a refinedquery. - Select a suggestion where
type="Address". Use:
id->*_address_idtype->*_address_type(must be"Address"for Loqate-based verification)${text}, ${description}->*_address_text
Example search:
{
"name": "postalform.search_addresses",
"arguments": {
"target": "recipient",
"query": "1600 Amphitheatre Pkwy, Mountain View"
}
}
Example drill-down (Container -> Address):
{
"name": "postalform.search_addresses",
"arguments": {
"target": "recipient",
"query": "Apt 4",
"container": "US|LP|Pz0_Qj4_bGJg|123456|99_ENG"
}
}
Note:
- If you send a
Containerid into the order payload, Loqate verification will fail later and the order will not mail. - If you already have a complete address (line1/city/state/zip), you can use manual address input instead of Loqate IDs.
Draft an order payload
Required fields:
request_id(UUID, used for idempotency)buyer_namebuyer_email(required; used as Stripereceipt_emailso receipts are delivered to the buyer)pdf(recommended canonical format:{ "upload_token": "..." }; also accepts{ download_url, file_id }, adata:application/pdf;base64,...URL, or an allowlisted https URL)- Sender:
sender_nameand either:- Loqate:
sender_address_id,sender_address_type="Address",sender_address_text, or - Manual:
sender_address_type="Manual",sender_address_manual
- Loqate:
- Recipient:
recipient_nameand either:- Loqate:
recipient_address_id,recipient_address_type="Address",recipient_address_text, or - Manual:
recipient_address_type="Manual",recipient_address_manual
- Loqate:
Common options:
double_sided(defaulttrue)color(defaultfalse)mail_class(standard,priority,express)certified(defaultfalse)
You can mix-and-match:
- Manual sender + Loqate recipient
- Loqate sender + manual recipient
Example request:
{
"request_id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
"buyer_name": "Agent Owner",
"buyer_email": "[email protected]",
"pdf": {
"download_url": "https://example.oaiusercontent.com/file.pdf",
"file_id": "file_abc123"
},
"file_name": "letter.pdf",
"sender_name": "Sender Example",
"sender_address_id": "US|LP|Pz0_Qj4_bGJg|16074807|13_ENG",
"sender_address_type": "Address",
"sender_address_text": "123 Sender St, Springfield, IL 62701",
"recipient_name": "Recipient Example",
"recipient_address_id": "US|LP|Pz0_Qj4_bGJg|199825276|99_ENG",
"recipient_address_type": "Address",
"recipient_address_text": "456 Recipient Ave, Springfield, IL 62701",
"double_sided": true,
"color": false,
"mail_class": "standard",
"certified": false
}
Example request (manual addresses):
{
"request_id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
"buyer_name": "Agent Owner",
"buyer_email": "[email protected]",
"pdf": {
"download_url": "https://example.oaiusercontent.com/file.pdf",
"file_id": "file_abc123"
},
"file_name": "letter.pdf",
"sender_name": "Sender Example",
"sender_address_type": "Manual",
"sender_address_manual": {
"line1": "123 Sender St",
"line2": "Apt 4",
"city": "Springfield",
"state": "IL",
"zip": "62701"
},
"recipient_name": "Recipient Example",
"recipient_address_type": "Manual",
"recipient_address_manual": {
"line1": "456 Recipient Ave",
"line2": "",
"city": "Springfield",
"state": "IL",
"zip": "62701"
},
"double_sided": true,
"color": false,
"mail_class": "standard",
"certified": false
}
Troubleshooting (common API responses)
422(invalid_request): payload validation failed. The response includeserrors[]withpath,message, and machine hints (hint,fix_examples) when available.- Manual addresses:
statemust be a valid two-letter US code andzipmust be ZIP or ZIP+4. - Loqate addresses:
*_address_typemust be"Address"(not"Container"), and ids must be US Loqate ids. - Don’t send
*_address_manualunless*_address_type="Manual".
- Manual addresses:
422(missing_buyer_email):buyer_emailwas missing when attempting machine payment; this email is required to set Stripereceipt_email.409(request_id_mismatch): the samerequest_idwas reused with a different payload.402(payment_required): pay and retry withPAYMENT-SIGNATURE(or usepurl).429(rate_limited): slow down address searches and retry later.
Pay autonomously (recommended: purl)
Stripe maintains an agent-facing wallet CLI called purl that can fully automate the 402 flow: it reads PAYMENT-REQUIRED, creates a payment, and retries the request with PAYMENT-SIGNATURE.
Install the CLI (requires Rust):
git clone https://github.com/stripe/purl
cd purl
cargo install --path cli
Example:
export POSTALFORM_URL="https://postalform.com/api/machine/orders"
export EVM_PRIVATE_KEY="0x..."
purl \
--private-key "$EVM_PRIVATE_KEY" \
--network eip155:8453 \
--max-amount 5000000 \
--output-format json \
-X POST \
--json '{"request_id":"8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b","buyer_name":"Agent Owner","buyer_email":"[email protected]","pdf":{"download_url":"https://example.oaiusercontent.com/file.pdf","file_id":"file_abc123"},"sender_name":"Sender Example","sender_address_id":"US|LP|Pz0_Qj4_bGJg|16074807|13_ENG","sender_address_type":"Address","sender_address_text":"123 Sender St, Springfield, IL 62701","recipient_name":"Recipient Example","recipient_address_id":"US|LP|Pz0_Qj4_bGJg|199825276|99_ENG","recipient_address_type":"Address","recipient_address_text":"456 Recipient Ave, Springfield, IL 62701","double_sided":true,"color":false,"mail_class":"standard","certified":false}' \
"$POSTALFORM_URL"
Notes:
--max-amountis a safety cap in atomic units (USDC has 6 decimals).purlwill refuse to pay if the server asks for more than this cap.--networkmust match the network returned inPAYMENT-REQUIRED(for Base mainnet useeip155:8453; for Base Sepolia useeip155:84532).- Your agent can retry the same
request_idsafely. PostalForm rejects the samerequest_idif the payload changes.
Pay autonomously (JavaScript)
If you prefer to implement the x402 handshake directly, use the official x402 packages:
@x402/corefor the HTTP protocol wrapper@x402/evmfor EVM signing (USDC)
High-level flow:
fetch(POST /api/machine/orders)-> expect402.- Decode
PAYMENT-REQUIRED. - Create a payment payload.
- Retry request with
PAYMENT-SIGNATURE. - Read
PAYMENT-RESPONSEand persist the settlement tx hash.
Track fulfillment
After a successful payment, poll:
curl -sS "https://postalform.com/api/machine/orders/<request_id>"
The response includes is_paid, current_step, and x402 settlement metadata.
It also includes order_complete_url with the following behavior:
- Before payment is settled:
"Order URL will be returned once payment is settled." - After payment is settled (
is_paid=true):https://postalform.com/order/complete?order_id=<request_id>
Tools for agents
If you need help finding address IDs or handling PDF uploads, the PostalForm MCP server can help:
- MCP endpoint:
https://postalform.com/mcp - Tools:
postalform.search_addresses(get*_address_idvalues)postalform.create_pdf_upload(get anupload_tokenif you cannot pass a file download URL)postalform.get_order_status(poll status for non-machine draft/checkout flows)
If your agent also uses workflow tools (postalform.list_forms + postalform.get_form_schema), treat checkout_flow in schema responses as optional informational metadata for PostalForm web routing. It does not change MCP call order.
Refunds
Stripe supports refunds for crypto pay-ins in live mode. If you need to reverse a machine payment, you can refund the Stripe PaymentIntent and have USDC sent back to the payer wallet.
Contact [email protected] if you need help enabling machine payments or wiring up a facilitator for your deployment.