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"
    }
  }'

Authentication

A Full access key. Send it one of three ways:

  • AuthorizationheaderPreferred
    Bearer followed by a space and your key.
  • X-API-Keyheader
    Your key, for senders that can set a header but not Authorization.
  • api_keybody field
    Your 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.

Headers
Authorization: Bearer $POSTAGED_SEND_KEY
Content-Type: application/json

Request body

  • campaign_idstringRequired
    The campaign to mail, from its API / Webhook setup: PA- followed by 6 characters.
  • recipientsarray of objectsRequired
    1 to 5,000 recipients. On a Neighborhood Mail campaign, 1 to 250 addresses to mail around.
    • first_namestringOptional
      Given name.
    • last_namestringOptional
      Family name.
    • address_1stringRequired
      Street address. A recipient without it is not mailed (invalid_address); the rest of the request still runs.
    • address_2stringOptional
      Unit or suite.
    • citystringRequired
      City. A recipient without it is not mailed (invalid_address); the rest of the request still runs.
    • statestringRequired
      Two-letter state. A recipient without it is not mailed (invalid_address); the rest of the request still runs.
    • zipstringRequired
      ZIP code. A recipient without it is not mailed (invalid_address); the rest of the request still runs.
    • referencestringOptional
      Your own ID for this recipient, echoed back in its result.
    • birthdaystringOptional
      Birthday, like 3/14 or 1985-03-14.
    • any other keystringOptional
      A 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_typesobjectOptional
    The type of each new custom field, by name: text, number, date, or yes_no. Without one, a new field is Text.
  • require_variable_imagesbooleanOptional, default false
    When true, a recipient without a usable variable image is not mailed (variable_image_missing) instead of printing the frame's default image.
  • api_keystringOptional
    Your key, only when you cannot send it in a header.
Request body, every field
{
  "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, and notMailed counts. unparseableTypedValues appears when a custom value didn't fit its column's type (stored exactly as sent). computedFieldsIgnored counts values dropped for formula and AI columns.
  • resultsarray of objects
    In the order you sent them.
    • referencestring or null
      Your ID, when you sent one.
    • addressstring
      The address, verified when it could be, otherwise as sent. address_2 is not included.
    • statusstring
      mailed or not_mailed.
    • reasonstring
      Why a recipient was not mailed (below).
    • detailstring
      A sentence you can show to a person. It varies with the case. Branch on status and reason, never on detail.
    • homesFound, homesMailedinteger
      Neighborhood Mail only: homes found around this address, and homes mailed.
    • product_type, postage, mail_by_datestring
      For a mailed recipient on a campaign that isn't Neighborhood Mail: the product's name, standard or first_class, and the mail-by date (YYYY-MM-DD).
    • warningsarray of objects
      Mailed with a fallback (not on Neighborhood Mail): code, field, and detail (codes below).
    • return_address_fallback, return_address_reasonboolean, string
      Present when the recipient's return address couldn't be used, so the design's fallback return address prints (not on Neighborhood Mail).
  • results_truncatedboolean
    Present (true) when the full response would have been larger than 4 MB. results then lists only the recipients that were not mailed or were mailed with warnings or return_address_fallback, as many as fit, in the order you sent them. summary still counts every recipient.
Response200 OK
{
  "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

reasonTypical detail
invalid_addressWe could not verify this address.
do_not_mailThis address is on a do-not-mail list for this campaign.
filtered_locationOutside the area you drew on the map.
filtered_conditionsAdded to the campaign. Waiting on your filter conditions.
budget_exceededThis campaign's budget was already spent.
duplicate_recentWe already mailed this address for this campaign recently.
payment_failedYour card was declined, so nothing was mailed.
no_homes_foundNo homes inside the radius around this address.
request_capacity_reachedWe reached this request's limit. Send this address again in another request.
text_overflowThe custom text does not fit its text box on your design.
variable_image_missingVariable 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_senderRemoved 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.

codeWhat happened
variable_image_fallbackThe variable image couldn't be used, so the frame's default image prints.
variable_image_blankThe variable image couldn't be used and the frame has no default, so the frame prints empty.
qr_fallback_linkThe QR code uses the design's fallback link because a field it needs is empty.
text_overlapThis recipient's text would overlap another element. The card prints with the overlap.
text_unmeasurableWe couldn't check a text box for fit or overlaps (the detail names it). The card prints in full.
text_curve_too_longThis 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.
A recipient that was not mailed
{
  "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.

StatuscodemessageWhat to do
401invalid_api_keyWe 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.
404campaign_not_foundWe 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.
409campaign_not_activeThat 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.
409design_cannot_renderA 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.
413payload_too_largeThat 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.
422invalid_payloadWe could not read that request. Check the format and try again.Send a valid JSON body with a recipients array that isn't empty.
429rate_limitedToo many requests. Wait a moment and try again.Wait the number of seconds in Retry-After, then send the request again.
500internal_errorSomething went wrong on our end. Nothing was mailed. Try again shortly.Send the same request again in a few minutes.
503validation_unavailableWe 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.
invalid_api_key401
{
  "code": "invalid_api_key",
  "message": "We could not verify that API key. Check the key and try again."
}
campaign_not_found404
{
  "code": "campaign_not_found",
  "message": "We could not find a campaign with that campaign ID."
}
campaign_not_active409
{
  "code": "campaign_not_active",
  "message": "That campaign is not active, so we did not mail anyone."
}
design_cannot_render409
{
  "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."
}
design_cannot_render409
{
  "code": "design_cannot_render",
  "message": "Open and save this AI draft in the designer before using it in a campaign."
}
payload_too_large413
{
  "code": "payload_too_large",
  "message": "That request was too large. Send up to 5,000 recipients per request."
}
invalid_payload422
{
  "code": "invalid_payload",
  "message": "We could not read that request. Check the format and try again."
}
rate_limited429
HTTP/1.1 429 Too Many Requests
Retry-After: 60

{
  "code": "rate_limited",
  "message": "Too many requests. Wait a moment and try again."
}
internal_error500
{
  "code": "internal_error",
  "message": "Something went wrong on our end. Nothing was mailed. Try again shortly."
}
validation_unavailable503
{
  "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_truncated set to true.
  • 60 requests per minute per API key, and 60 per minute from one source address. Over the limit: 429 rate_limited with Retry-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-Key header 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.