Send real postal mail from code: upload a PDF, quote it, create the order, and track it to the mailbox with signed webhooks. Free simulated test mode, prepaid credits for live mail, and Stripe Projects provisioning.
Every PostalForm Projects workspace has isolated test and live environments. Test keys (pf_test_…) run the entire flow — uploads, quotes, orders, status timelines, and webhooks — against simulated fulfillment, free of charge. Live keys (pf_live_…) create real mail and spend prepaid credits only after the mailpiece is accepted for fulfillment.
Dashboard: https://projects.postalform.com
API: https://projects.postalform.com/api/v1
Authenticate every API call with a bearer key. Fulfillment routing is automatic: you choose the mailpiece options (speed, color, proof mail, destination) and PostalForm selects an eligible production path.
Authorization: Bearer pf_test_...
Authorization: Bearer pf_live_...
| Capability | Test mode | Live mode |
|---|---|---|
| Fulfillment | Simulated end to end | Real print-and-mail fulfillment |
| Cost | Free (simulated test credits) | Prepaid credits, captured on acceptance |
| Webhooks | Delivered and signed normally | Delivered and signed normally |
| Tracking | Simulated status progression | Carrier tracking numbers where available |
One command provisions a free workspace — no signup flow — and Stripe syncs the env vars into your secret store.
stripe projects add postalform/mail
Create a workspace at projects.postalform.com/signup, then copy your key from the API Keys page.
POSTALFORM_TEST_API_KEY=pf_test_...
POSTALFORM_WORKSPACE_ID=pfws_...
PUT the PDF bytes to the returned URL, and mark the document complete.Idempotency-Key header.export POSTALFORM_API_KEY="pf_test_..."
export POSTALFORM_API_BASE="https://projects.postalform.com/api/v1"
# 1. Upload a PDF
curl -s "$POSTALFORM_API_BASE/documents/upload-intent" \
-H "Authorization: Bearer $POSTALFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content_type":"application/pdf","byte_size":123456,"page_count":1}'
# → returns document_id + upload_url
curl -s -X PUT "<upload_url>" \
-H "Content-Type: application/pdf" \
--data-binary @letter.pdf
curl -s -X POST "$POSTALFORM_API_BASE/documents/<document_id>/complete" \
-H "Authorization: Bearer $POSTALFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"page_count":1}'
# 2. Quote it
curl -s "$POSTALFORM_API_BASE/letters/quotes" \
-H "Authorization: Bearer $POSTALFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"document_id":"doc_..."}'
# → returns quote_id + price_cents
# 3. Send it
curl -s "$POSTALFORM_API_BASE/letters" \
-H "Authorization: Bearer $POSTALFORM_API_KEY" \
-H "Idempotency-Key: first-letter-2026-07-01" \
-H "Content-Type: application/json" \
-d '{"quote_id":"quote_...","recipient":{"name":"Ada Lovelace","line1":"1 Analytical Way","city":"San Francisco","state":"CA","postal_code":"94105"}}'
# 4. Watch it move
curl -s "$POSTALFORM_API_BASE/letters/<order_id>" \
-H "Authorization: Bearer $POSTALFORM_API_KEY"
Prefer zero code first? The dashboard Send page runs this whole flow from a file picker. Use a pf_live_... key only when you intend to send real mail.
POST /api/v1/documents/upload-intent
PUT {upload_url}
POST /api/v1/documents/{document_id}/complete
POST /api/v1/letters/quotes
POST /api/v1/letters
GET /api/v1/letters/{order_id}
GET /api/v1/letters/{order_id}/document.pdf
Create an upload intent for a PDF, PUT the bytes to the returned upload URL, then mark the document complete. page_count is optional but recommended because it makes quotes deterministic before the file is inspected.
POST /api/v1/documents/upload-intent
{
"content_type": "application/pdf",
"byte_size": 123456,
"page_count": 4
}
POST /api/v1/documents/{document_id}/complete
{
"page_count": 4
}
Upload URLs expire — finish the PUT and the complete call promptly after creating the intent. A validated document can back multiple quotes.
Quotes calculate the customer price and lock in the mail options for a later order. PostalForm automatically selects an eligible fulfillment path; API clients choose the mailpiece options, not the underlying production network.
POST /api/v1/letters/quotes
{
"document_id": "doc_...",
"mail_class": "usps_first_class",
"color": false,
"double_sided": true,
"certified": false,
"certified_return_receipt": false,
"signature_required": false,
"destination_country_code": "US",
"origin_country_code": "US"
}
mail_class accepts standard/usps_first_class, priority/usps_priority, and express/usps_express. Priority and Express cannot be combined with certified or registered proof mail.
color controls black-and-white versus color printing. double_sided controls duplex printing when supported. Billing includes an inserted address page for letter mail.
Pricing is separate from the one-off PostalForm.com workflow: standard letters start with a $1.80 base fee plus $0.20 per billable black-and-white page or $0.40 per billable color page. Proof-mail add-ons are $8.00 for U.S. Certified Mail, $32.00 for Canada Post Registered Mail, and $3.00 for Electronic Return Receipt where eligible. The quote is always the final price — estimate combinations on the pricing calculator.
Set certified=true for proof-mail services on eligible standard letters. U.S. destinations request USPS Certified Mail. Canadian destinations request Canada Post Registered Mail. Belgium, Switzerland, and France destinations request the available registered-mail option for that destination.
certified_return_receipt=true applies only to eligible U.S. Certified Mail. For Canada Post Registered Mail, the return-receipt flag is ignored because the registered service already includes the available delivery proof.
signature_required=true applies to eligible U.S. Priority and Express letters. Postcards do not support certified, registered, return-receipt, or signature-required options.
Letter quotes accept destination_country_code and origin_country_code; both default to US. Supported first-party destination countries are US, CA, AT, BE, CH, DE, FR, GB, IN, LU, and NL.
Fast international mail is available only for destinations with a distinct fast-mail product. If a requested option is unavailable for the destination, the quote request returns a validation error instead of silently changing the requested service.
For international letters, include a sender address. Some destinations require a sender country that is compatible with the selected mailpiece options.
Postcards use /api/v1/postcards/quotes and /api/v1/postcards. The uploaded PDF must already be a composed two-page postcard artwork file: page 1 is the front and page 2 is the back.
POST /api/v1/postcards/quotes
{
"document_id": "doc_...",
"postcard_size": "4x6",
"destination_country_code": "US",
"origin_country_code": "US"
}
postcard_size accepts 4x6, 6x9, or 11x6. Postcards are quoted as first-class, color, double-sided mail and do not support proof-mail add-ons.
Create an order from a quote with an Idempotency-Key header. Reusing the same key with the same quote returns the original order with idempotent_replay=true, so network retries never create duplicate physical mail.
POST /api/v1/letters
Idempotency-Key: unique-order-key
{
"quote_id": "quote_...",
"recipient": { "name": "...", "line1": "...", "city": "...", "postal_code": "...", "countryCode": "US" },
"sender": { "name": "...", "line1": "...", "city": "...", "postal_code": "...", "countryCode": "US" },
"metadata": { "customer_id": "..." }
}
recipient is required. sender is strongly recommended for all live mail and required for some destinations and mailpiece options. metadata is stored on the order and returned in reads and webhooks.
Order reads return the customer-facing mailpiece state, pricing, funds state, metadata, timeline, customer webhook events, and any tracking number that has been received.
GET /api/v1/letters/{order_id}
{
"id": "ord_123",
"object": "letter",
"mailpiece_type": "letter",
"status": "in_transit",
"mode": "live",
"price_cents": 220,
"currency": "usd",
"funds_status": "captured",
"billing_rail": "prepaid_credits",
"tracking_number": "9405500000000000000001",
"tracking_status": "in_transit",
"metadata": { "customer_id": "..." },
"timeline": [],
"webhook_events": []
}
Status values include queued, document_preparing, document_prepared, submitted, accepted, in_transit, delivered, returned, submission_pending, failed, and canceled.
submission_pending is a safety marker: the fulfillment job entered a window where PostalForm will not blindly retry, because a blind retry could create duplicate physical mail. If a later status follows it, it was transient. If it remains the current status, the order is being reconciled before anything else is mailed.
Use /api/v1/webhook-endpoints (or the dashboard) to register HTTPS endpoints. Customer webhook events are emitted for fulfillment status changes with postalform.{letter|postcard}.{status} event names: accepted, in_transit, delivered, returned, failed, and canceled.
Signing secrets are endpoint-scoped, start with whsec_, and are shown once when the endpoint is created or rotated — list calls never expose them. One endpoint can receive both test and live events.
PostalForm-Signature: t=1777592400,v1=<hmac_sha256_hex>
{
"id": "evt_123",
"type": "postalform.letter.in_transit",
"data": {
"object": {
"id": "ord_123",
"object": "letter",
"status": "in_transit",
"mode": "live",
"price_cents": 220,
"currency": "usd",
"funds_status": "captured",
"billing_rail": "prepaid_credits",
"metadata": {}
}
},
"mailpiece": {
"status": "in_transit",
"tracking_number": "9405500000000000000001",
"tracking_status": "In Transit"
}
}
The signed payload is {timestamp}.{raw_json_body}, HMAC-SHA256 with your endpoint secret — the same shape as Stripe-style webhooks. Verify before trusting any delivery:
import { createHmac, timingSafeEqual } from 'node:crypto'
export function verifyPostalFormWebhook(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((part) => part.trim().split('=')))
const timestamp = Number(parts.t)
if (!Number.isFinite(timestamp) || Math.abs(Math.floor(Date.now() / 1000) - timestamp) > 300)
return false
const expected = createHmac('sha256', secret).update(timestamp + '.' + rawBody).digest('hex')
const received = parts.v1 ?? ''
return (
received.length === expected.length &&
timingSafeEqual(Buffer.from(received), Buffer.from(expected))
)
}
Respond with a 2xx quickly. Failed deliveries retry up to six times with increasing backoff (about 1 minute, 5 minutes, 30 minutes, 2 hours, 12 hours, then 24 hours). Replay any event on demand with POST /api/v1/webhook-events/{event_id}/replay, and inspect every delivery attempt from the dashboard.
Live mail spends prepaid credits: funds are reserved when an order is created and captured when fulfillment accepts the mailpiece. Top-ups are the only card charge that happens before mail is sent.
GET /api/v1/credits/balance
POST /api/v1/credits/checkout-session
GET /api/v1/credits/payment-methods
POST /api/v1/credits/payment-methods/setup-session
GET /api/v1/credits/auto-refill
POST /api/v1/credits/auto-refill
GET /api/v1/credits/ledger
POST /api/v1/credits/checkout-session.POST /api/v1/api-keys/rotate with {"mode":"live"}.Live keys stay disabled until the workspace is live-enabled through billing setup, so a leaked test integration can never mail anything real.
Errors return a machine-readable code, with a 4xx status for request problems and 5xx for PostalForm-side failures.
{ "error": "idempotency_key_required" }
| Code | Status | Meaning |
|---|---|---|
unauthorized | 401 | Missing or invalid bearer key. |
forbidden | 403 | The key cannot act on this resource (for example, live action on a test-only workspace). |
not_found | 404 | Unknown route or a resource that does not belong to this workspace. |
idempotency_key_required | 400 | Order creation without an Idempotency-Key header. |
rate_limited | 429 | Too many requests; the response includes retry_after_seconds. |
Each API key is limited to 120 requests per 60 seconds. Batch large jobs client-side and honor retry_after_seconds on 429 responses. Validation failures (an ineligible option combination, an expired quote, a malformed address) return a descriptive error code instead of silently changing the request.
The hosted OpenAPI YAML is the customer API source of truth for SDK generation. It documents request bodies, response properties, enums, authentication, and webhook payloads.
GET /openapi.json
GET /openapi.public.json
GET /openapi.yaml
GET /openapi.public.yaml
Generated TypeScript (@postalform/projects-sdk) and Python (postalform-projects-sdk) clients are built from this schema. Until they are published to npm and PyPI, generate your own client from /openapi.public.yaml with any OpenAPI generator, or call the API directly — every endpoint works with plain HTTP.
Building with agents? GET /llm-context.txt serves a compact machine-readable summary of provisioning and this API.
This table is generated from the public OpenAPI schema served by /openapi.json and /openapi.yaml.
| Method | Path | Purpose | Request | Primary response |
|---|---|---|---|---|
POST | /api/v1/documents/upload-intent | Create a PDF upload intent. | object | UploadIntent |
POST | /api/v1/documents/{document_id}/complete | Mark an uploaded document complete after the object is present. | object | object |
POST | /api/v1/letters/quotes | Quote a mailpiece. | CreateQuoteRequest | Quote |
POST | /api/v1/letters | Create a test or live mail order from a quote. | CreateLetterRequest | Letter |
GET | /api/v1/letters/{order_id} | Retrieve a mail order, timeline, tracking fields, and customer webhook events. | None | Letter + object |
GET | /api/v1/letters/{order_id}/document.pdf | Preview or download the PDF for a letter order. | None | PDF bytes |
POST | /api/v1/postcards/quotes | Quote a postcard mailpiece. | CreatePostcardQuoteRequest | Quote |
POST | /api/v1/postcards | Create a test or live postcard order from a postcard quote. | CreateLetterRequest | Letter |
GET | /api/v1/postcards/{order_id} | Retrieve a postcard order, timeline, tracking fields, and customer webhook events. | None | Letter + object |
GET | /api/v1/postcards/{order_id}/document.pdf | Preview or download the PDF for a postcard order. | None | PDF bytes |
GET | /api/v1/webhook-endpoints | List customer webhook endpoints for the workspace. | None | WebhookEndpoint[] |
POST | /api/v1/webhook-endpoints | Configure a customer webhook endpoint for status events. | object | WebhookEndpointSecretResponse |
DELETE | /api/v1/webhook-endpoints/{endpoint_id} | Disable a customer webhook endpoint. | None | WebhookEndpoint |
POST | /api/v1/webhook-endpoints/{endpoint_id}/rotate-secret | Rotate a customer webhook endpoint signing secret. | None | WebhookEndpointSecretResponse |
GET | /api/v1/webhook-events | List customer webhook events for the workspace. | None | WebhookEvent[] |
POST | /api/v1/webhook-events/{event_id}/replay | Queue a customer webhook event for replay. | None | Replay queued. |
GET | /api/v1/credits/balance | Get prepaid credit balance. | None | CreditBalance |
GET | /api/v1/credits/payment-methods | List saved live billing payment methods. | None | BillingPaymentMethod[] |
POST | /api/v1/credits/payment-methods/setup-session | Create a Checkout setup session for live credit auto-refill. | None | PaymentMethodSetupSession |
GET | /api/v1/credits/auto-refill | Get credit auto-refill threshold policies. | None | CreditAutoRefillPolicies |
POST | /api/v1/credits/auto-refill | Configure the credit auto-refill threshold for the authenticated key mode. | CreditAutoRefillRequest | object |
GET | /api/v1/credits/ledger | List prepaid credit ledger entries. | None | CreditLedgerEntry[] |
GET | /api/v1/api-keys | List API key prefixes and status. | None | ApiKey[] |
POST | /api/v1/api-keys/rotate | Rotate a test or live API key. | object | ApiKeyRotation |
POST | /api/v1/credits/checkout-session | Create a prepaid credit top-up checkout session. | object | CreditCheckoutSession |
These request-body tables come from the same OpenAPI schema used for SDK generation.
Letter quote request. API clients choose mailpiece options; PostalForm handles fulfillment automatically.
| Property | Type / values | Required | Default | Meaning |
|---|---|---|---|---|
document_id | string | Yes | None | Uploaded PDF to quote. |
page_count | integer; min 1 | No | None | Optional explicit PDF page count. Used for deterministic pricing when present. |
mail_class | string | No | usps_first_class | Standard/USPS First Class by default. Accepts standard/usps_first_class, priority/usps_priority, and express/usps_express. Priority/Express cannot be combined with certified or registered proof mail. |
color | boolean | No | false | Whether the letter is printed in color instead of black and white. |
double_sided | boolean | No | true | Whether eligible pages are printed duplex. |
certified | boolean | No | false | Proof-mail add-on for letters only. Eligible U.S. standard letters request USPS Certified Mail. Eligible Canada standard letters request Canada Post Registered Mail. Eligible Belgium, Switzerland, and France standard letters request the available registered-mail option for that destination. |
certified_return_receipt | boolean | No | false | Electronic return receipt for eligible U.S. Certified Mail letters. |
signature_required | boolean | No | false | Signature confirmation for eligible U.S. Priority and Express letters. |
destination_country_code | string; min length 2, max length 2 | No | US | ISO 3166-1 alpha-2 destination country code used for quote validation and pricing. Defaults to US. First-party Projects destinations are US, CA, AT, BE, CH, DE, FR, GB, IN, LU, and NL. |
origin_country_code | string; min length 2, max length 2 | No | US | ISO 3166-1 alpha-2 sender/origin country code used for quote validation and pricing. Defaults to US. |
Postcard quote request. Postcards force first-class color double-sided mail and cannot use certified or registered mail.
| Property | Type / values | Required | Default | Meaning |
|---|---|---|---|---|
document_id | string | Yes | None | See the schema type and endpoint context. |
page_count | integer; min 1 | No | None | Optional explicit PDF page count. Postcard source PDFs should be fully composed. |
postcard_size | 4x6, 6x9, 11x6 | Yes | None | See the schema type and endpoint context. |
destination_country_code | string; min length 2, max length 2 | No | US | ISO 3166-1 alpha-2 destination country code used for quote validation and pricing. Defaults to US. |
origin_country_code | string; min length 2, max length 2 | No | US | ISO 3166-1 alpha-2 sender/origin country code used for quote validation and pricing. Defaults to US. |
Create an order from a quote. Requires Idempotency-Key header. Sender is strongly recommended for all live mail and required for some destinations and mailpiece options.
| Property | Type / values | Required | Default | Meaning |
|---|---|---|---|---|
quote_id | string | Yes | None | Quote returned by the quote endpoint. |
recipient | MailingAddress | Yes | None | Destination mailing address. |
sender | MailingAddress | No | None | Return/sender address. Strongly recommended for all live mail. |
metadata | object map | No | None | Optional caller metadata stored on the order and surfaced in reads/webhook payloads. |
Flexible mailing address object. Pass countryCode, country_code, country, address_country, or addressCountry for international destinations; country defaults to US when omitted.
| Property | Type / values | Required | Default | Meaning |
|---|---|---|---|---|
name | string | No | None | Recipient or sender name. |
company | string | No | None | Optional organization name. |
line1 | string | No | None | Also accepts street1, address_line1, or addressLine1. |
line2 | string | No | None | Also accepts street2, address_line2, or addressLine2. |
city | string | No | None | Also accepts address_city or addressCity. |
state | string | No | None | Also accepts province, provinceOrState, address_state, or addressState. |
postal_code | string | No | None | Also accepts postalCode, postalOrZip, zip, address_zip, or addressZip. |
countryCode | string; min length 2, max length 2 | No | US | ISO 3166-1 alpha-2 country code. Defaults to US when omitted. |
These response tables explain the IDs, prices, state fields, timeline entries, and tracking fields returned by order and credit reads.
| Property | Type / values | Required | Default | Meaning |
|---|---|---|---|---|
document_id | string | No | None | Stable document identifier used in quote requests. |
upload_url | string (uri) | No | None | Temporary URL that accepts the PDF bytes. |
upload_method | PUT | No | None | HTTP method to use when uploading to the temporary URL. |
expires_at | string (date-time) | No | None | When the temporary upload URL stops accepting uploads. |
| Property | Type / values | Required | Default | Meaning |
|---|---|---|---|---|
quote_id | string | No | None | Short-lived quote token used to create the order. |
mailpiece_type | letter, postcard | No | None | Whether the quote is for a letter or postcard. |
postcard_size | 4x6, 6x9, 11x6, null | No | None | Postcard size for postcard quotes; null for letters. |
price_cents | integer | No | None | Customer price in cents for the quoted mailpiece. |
currency | usd | No | None | Quote currency. |
pricing_version | string | No | None | Pricing table version used for this quote. |
certified_return_receipt | boolean | No | None | Whether electronic return receipt was accepted for the quote. |
signature_required | boolean | No | None | Whether signature confirmation was accepted for the quote. |
expires_at | string (date-time) | No | None | When the quote can no longer be used to create an order. |
| Property | Type / values | Required | Default | Meaning |
|---|---|---|---|---|
id | string | No | None | PostalForm order ID. |
object | letter, postcard | No | None | Object type returned by the API. |
mailpiece_type | letter, postcard | No | None | Mailpiece family for the order. |
postcard_size | 4x6, 6x9, 11x6, null | No | None | Postcard size for postcard orders; null for letters. |
status | MailOrderStatus | No | None | Customer-facing order state. |
mode | Mode | No | None | Whether the order was created with a test or live key. |
price_cents | integer | No | None | Final customer price reserved or captured for the order. |
currency | string | No | None | Order currency. |
funds_status | string | No | None | Billing state for the order funds. |
billing_rail | string | No | None | Billing source used for the order. |
tracking_number | string | null | No | None | Carrier tracking number when available. |
tracking_status | string | null | No | None | Normalized tracking status when available. |
metadata | object map | No | None | Caller-defined metadata copied from the create-order request. |
created_at | string (date-time) | No | None | Order creation timestamp. |
updated_at | string (date-time) | No | None | Most recent order update timestamp. |
idempotent_replay | boolean | No | None | True when the response came from a repeated idempotency key. |
Customer-facing order timeline event. Internal fulfillment identifiers are not exposed.
| Property | Type / values | Required | Default | Meaning |
|---|---|---|---|---|
id | string | No | None | Timeline event ID. |
event_type | string | No | None | Customer-facing event type. |
status_before | string | No | None | Order status before the event, when known. |
status_after | string | No | None | Order status after the event, when known. |
source | string | No | None | Public source category for the status update. |
created_at | string (date-time) | No | None | Timeline event timestamp. |
| Property | Type / values | Required | Default | Meaning |
|---|---|---|---|---|
mode | test, live | No | None | Key mode for the returned balance. |
billing_rail | mock_test_credits, prepaid_credits, future_projects_spt, future_metronome_streaming, future_mpp_tempo, manual_invoice | No | None | Billing source currently backing credits. |
available_cents | integer | No | None | Credit cents available for new live orders. |
reserved_cents | integer | No | None | Credit cents reserved for created orders that are not fully settled. |
currency | string | No | None | Balance currency. |
status | string | No | None | Credit account status. |
test | CreditBalanceSnapshot | No | None | Test-mode balance snapshot. |
live | CreditBalanceSnapshot | No | None | Live-mode balance snapshot. |
auto_refill | CreditAutoRefillPolicies | No | None | Configured auto-refill policies. |
Webhook payloads use customer-facing order states. Store webhook signing secrets when they are created or rotated because they are returned once.
| Property | Type / values | Required | Default | Meaning |
|---|---|---|---|---|
id | string | No | None | Webhook endpoint ID. |
url | string (uri); pattern ^https:// | No | None | HTTPS URL that receives signed status webhooks. |
status | string | No | None | Endpoint lifecycle status. |
created_at | string (date-time) | No | None | Endpoint creation timestamp. |
| Property | Type / values | Required | Default | Meaning |
|---|---|---|---|---|
id | string | No | None | See the schema type and endpoint context. |
url | string (uri); pattern ^https:// | No | None | See the schema type and endpoint context. |
status | string | No | None | See the schema type and endpoint context. |
created_at | string (date-time) | No | None | See the schema type and endpoint context. |
signing_secret | string | No | None | Returned only when the endpoint is created or its signing secret is rotated. |
JSON body POSTed to customer webhook endpoints.
| Property | Type / values | Required | Default | Meaning |
|---|---|---|---|---|
id | string | No | None | Webhook event ID. |
type | CustomerWebhookEventType | No | None | PostalForm event name. |
data | object | No | None | Webhook data wrapper. data.object is the current order object. |
mailpiece | object | No | None | Tracking and delivery-state snapshot for the physical mailpiece. |
| Property | Type / values | Required | Default | Meaning |
|---|---|---|---|---|
mode | Mode | No | None | Optional key mode override. Defaults to the authenticated key mode. |
enabled | boolean | No | true | Whether automatic refill is enabled. |
threshold_cents | integer; min 100, max 100000 | Yes | None | Trigger a refill when available credits fall below this amount. |
refill_amount_cents | integer; min 100, max 100000 | Yes | None | Credit amount to purchase when the threshold is crossed. |
| Property | Type / values | Required | Default | Meaning |
|---|---|---|---|---|
mode | Mode | No | None | Key mode that was rotated. |
api_key | string | No | None | Full API key secret. Returned once. |
api_key_prefix | string | No | None | Non-secret key prefix shown later in dashboards and list responses. |
Order timeline status. `submission_pending` means the fulfillment submit job entered a physical-mail safety window where PostalForm cannot blindly retry without risking duplicate mail; it requires reconciliation if it remains the current order status.
| Value | Meaning |
|---|---|
queued | Order accepted and waiting for document preparation. |
document_preparing | PostalForm is preparing the print-ready PDF. |
document_prepared | The print-ready PDF is ready for submission. |
submitted | The mailpiece has been submitted for production. |
accepted | The mailpiece was accepted for production or mailing. |
in_transit | The mailpiece is moving through the mail stream. |
delivered | Delivery was reported. |
returned | The mailpiece was returned. |
submission_pending | Submission entered a safety window and needs reconciliation before blind retry. |
failed | The order failed and needs review or retry. |
canceled | The order was canceled. |
Customer webhook event names emitted for fulfillment status changes. Replace `letter` with `postcard` for postcard mailpieces.
| Value | Meaning |
|---|---|
postalform.letter.accepted | Letter accepted for production or mailing after the order leaves PostalForm's preparation queue. |
postalform.letter.in_transit | Letter entered the mail stream. The payload may include or update `mailpiece.tracking_number` when tracking is available. |
postalform.letter.delivered | Letter reported delivered by the carrier or delivery network. |
postalform.letter.returned | Letter returned or otherwise marked undeliverable. |
postalform.letter.failed | Letter could not be produced or mailed. Inspect the order timeline and retry with a corrected request when appropriate. |
postalform.letter.canceled | Letter canceled before delivery completion. |
postalform.postcard.accepted | Postcard accepted for production or mailing after the order leaves PostalForm's preparation queue. |
postalform.postcard.in_transit | Postcard entered the mail stream. The payload may include or update `mailpiece.tracking_number` when tracking is available. |
postalform.postcard.delivered | Postcard reported delivered by the carrier or delivery network. |
postalform.postcard.returned | Postcard returned or otherwise marked undeliverable. |
postalform.postcard.failed | Postcard could not be produced or mailed. Inspect the order timeline and retry with a corrected request when appropriate. |
postalform.postcard.canceled | Postcard canceled before delivery completion. |
| Value | Meaning |
|---|---|
test | Test mode. |
live | Real mail and live billing. |