Guides

Errors

How each API reports a failed request, with an example of every error it can return.

Each API reports errors in its own envelope. The envelopes differ, so each is described as it is. Branch on the status and the code, never on the message text. Every code, its message, and what to do about it are in the Error reference.

Dynamic Audience API

A failed request returns a top-level { "code", "message" } body with its HTTP status. Each status means the whole request failed and nothing was mailed or charged. A request that succeeded returns 200, and each recipient that was not mailed carries its own reason instead (Reasons a recipient was not mailed).

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.

The examples show every error this API returns. See the Error reference for what to do about each.

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

Integrations push API

A failed request returns { "error": { "code", "message", "details" } } with its HTTP status. details appears only on some codes, with machine-readable detail like the limit you went over. A batch that was accepted returns 200 and lists one rejection per refused record and the dropped values grouped by field instead.

A body over the hosting platform's cap can get the platform's own 413, without this body.

The examples show every error the push API returns. See the Error reference for what to do about each.

invalid_key401
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="Postaged"

{
  "error": {
    "code": "invalid_key",
    "message": "That API key is not valid for this integration."
  }
}
integration_unavailable403
{
  "error": {
    "code": "integration_unavailable",
    "message": "This integration is not accepting data. Contact support if you think that is wrong."
  }
}
rate_limited429
HTTP/1.1 429 Too Many Requests
Retry-After: 60

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Slow down and retry."
  }
}
invalid_json400
{
  "error": {
    "code": "invalid_json",
    "message": "The request body must be JSON."
  }
}
invalid_request400
{
  "error": {
    "code": "invalid_request",
    "message": "Send { fields: [...] } describing your columns."
  }
}
too_many_fields400
{
  "error": {
    "code": "too_many_fields",
    "message": "You declared 1001 fields. The limit is 1000.",
    "details": { "limit": 1000, "received": 1001 }
  }
}
field_locked409
{
  "error": {
    "code": "field_locked",
    "message": "These fields have a type set in Postaged and cannot be changed from here.",
    "details": {
      "fields": ["deal_value"]
    }
  }
}
missing_idempotency_key400
{
  "error": {
    "code": "missing_idempotency_key",
    "message": "Send an Idempotency-Key header so a retry cannot open a second sync."
  }
}
sync_in_progress409
{
  "error": {
    "code": "sync_in_progress",
    "message": "A sync is already open for this integration. Complete it before beginning another.",
    "details": {
      "syncId": "0b6f3c3e-8d2a-4f7e-9a51-2c4d8e6f1a20",
      "mode": "full",
      "expiresAt": "2026-10-04T14:05:00.000Z"
    }
  }
}
idempotency_key_reused409
{
  "error": {
    "code": "idempotency_key_reused",
    "message": "That Idempotency-Key was used for a different request. Use a new key."
  }
}
in_progress409
{
  "error": {
    "code": "in_progress",
    "message": "That request is still being processed."
  }
}
no_open_sync409
{
  "error": {
    "code": "no_open_sync",
    "message": "There is no open sync to complete."
  }
}
too_many_records413
{
  "error": {
    "code": "too_many_records",
    "message": "You sent 1001 records. Send at most 1000 per batch.",
    "details": { "limit": 1000, "received": 1001 }
  }
}
payload_too_large413
{
  "error": {
    "code": "payload_too_large",
    "message": "A batch may be at most 4456448 bytes.",
    "details": { "limit": 4456448 }
  }
}
invalid_sync_id400
{
  "error": {
    "code": "invalid_sync_id",
    "message": "syncId must be the id a sync call returned."
  }
}
invalid_type400
{
  "error": {
    "code": "invalid_type",
    "message": "type must be \"rejection\" or \"warning\"."
  }
}
invalid_cursor400
{
  "error": {
    "code": "invalid_cursor",
    "message": "after must be the nextAfter value of the previous page."
  }
}
invalid_limit400
{
  "error": {
    "code": "invalid_limit",
    "message": "limit must be a whole number from 1 to 1000."
  }
}
sync_not_found404
{
  "error": {
    "code": "sync_not_found",
    "message": "There is no sync with that id on this integration."
  }
}

Integrations MCP server

  • JSON-RPC errors come back with HTTP 200, as { "jsonrpc": "2.0", "id", "error": { "code", "message" } }.
  • Authentication, availability, and rate-limit failures use the push API's envelope with HTTP 401, 403, or 429. A GET to the endpoint gets 405 use_post.
  • Tool-level failures come back as a tool result whose text carries error and message, so your agent can read them and recover.

See the Error reference for every code.

invalid_key401
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="Postaged"

{
  "error": {
    "code": "invalid_key",
    "message": "That API key is not valid for this integration."
  }
}
integration_unavailable403
HTTP/1.1 403 Forbidden

{
  "error": {
    "code": "integration_unavailable",
    "message": "This integration is not accepting data. Contact support if you think that is wrong."
  }
}
rate_limited429
HTTP/1.1 429 Too Many Requests
Retry-After: 60

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Slow down and retry."
  }
}
use_post405
HTTP/1.1 405 Method Not Allowed

{
  "error": {
    "code": "use_post",
    "message": "This MCP endpoint speaks JSON-RPC over POST."
  }
}
-32700200 OK
{
  "jsonrpc": "2.0",
  "id": null,
  "error": { "code": -32700, "message": "The request body must be JSON-RPC." }
}
-32600200 OK
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": { "code": -32600, "message": "A JSON-RPC method is required." }
}
-32600200 OK
{
  "jsonrpc": "2.0",
  "id": null,
  "error": { "code": -32600, "message": "That request is too large." }
}
-32601200 OK
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": { "code": -32601, "message": "Unknown tool: send_mail" }
}
-32601200 OK
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": { "code": -32601, "message": "Unknown method: resources/subscribe" }
}
-32603200 OK
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": { "code": -32603, "message": "That call could not be completed." }
}
begin_sync: sync_in_progress200 OK
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"error\": \"sync_in_progress\",\n  \"message\": \"A sync is already open. Complete it before beginning another.\"\n}"
      }
    ]
  }
}
push_records: no_open_sync200 OK
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"error\": \"no_open_sync\",\n  \"message\": \"Call begin_sync first.\"\n}"
      }
    ]
  }
}
push_records: too_many_records200 OK
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"error\": \"too_many_records\",\n  \"message\": \"Send at most 1000 records per batch.\"\n}"
      }
    ]
  }
}
complete_sync: no_open_sync200 OK
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"error\": \"no_open_sync\",\n  \"message\": \"There is no sync to complete.\"\n}"
      }
    ]
  }
}
get_rejections: sync_not_found200 OK
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"error\": \"sync_not_found\",\n  \"message\": \"There is no sync with that id on this integration.\"\n}"
      }
    ]
  }
}
get_rejections: invalid_sync_id200 OK
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"error\": \"invalid_sync_id\",\n  \"message\": \"syncId must be the id a sync call returned.\"\n}"
      }
    ]
  }
}
get_rejections: invalid_type200 OK
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"error\": \"invalid_type\",\n  \"message\": \"type must be \\\"rejection\\\" or \\\"warning\\\".\"\n}"
      }
    ]
  }
}
get_rejections: invalid_cursor200 OK
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"error\": \"invalid_cursor\",\n  \"message\": \"after must be the nextAfter value of the previous page.\"\n}"
      }
    ]
  }
}
get_rejections: invalid_limit200 OK
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"error\": \"invalid_limit\",\n  \"message\": \"limit must be a whole number from 1 to 1000.\"\n}"
      }
    ]
  }
}