Send real handwritten letters — written in real handwriting, folded by hand, stamped and mailed — straight from your software. One order, one recipient, or a whole farm — a thousand doors or ten.
The API has two doors. Pick yours:
- Developer door — plain REST over HTTPS with JSON. Start here.
Political and campaign orders
Is the order political, election, or campaign related? Add the optional campaign block so the details are kept with the order record:
{
"recipients": [ ... ],
"message": "Dear {{first_name}}, ...",
"return_address": { ... },
"reference": "GOTV batch 2",
"campaign": {
"political": true,
"committee": "Friends of Jane Smith",
"contact_name": "Robert Jones",
"contact_email": "treasurer@janesmithforcongress.example",
"state": "Ohio",
"list_source": "Campaign-provided CSV / voter file export",
"paid_for": "Paid for by Friends of Jane Smith"
}
}
When political is true, four fields become required: committee, contact_name, contact_email and state. The block is ignored entirely for everyone else — existing integrations send orders exactly as before. list_source notes where the recipient list came from (we are a mail vendor; the list is yours) and paid_for is an optional line to write on the letter where state rules require it.
Developer reference
Quickstart
Confirm your key works (returns your email and card on file):
curl https://realinkletters.com/wp-json/realink/v1/ping \
-H "X-RIL-Key: ril_your_key_here"
Send your first letter order:
curl -X POST https://realinkletters.com/wp-json/realink/v1/orders/create \
-H "X-RIL-Key: ril_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"recipients": [
{"first_name": "James", "last_name": "Whitfield", "street": "136 Marigold Way", "city": "Boise", "state": "ID", "zip": "83704"}
],
"message": "Hi {first_name},\n\nWelcome to the neighborhood! If you ever need anything, I'm one street over.\n\n— Sarah",
"return_address": {"name": "Sarah Mitchell", "street": "12 Peard Lane", "city": "Boise", "state": "ID", "zip": "83702"}
}'
Success response:
{
"ok": true,
"order_id": 1053,
"order_number": "1053",
"recipients": 1,
"unit_price": 4.5,
"total": 4.5,
"charged": true
}
The card on file is charged immediately, the order appears in your account, and we write, fold, stamp and mail the letters.
Authentication
Every request carries your API key in the X-RIL-Key header. Authorization: Bearer <key> is also accepted.
- Keys are issued per account at My Account → API Keys (self-service, up to 3 active keys, revoke anytime).
- A key can only ever place orders for — and charge — its own account’s saved card.
- Revoking a key is instant and does not affect orders already placed.
Test your key with the ping endpoint any time.
Endpoints
Ping
GET /wp-json/realink/v1/ping — confirms the key and shows the card on file.
{
"ok": true,
"customer": "you@yourbusiness.com",
"card_on_file": { "brand": "visa", "last4": "4242" }
}
card_on_file is false until a card is saved.
Create order
POST /wp-json/realink/v1/orders/create
Request body:
| Field | Type | Required | Notes |
|---|---|---|---|
recipients |
array | yes | 1–10,000 objects. The array is the billable quantity. |
recipients[].first_name / last_name |
string | yes* | Or a single name field — we split it. |
recipients[].street |
string | yes | |
recipients[].street2 |
string | no | Apartment, unit, etc. |
recipients[].city / state / zip |
string | yes | |
custom_fields |
object | no | Custom merge tokens: {"token": "label"}. Up to 15, snake_case names. Labels are for the print office. A JSON-encoded string is also accepted (Make modules). |
recipients[].<token> |
string | no | Per-recipient values for tokens declared in custom_fields (max 80 characters each). |
recipients[].custom |
string | no | Integrator convenience (Make modules): token=value;token2=value2 pairs, parsed onto the recipient row. Only declared tokens; explicit token keys win. |
message |
string | yes | The letter text, max 432 characters. Merge fields supported. (letter accepted as an alias.) |
return_address |
object | no | name, street, street2?, city, state, zip. Optional — omit to use the return address saved on your account (My Account → Return Address, seeded from your Invoice Address). If provided, every required field must be present. Every envelope carries one. |
reference |
string | no | Your own tracking label, shown on the order. |
paper |
string | no | half-letter (the only stock). |
Merge fields — use these in message and they’re replaced per recipient: {first_name}, {last_name}, {city}, {street}.
Custom merge fields — declare your own tokens in custom_fields (snake_case, up to 15 per order), pass a value for each token on each recipient (80 characters max), then use {token} anywhere in the message. Coupon codes, policy numbers, appointment dates — any per-recipient data merges in exactly like the standard fields. The production preflight flags any recipient missing a token value before a single letter is written.
"custom_fields": { "coupon_code": "Coupon code", "renewal_date": "Renewal date" },
"recipients": [
{"first_name": "James", "last_name": "Whitfield", "street": "136 Marigold Way", "city": "Boise", "state": "ID", "zip": "83704", "coupon_code": "SAVE20", "renewal_date": "Oct 14"}
],
"message": "Hi {first_name},\n\nYour code {coupon_code} is ready — it expires {renewal_date}.\n\n— Sarah"
Pricing — automatic, from the live volume ladder, based on the recipients array length:
| Recipients | Price per letter |
|---|---|
| 1 to 49 | $4.31 |
| 50 to 99 | $3.31 |
| 100 to 199 | $2.61 |
| 200 to 299 | $2.31 |
| 300 to 399 | $2.01 |
| 400 and up | $1.81 |
Payment — charged immediately to the saved card (Stripe). If the charge fails, the order is parked as Failed, no letters are produced, the account owner gets an email, and the API returns:
{ "ok": false, "order_id": 1054, "error": "Your card was declined." }
Fix the card in My Account and re-send the order. Failed orders never mail anything.
Success response: ok, order_id, order_number, recipients (count), unit_price, total, charged: true. An email confirmation is sent to the account owner for every API order, success or failure.
Errors
| HTTP | code |
Meaning / fix |
|---|---|---|
| 400 | ril_bad_body |
Body isn’t valid JSON. |
| 401 | ril_no_key |
Send the X-RIL-Key header. |
| 401 | ril_bad_key |
Key is unknown or revoked — generate a new one. |
| 402 | ril_no_card |
No saved card on the account. Save one in My Account → Payment Methods. |
| 422 | ril_no_recipients |
recipients must be a non-empty array. |
| 422 | ril_too_many |
More than 10,000 recipients. Split into multiple orders. |
| 422 | ril_bad_recipient |
Recipient #N is missing a required field — the message says which. |
| 422 | ril_no_letter |
message is required. |
| 422 | ril_letter_too_long |
Letter exceeds 432 characters. |
| 422 | ril_no_return |
No return_address given and none saved on the account — save one at My Account → Return Address, or include return_address in the request. An incomplete return_address is also rejected. |
| 200 | — (ok: false) |
Card was declined. Order parked as Failed; see Payment above. |
Limits
- Max letter length: 432 characters (one handwritten half-letter page).
- Max order size: 10,000 recipients. For bigger sends, place multiple orders or email hello@realinkletters.com.
- Max custom merge fields: 15 tokens per order, each value capped at 80 characters.
- A failed network response may still have created the order — check your account (or the email confirmation) before retrying, since a retry creates a new order.
Changelog
1.2.0 — 2026-09-30
- Integrator support:
custom_fieldsaccepts a JSON-encoded string (raw JSON posts), and recipients accept acustomfield oftoken=value;token2=value2pairs.
1.1.9 — 2026-09-30
- Custom merge fields: declare up to 15 snake_case tokens in
custom_fields, pass per-recipient values (80-char cap), merge them into the letter exactly like the standard fields. Matches the custom-column support in the order flow (v2.2.0).
1.1.0 — 2026-09-18
- Customer self-service API keys: My Account → API Keys (generate, label, revoke; up to 3 active keys).
- Card-on-file status surfaced in
ping.
1.0.0 — 2026-09-18
- Initial release.
pingandorders/createendpoints, API-key authentication, off-session card charging, volume-tier pricing, merge fields,referencelabels,paperoption.
Questions: hello@realinkletters.com