API reference
Dynamic Audience API
Send the people who qualify for a dynamic campaign, and get a final result for each one in the same request.
Generated from /openapi.json.
Send recipients
POSThttps://www.postaged.com/api/v1/recipients
Posts the people who meet your criteria to one dynamic campaign. Everything is decided inside this request: each address is verified, the campaign's filters are applied, the budget is checked, and your team is charged. Each result is final when you receive it, except filtered_conditions (see Reasons a recipient was not mailed).
A successful request mails cards and charges your team. Check a payload first with Send test request in the campaign's API / Webhook setup (see Testing).
curl https://www.postaged.com/api/v1/recipients \
-H "Authorization: Bearer $POSTAGED_SEND_KEY" \
-H "Content-Type: application/json" \
-d '{
"campaign_id": "PA-4H2K9C",
"recipients": [
{
"reference": "crm-001",
"first_name": "Dana",
"last_name": "Reyes",
"address_1": "25 Gale Road",
"city": "Bloomfield",
"state": "CT",
"zip": "06002",
"Offer_Code": "SPRING24"
}
],
"field_types": {
"Offer_Code": "text"
}
}'const response = await fetch("https://www.postaged.com/api/v1/recipients", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.POSTAGED_SEND_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
campaign_id: "PA-4H2K9C",
recipients: [
{
reference: "crm-001",
first_name: "Dana",
last_name: "Reyes",
address_1: "25 Gale Road",
city: "Bloomfield",
state: "CT",
zip: "06002",
Offer_Code: "SPRING24",
},
],
field_types: {
Offer_Code: "text",
},
}),
});
const result = await response.json();
console.log(result);import os
import requests
response = requests.post(
"https://www.postaged.com/api/v1/recipients",
headers={"Authorization": f"Bearer {os.environ['POSTAGED_SEND_KEY']}"},
json={
"campaign_id": "PA-4H2K9C",
"recipients": [
{
"reference": "crm-001",
"first_name": "Dana",
"last_name": "Reyes",
"address_1": "25 Gale Road",
"city": "Bloomfield",
"state": "CT",
"zip": "06002",
"Offer_Code": "SPRING24",
},
],
"field_types": {
"Offer_Code": "text",
},
},
timeout=300,
)
print(response.json())Authentication
A Full access key. Send it one of three ways:
- AuthorizationheaderPreferred
Bearerfollowed by a space and your key. - X-API-KeyheaderYour key, for senders that can set a header but not
Authorization. - api_keybody fieldYour key, for senders that cannot set headers.
Never send a key in the URL or a query string. Integration keys and Formulas only keys can't send recipients: they get the same 401 invalid_api_key as a wrong or revoked key.
Authorization: Bearer $POSTAGED_SEND_KEY
Content-Type: application/jsonRequest body
- campaign_idstringRequiredThe campaign to mail, from its API / Webhook setup:
PA-followed by 6 characters. - recipientsarray of objectsRequired1 to 5,000 recipients. On a Neighborhood Mail campaign, 1 to 250 addresses to mail around.
- first_namestringOptionalGiven name.
- last_namestringOptionalFamily name.
- address_1stringRequiredStreet address. A recipient without it is not mailed (
invalid_address); the rest of the request still runs. - address_2stringOptionalUnit or suite.
- citystringRequiredCity. A recipient without it is not mailed (
invalid_address); the rest of the request still runs. - statestringRequiredTwo-letter state. A recipient without it is not mailed (
invalid_address); the rest of the request still runs. - zipstringRequiredZIP code. A recipient without it is not mailed (
invalid_address); the rest of the request still runs. - referencestringOptionalYour own ID for this recipient, echoed back in its result.
- birthdaystringOptionalBirthday, like
3/14or1985-03-14. - any other keystringOptionalA custom field, by name. A name you haven't used before becomes a real address book column. Values are stored up to 600 characters. A variable image is an https link sent under the field name shown in the designer. Values sent for formula and AI columns are ignored and counted in
computedFieldsIgnored.
- field_typesobjectOptionalThe type of each new custom field, by name:
text,number,date, oryes_no. Without one, a new field is Text. - require_variable_imagesbooleanOptional, default falseWhen true, a recipient without a usable variable image is not mailed (
variable_image_missing) instead of printing the frame's default image. - api_keystringOptionalYour key, only when you cannot send it in a header.
{
"campaign_id": "PA-4H2K9C",
"recipients": [
{
"reference": "crm-001",
"first_name": "Dana",
"last_name": "Reyes",
"address_1": "25 Gale Road",
"city": "Bloomfield",
"state": "CT",
"zip": "06002",
"address_2": "Unit 4",
"birthday": "1985-03-14",
"Offer_Code": "SPRING24",
"Hero_Photo": "https://images.example.com/dana.jpg"
}
],
"field_types": { "Offer_Code": "text" },
"require_variable_images": false
}Response 200
One result per recipient, whether or not each was mailed. A recipient's result is final when you receive it, except filtered_conditions (below).
- summaryobject
received,mailed, andnotMailedcounts.unparseableTypedValuesappears when a custom value didn't fit its column's type (stored exactly as sent).computedFieldsIgnoredcounts values dropped for formula and AI columns. - resultsarray of objectsIn the order you sent them.
- referencestring or nullYour ID, when you sent one.
- addressstringThe address, verified when it could be, otherwise as sent.
address_2is not included. - statusstring
mailedornot_mailed. - reasonstringWhy a recipient was not mailed (below).
- detailstringA sentence you can show to a person. It varies with the case. Branch on
statusandreason, never ondetail. - homesFound, homesMailedintegerNeighborhood Mail only: homes found around this address, and homes mailed.
- product_type, postage, mail_by_datestringFor a mailed recipient on a campaign that isn't Neighborhood Mail: the product's name,
standardorfirst_class, and the mail-by date (YYYY-MM-DD). - warningsarray of objectsMailed with a fallback (not on Neighborhood Mail):
code,field, anddetail(codes below). - return_address_fallback, return_address_reasonboolean, stringPresent when the recipient's return address couldn't be used, so the design's fallback return address prints (not on Neighborhood Mail).
- results_truncatedbooleanPresent (
true) when the full response would have been larger than 4 MB.resultsthen lists only the recipients that were not mailed or were mailed withwarningsorreturn_address_fallback, as many as fit, in the order you sent them.summarystill counts every recipient.
{
"summary": { "received": 3, "mailed": 2, "notMailed": 1 },
"results": [
{
"reference": "crm-001",
"address": "25 Gale Rd, Bloomfield, CT 06002-2214",
"status": "mailed",
"detail": "Queued to send.",
"product_type": "Handwritten Card & Handwritten Envelope",
"postage": "first_class",
"mail_by_date": "2026-10-06"
},
{
"reference": "crm-002",
"address": "18 Rowan St, Hartford, CT 06105-2214",
"status": "mailed",
"detail": "Queued to send.",
"product_type": "Handwritten Card & Handwritten Envelope",
"postage": "first_class",
"mail_by_date": "2026-10-06",
"warnings": [
{
"code": "text_overlap",
"field": "Offer_Code",
"detail": "Text for this recipient in the \"Offer\" text box would overlap the QR code. The card will print with the overlap."
}
]
},
{
"reference": "crm-003",
"address": "9 Elm St, Hartford, CT 06106-1830",
"status": "not_mailed",
"reason": "duplicate_recent",
"detail": "We already mailed this address for this campaign recently."
}
]
}Reasons a recipient was not mailed
| reason | Typical detail |
|---|---|
invalid_address | We could not verify this address. |
do_not_mail | This address is on a do-not-mail list for this campaign. |
filtered_location | Outside the area you drew on the map. |
filtered_conditions | Added to the campaign. Waiting on your filter conditions. |
budget_exceeded | This campaign's budget was already spent. |
duplicate_recent | We already mailed this address for this campaign recently. |
payment_failed | Your card was declined, so nothing was mailed. |
no_homes_found | No homes inside the radius around this address. |
request_capacity_reached | We reached this request's limit. Send this address again in another request. |
text_overflow | The custom text does not fit its text box on your design. |
variable_image_missing | Variable image "{title}" is not usable, and this request set require_variable_images. |
unsupported_characters | {font name} can't print {characters} in the "{box name}" text box. |
removed_by_sender | Removed from this campaign by the sender. |
detail can vary with the case, for example naming the text box or the image. Branch on reason, never on detail.
filtered_conditions isn't final. We add the recipient to the campaign and check them against your filter conditions every day. When they match, we mail them and charge your team then, within the campaign's budget, and the mailing shows in the campaign's Recipients received list. Sending the same address again updates their information.
Warning codes
A mailed recipient carries warnings when a fallback fired. The card still mails.
| code | What happened |
|---|---|
variable_image_fallback | The variable image couldn't be used, so the frame's default image prints. |
variable_image_blank | The variable image couldn't be used and the frame has no default, so the frame prints empty. |
qr_fallback_link | The QR code uses the design's fallback link because a field it needs is empty. |
text_overlap | This recipient's text would overlap another element. The card prints with the overlap. |
text_unmeasurable | We couldn't check a text box for fit or overlaps (the detail names it). The card prints in full. |
text_curve_too_long | This recipient's text is too long for its curve or path, even at its smallest size. The card prints in full. |
return_address_reason values
missing_token_value: a field the return address uses is empty.unverified_address: the return address could not be verified.verification_limit_reached: this request reached its return address verification limit.
{
"reference": "crm-004",
"address": "77 Birch Ln, Avon, CT 06001",
"status": "not_mailed",
"reason": "filtered_location",
"detail": "Outside the area you drew on the map."
}Errors
Each status in the table below means the whole request failed and nothing was mailed or charged. The body is { "code", "message" }. Full tables: Error reference.
A timeout (504) or a dropped connection may come after some recipients were mailed. Send the same payload again: no one is mailed twice. Within 15 minutes of a request that finished, you get its stored response; otherwise recipients already mailed come back as duplicate_recent. If a request times out, wait 20 minutes, then send it again. Recipients it already mailed come back as duplicates; the rest are mailed once.
A body over 4.25 MB can get the hosting platform's own 413, without the { "code", "message" } body.
| Status | code | message | What to do |
|---|---|---|---|
| 401 | invalid_api_key | We could not verify that API key. Check the key and try again. | Send a Full access key. Integration keys and Formulas only keys get this same response. |
| 404 | campaign_not_found | We could not find a campaign with that campaign ID. | Copy the campaign ID again from the campaign's API / Webhook setup. IDs are case-sensitive, and a request with no campaign_id gets this response too. |
| 409 | campaign_not_active | That campaign is not active, so we did not mail anyone. | Open the campaign in Postaged and check that it is active, gets its audience from API / Webhook, and has a design on every step, then send the request again. If it keeps happening, contact support. |
| 409 | design_cannot_render | A design holds a setting we can't print: This campaign can't be placed yet. "{name}" on the {surface} of "{template name}" uses a setting we can't print. Change or delete it in the designer, then try again. A design is still an AI draft: Open and save this AI draft in the designer before using it in a campaign. | Open the named design in the designer, change or delete the item the message names, then send the request again. When the message says to open and save an AI draft, open that template in the designer and save it, then send the request again. Nothing was mailed or charged. |
| 413 | payload_too_large | That request was too large. Send up to 5,000 recipients per request. | Split the recipients across requests: up to 5,000 each (250 for Neighborhood Mail), each under 4.25 MB. |
| 422 | invalid_payload | We could not read that request. Check the format and try again. | Send a valid JSON body with a recipients array that isn't empty. |
| 429 | rate_limited | Too many requests. Wait a moment and try again. | Wait the number of seconds in Retry-After, then send the request again. |
| 500 | internal_error | Something went wrong on our end. Nothing was mailed. Try again shortly. | Send the same request again in a few minutes. |
| 503 | validation_unavailable | We could not check addresses just now, so nothing was mailed. Try again in a few minutes. If it keeps happening, contact support. | Send the same request again in a few minutes. Contact support if it keeps happening. |
{
"code": "invalid_api_key",
"message": "We could not verify that API key. Check the key and try again."
}{
"code": "campaign_not_found",
"message": "We could not find a campaign with that campaign ID."
}{
"code": "campaign_not_active",
"message": "That campaign is not active, so we did not mail anyone."
}{
"code": "design_cannot_render",
"message": "This campaign can't be placed yet. \"Gradient banner\" on the front of \"Spring open house\" uses a setting we can't print. Change or delete it in the designer, then try again."
}{
"code": "design_cannot_render",
"message": "Open and save this AI draft in the designer before using it in a campaign."
}{
"code": "payload_too_large",
"message": "That request was too large. Send up to 5,000 recipients per request."
}{
"code": "invalid_payload",
"message": "We could not read that request. Check the format and try again."
}HTTP/1.1 429 Too Many Requests
Retry-After: 60
{
"code": "rate_limited",
"message": "Too many requests. Wait a moment and try again."
}{
"code": "internal_error",
"message": "Something went wrong on our end. Nothing was mailed. Try again shortly."
}{
"code": "validation_unavailable",
"message": "We could not check addresses just now, so nothing was mailed. Try again in a few minutes. If it keeps happening, contact support."
}Limits
- 5,000 recipients per request. On a Neighborhood Mail campaign, 250 addresses and 10,000 homes per request.
- 4.25 MB per request body.
- 4 MB per response. A larger response lists only the recipients that were not mailed or were mailed with a warning, with
results_truncatedset totrue. - 60 requests per minute per API key, and 60 per minute from one source address. Over the limit:
429 rate_limitedwithRetry-After: 60. - A request that sends the same addresses to the same campaign within 15 minutes returns the stored response instead of running again, even when names or custom fields changed, unless that response mailed nobody. No
Idempotency-Keyheader is read. - An address mailed for a campaign is not mailed again for that campaign for 30 days, or for the campaign's re-send protection window when that is on and longer (90 days by default) (
duplicate_recent). - 10 new custom columns per request, and 50 API-created columns per team. Past either limit, no new column is created and the request still runs. Custom values are stored up to 600 characters.
See also Rate limits and Idempotency and retries.