MPP
PostalForm MPP endpoint guide
Use this file when an MPP registry reviewer or MPP-capable agent needs only PostalForm's MPP endpoints and payment instructions.
Published Jun 17, 2026 • Updated Jun 19, 2026
MPP endpoints
Document mail, workflow forms, postcards, and bulk mail:
| Purpose | Method and path |
|---|---|
| Discovery probe challenge | GET /api/machine/mpp/orders |
| Validate and quote without payment side effects | POST /api/machine/mpp/orders/validate |
| Create unpaid order, receive challenge, and retry after payment | POST /api/machine/mpp/orders |
| Poll payment and fulfillment status | GET /api/machine/mpp/orders/{id} |
Flower letters:
| Purpose | Method and path |
|---|---|
| Validate product, ZIP, delivery date, and final quote | POST /api/machine/mpp/flower-letters/validate |
| Create unpaid flower order, receive challenge, and retry after payment | POST /api/machine/mpp/flower-letters |
| Poll payment and florist submission status | GET /api/machine/mpp/flower-letters/{id} |
Flower catalog prep endpoints are not MPP-paid, but agents need them before creating a flower-letter MPP order:
| Purpose | Method and path |
|---|---|
| List flower categories | GET /api/flowers/categories |
| List flower products | GET /api/flowers/products?category=bs&count=18&start=1&sorttype=pa |
| List delivery dates for a ZIP | GET /api/flowers/delivery-dates?zipcode=32503 |
Payment flow
- Validate first. Use the relevant
/validateendpoint and show the quote to the owner. - Create the unpaid order with the same JSON body and no
Authorizationheader. - PostalForm returns HTTP
402with one or moreWWW-Authenticate: Payment ...headers. - Parse the returned challenges and choose exactly one supported method.
- Pay the challenge with an MPP-capable client.
- Retry the exact same create request with the exact same JSON body and
request_id, addingAuthorization: Payment <serialized-mpp-credential>. - Read the
Payment-Receiptheader on success. - Poll the returned status endpoint until
payment_statusispaid.
Do not create a second order after the 402. Reuse the same request_id and byte-equivalent JSON body. If the body changes for an existing request_id, PostalForm returns 409 request_id_mismatch.
Payment methods
Use one of these methods for this MPP verification guide:
tempo: settle the Tempo challenge with an MPP-capable Tempo client such asmppx.stripe_spt: mint or obtain a buyer-approved Stripe Shared Payment Token (spt_...) and serialize it into the Stripe MPP credential.
Document mail quick start
Use POST /api/machine/mpp/orders/validate, then POST /api/machine/mpp/orders.
Document-mail MPP addresses follow the same country configuration as PostalForm's checkout address picker. Manual countryCode values and Loqate ID country prefixes must be one of US, CA, AT, BE, CH, DE, FR, GB, IN, LU, or NL. Omitted manual-address countries default to US; unsupported or invalid codes are rejected. Bulk CSV rows use the same list through an optional country, country_code, or countrycode column.
curl -sS https://postalform.com/api/machine/mpp/orders/validate \
-H 'Content-Type: application/json' \
--json '{
"request_id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
"buyer_name": "Agent Owner",
"buyer_email": "owner@example.com",
"letter": {
"title": "MPP verification letter",
"body": "Hello,\n\nThis is a PostalForm MPP verification letter.\n\nSincerely,\nAgent Owner",
"format": "text",
"signature": "Agent Owner"
},
"sender_name": "Agent Owner",
"sender_address_type": "Manual",
"sender_address_manual": {
"line1": "123 Sender St",
"city": "Springfield",
"state": "IL",
"zip": "62701",
"countryCode": "US"
},
"recipient_name": "Recipient Example",
"recipient_address_type": "Manual",
"recipient_address_manual": {
"line1": "456 Recipient Ave",
"city": "Springfield",
"state": "IL",
"zip": "62701",
"countryCode": "US"
},
"double_sided": true,
"color": false,
"mail_class": "standard",
"certified": false
}'
Create the unpaid order using the same body:
curl -i https://postalform.com/api/machine/mpp/orders \
-H 'Content-Type: application/json' \
--json @mpp-mail-body.json
On 402, choose one returned MPP challenge and retry:
curl -i https://postalform.com/api/machine/mpp/orders \
-H 'Content-Type: application/json' \
-H 'Authorization: Payment <serialized-mpp-credential>' \
--json @mpp-mail-body.json
Poll:
curl -sS https://postalform.com/api/machine/mpp/orders/8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b
See the mail-only version: https://postalform.com/mpp-mail.md
Flower-letter quick start
Use the flower catalog endpoints first, then validate and create with the MPP flower endpoints.
curl -sS https://postalform.com/api/machine/mpp/flower-letters/validate \
-H 'Content-Type: application/json' \
--json '{
"request_id": "0bc151ff-31a9-47a6-a0a4-2853cd75ff2a",
"buyer_name": "Agent Owner",
"buyer_email": "owner@example.com",
"note": "For your desk, your day, and the smile I hope this brings.",
"zipcode": "32503",
"product_code": "F1-509",
"delivery_date": "2026-07-03",
"customer": {
"name": "Agent Owner",
"phone": "8505550100",
"address1": "123 Sender St",
"city": "Pensacola",
"state": "FL",
"zipcode": "32503",
"country": "US"
},
"recipient": {
"name": "Recipient Example",
"phone": "8505550199",
"address1": "456 Recipient Ave",
"city": "Pensacola",
"state": "FL",
"zipcode": "32503",
"country": "US"
},
"allow_substitutions": true
}'
Create and retry after payment using the same POST /api/machine/mpp/flower-letters body. Poll GET /api/machine/mpp/flower-letters/{id}.
See the flower-only version: https://postalform.com/mpp-flowers.md
Stripe SPT credential shape
For Stripe SPT, the retry header must contain the serialized MPP credential, not the bare spt_... token.
import { Challenge, Credential } from 'mppx'
const unpaid = await fetch('https://postalform.com/api/machine/mpp/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
})
if (unpaid.status !== 402) throw new Error(`Expected 402, got ${unpaid.status}`)
const challenges = Challenge.fromResponseList(unpaid)
const stripeChallenge = challenges.find((challenge) => challenge.method === 'stripe')
if (!stripeChallenge) throw new Error('No stripe challenge returned')
const spt = await mintSharedPaymentGrantedToken({
amount: stripeChallenge.request.amount,
currency: stripeChallenge.request.currency,
networkId: stripeChallenge.request.methodDetails?.networkId,
externalId: body.request_id,
})
const authorization = Credential.serialize(
Credential.from({
challenge: stripeChallenge,
payload: { spt },
})
)
const paid = await fetch('https://postalform.com/api/machine/mpp/orders', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': authorization,
},
body: JSON.stringify(body),
})
The token must match the challenge amount, currency, seller network id, and validity window.
Tempo client shape
If your verifier uses mppx, it can parse the WWW-Authenticate: Payment ... challenge, settle it, and retry:
mppx \
https://postalform.com/api/machine/mpp/orders \
--account my-tempo-wallet \
--json-body "$(cat mpp-mail-body.json)" \
--include \
--verbose
Status values
Document mail status responses include:
awaiting_payment: unpaid draft exists and can be retried withAuthorization: Payment ....settled_pending_webhook: payment was accepted and PostalForm is waiting for final payment finalization.paid: payment is finalized and fulfillment has been dispatched or queued.
Flower-letter status responses include:
awaiting_payment: unpaid flower order exists.settled_pending_submission: payment was accepted and florist submission is queued.paid: payment was accepted and submission processing has started.
Errors agents should handle
400or422 invalid_request: payload validation failed. Read theerrorsarray and fix the request before paying.402 payment_required: normal MPP challenge response. Pay one challenge and retry the same create request.409 request_id_mismatch: the samerequest_idwas reused with a different payload. Use the original body or a fresh UUID.422 missing_buyer_email:buyer_emailis required for machine payments and Stripe receipt delivery.429 rate_limited: slow down unpaid order creation and retry later.500 payment_error: payment verification failed or the payment provider returned an error.
Machine-readable references
- MPP mail guide:
https://postalform.com/mpp-mail.md - MPP flower guide:
https://postalform.com/mpp-flowers.md - OpenAPI:
https://postalform.com/openapi.json - Broader agent guide:
https://postalform.com/agents.md - Agent skill:
https://postalform.com/skill.md