API reference
Error reference
Every error code, reason, and warning each API returns, with what to do about it.
Dynamic Audience API
Errors use a top-level { "code", "message" } body. Each status means the whole request failed and nothing was mailed or charged. See Errors for an example of each.
| 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. |
Reasons a recipient was not mailed
A recipient's reason when its status is not_mailed. The request itself succeeded with 200.
| reason | Typical detail | What to do |
|---|---|---|
invalid_address | We could not verify this address. | Check this recipient's address fields, then send it again in a later request. |
do_not_mail | This address is on a do-not-mail list for this campaign. | Nothing to do: this address is on a do-not-mail list for this campaign. |
filtered_location | Outside the area you drew on the map. | Nothing to do: the address is outside the area drawn on the campaign's map. |
filtered_conditions | Added to the campaign. Waiting on your filter conditions. | Nothing to do: the recipient waits in the campaign and is mailed if it meets your filter conditions later. |
budget_exceeded | This campaign's budget was already spent. | Raise the campaign's budget in Postaged, then send this recipient again. |
duplicate_recent | We already mailed this address for this campaign recently. | Nothing to do: this address was already mailed for this campaign within its re-send window. |
payment_failed | Your card was declined, so nothing was mailed. | Update the team's payment method in Postaged, then send the request again. |
no_homes_found | No homes inside the radius around this address. | Nothing to do: no homes were found inside the radius around this address. |
request_capacity_reached | We reached this request's limit. Send this address again in another request. | Send this recipient again in another request. |
text_overflow | The custom text does not fit its text box on your design. | Shorten this recipient's custom text, or make its text box larger in the designer, then send it again. |
variable_image_missing | Variable image "{title}" is not usable, and this request set require_variable_images. | Send a usable image link for the field the detail names, or leave require_variable_images off to mail with the default image. |
unsupported_characters | {font name} can't print {characters} in the "{box name}" text box. | Send this recipient's values without the characters the detail names, or change the text box's font in the designer, then send it again. |
removed_by_sender | Removed from this campaign by the sender. | Nothing to do: you removed this recipient from the campaign. |
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 to do |
|---|---|
variable_image_fallback | The card was mailed with the design's default image. Send a usable image link for the field next time, or set require_variable_images to skip recipients instead. |
variable_image_blank | The card was mailed with the image area blank, because the design has no default image. Send a usable image link, add a default image, or set require_variable_images. |
qr_fallback_link | The QR code sends this recipient to the fallback link because the fields it needs were empty. Send those fields to give each recipient their own link. |
text_overlap | The card was mailed with this recipient's text overlapping another element. Shorten the value or make room for it in the designer. |
text_unmeasurable | The card was mailed in full, but we couldn't check the text box the detail names for fit or overlaps. Check it in a proof in Postaged. |
text_curve_too_long | The card was mailed in full with this recipient's text too long for its curve or path, even at its smallest size. Shorten the value, or reduce the curve or lengthen the path in the designer. |
return_address_reason values
| value | What to do |
|---|---|
missing_token_value | Send every field the return address uses, so it can be filled in for this recipient. |
unverified_address | Check the return-address fields you send: the address they make could not be verified. |
verification_limit_reached | Return addresses were not checked because this month's verification limit was reached. The card used the fallback return address; nothing is needed from you. |
Integrations push API
Errors use the body { "error": { "code", "message", "details" } }; details appears only on some codes. A {name} in a message is filled with the value for your request. See Errors for an example of each.
| Status | code | message | What to do |
|---|---|---|---|
| 401 | invalid_key | That API key is not valid for this integration. | Send this integration's key (it starts with pk_int_) in the Authorization: Bearer header. A revoked key, another kind of key, or a key for a removed integration gets this error. |
| 403 | integration_unavailable | This integration is not accepting data. Contact support if you think that is wrong. | The integration is paused or disconnected in Postaged. Ask the owner to resume it, then send the request again. |
| 429 | rate_limited | Too many requests. Slow down and retry. | Wait the number of seconds in the Retry-After header, then send the request again. |
| 400 | invalid_json | The request body must be JSON. | Send the body as JSON with a Content-Type of application/json. |
| 400 | invalid_request | Push records: Send { records: [...] }. Declare the schema: Send { fields: [...] } describing your columns. Declare the schema, a field with no key: Every field needs a non-empty key. | Send the body in the shape the message names, then send the request again. |
| 413 | too_many_records | You sent {received} records. Send at most {limit} per batch. | Split the batch so each one holds at most the records in details.limit. |
| 413 | payload_too_large | A batch may be at most {limit} bytes. | Split the batch so each body stays at or under the bytes in details.limit. |
| 400 | missing_idempotency_key | Push records: Send an Idempotency-Key header so a retried batch is not applied twice. Begin a sync: Send an Idempotency-Key header so a retry cannot open a second sync. Complete a sync: Send an Idempotency-Key header so a retry cannot complete the sync twice. | Send an Idempotency-Key header with a new value for each request, and the same value when you retry that request. |
| 409 | idempotency_key_reused | Push records: That Idempotency-Key was used for a different batch. Use a new key. Begin a sync and Complete a sync: That Idempotency-Key was used for a different request. Use a new key. | Use a new Idempotency-Key for this request. Reuse a key only to retry the same request with the same body. |
| 409 | in_progress | Push records: That batch is still being processed. Begin a sync and Complete a sync: That request is still being processed. | The first attempt is still running. Wait a few seconds, then retry with the same Idempotency-Key to get its response. |
| 409 | no_open_sync | Push records: Begin a sync before pushing records, so a full sync knows what it saw. Complete a sync: There is no open sync to complete. | Begin a sync with POST /api/integrations/push/v1/sync first. A sync left open expires after 24 hours. |
| 409 | sync_in_progress | A sync is already open for this integration. Complete it before beginning another. | Complete the open sync named in details.syncId, or wait for it to expire, then begin a new one. |
| 400 | too_many_fields | You declared {received} fields. The limit is {limit}. | Declare at most the fields in details.limit, counting the fields already declared. |
| 409 | field_locked | These fields have a type set in Postaged and cannot be changed from here. | The owner set these fields' types in Postaged. Declare them with the types they already have, or ask the owner to change them. |
| 404 | sync_not_found | There is no sync with that id on this integration. | Use a syncId this integration's sync calls returned, or leave it out to read the latest sync. |
| 400 | invalid_sync_id | syncId must be the id a sync call returned. | Fix the query parameter the message names: syncId is a sync id from a sync call, type is rejection or warning, after is the previous page's nextAfter, and limit is a whole number in range. |
| 400 | invalid_type | type must be "rejection" or "warning". | Fix the query parameter the message names: syncId is a sync id from a sync call, type is rejection or warning, after is the previous page's nextAfter, and limit is a whole number in range. |
| 400 | invalid_cursor | after must be the nextAfter value of the previous page. | Fix the query parameter the message names: syncId is a sync id from a sync call, type is rejection or warning, after is the previous page's nextAfter, and limit is a whole number in range. |
| 400 | invalid_limit | limit must be a whole number from 1 to 1000. | Fix the query parameter the message names: syncId is a sync id from a sync call, type is rejection or warning, after is the previous page's nextAfter, and limit is a whole number in range. |
Rejection codes
One rejection per record a batch refused. The rest of the batch is still applied.
| code | What to do |
|---|---|
missing_external_id | Send an external_id on every record: your system's stable id for the contact. |
invalid_external_id | Shorten the external_id to 128 characters or fewer. |
unknown_field | Declare the field with POST /api/integrations/push/v1/schema before you push it, or stop sending it. |
invalid_type | Send a value that fits the field's declared type. This rejects the record only for a field mapped to a contact column (it has a semantic); any other field drops just the value, as a warning. |
invalid_email | Send a complete email address, or leave the field empty. |
duplicate_in_batch | Send each contact once per batch. Merge the duplicates under one external_id. |
record_too_large | Send less data in this record. A record may be at most 64 KB. |
Value warnings
Values dropped from records that were still accepted, one warning per field and code, with a count and up to 5 example externalIds (index and externalId name the first). Every dropped value is listed one per record by GET /sync/rejections with type=warning.
| code | What to do |
|---|---|
invalid_type | The record was accepted without this value. Send values that fit the field's declared type, or declare the field as text. |
Integrations MCP server
JSON-RPC errors, every one with HTTP 200. A {name} is filled with the tool or method you called.
| Code | HTTP status | message | What to do |
|---|---|---|---|
-32700 | 200 | The request body must be JSON-RPC. | Send the request body as one JSON-RPC 2.0 message in JSON. |
-32600 | 200 | A JSON-RPC method is required. | The message has no method, or the body is over the size limit. Fix the request, then send it again. |
-32600 | 200 | That request is too large. | The message has no method, or the body is over the size limit. Fix the request, then send it again. |
-32601 | 200 | Unknown tool: {name} | Use a method and a tool name the server lists. Call tools/list for the tool names. |
-32601 | 200 | Unknown method: {method} | Use a method and a tool name the server lists. Call tools/list for the tool names. |
-32603 | 200 | That call could not be completed. | Something went wrong on our side and nothing was changed. Send the call again shortly; if it keeps failing, contact support. |
HTTP errors
Authentication, availability, and rate-limit failures, and a GET, use the push API's error body.
| Status | code | message | What to do |
|---|---|---|---|
| 401 | invalid_key | That API key is not valid for this integration. | Send this integration's key (it starts with pk_int_) in the Authorization: Bearer header. A revoked key, another kind of key, or a key for a removed integration gets this error. |
| 403 | integration_unavailable | This integration is not accepting data. Contact support if you think that is wrong. | The integration is paused or disconnected in Postaged. Ask the owner to resume it, then send the request again. |
| 429 | rate_limited | Too many requests. Slow down and retry. | Wait the number of seconds in the Retry-After header, then send the request again. |
| 405 | use_post | This MCP endpoint speaks JSON-RPC over POST. | Send JSON-RPC messages to the Integrations MCP endpoint with POST. |
Tool failures
A tool that cannot do what it was asked returns a result, not a JSON-RPC error. Its text carries error and message.
| Tool | error | message |
|---|---|---|
begin_sync | sync_in_progress | A sync is already open. Complete it before beginning another. |
push_records | no_open_sync | Call begin_sync first. |
push_records | too_many_records | Send at most {limit} records per batch. |
complete_sync | no_open_sync | There is no sync to complete. |
get_rejections | sync_not_found | There is no sync with that id on this integration. |
get_rejections | invalid_sync_id | syncId must be the id a sync call returned. |
get_rejections | invalid_type | type must be "rejection" or "warning". |
get_rejections | invalid_cursor | after must be the nextAfter value of the previous page. |
get_rejections | invalid_limit | limit must be a whole number from 1 to 1000. |