{"openapi":"3.1.0","info":{"title":"Postaged API","version":"1","summary":"Send recipients to dynamic campaigns, sync a custom CRM, and receive QR scan webhooks.","description":"Every request goes to https://www.postaged.com. Changes to /v1 only add things: new response fields, new error and reason codes, and new optional parameters. Build your integration to ignore fields and codes it doesn't recognize."},"servers":[{"url":"https://www.postaged.com"}],"tags":[{"name":"Dynamic Audience API","description":"Send recipients to a dynamic campaign. A live request mails and charges in the same request."},{"name":"Integrations push API","description":"Push contacts from a custom CRM into a Postaged integration."}],"x-postaged-limits":{"dynamicAudience":{"recipientsPerRequest":5000,"centersPerRequest":250,"homesPerRequest":10000,"bodyBytes":4456448,"responseBytes":4194304,"requestsPerMinutePerKey":60,"replayWindowMinutes":15,"duplicateWindowDays":30,"newColumnsPerRequest":10,"apiColumnsPerTeam":50,"customValueCharacters":600,"retryAfterSeconds":60},"pushApi":{"recordsPerBatch":1000,"bytesPerBatch":4456448,"recordBytes":65536,"warningExamplesPerField":5,"declaredFields":1000,"externalIdCharacters":128,"idempotencyKeyCharacters":200,"syncExpiryHours":24,"rejectionsPageDefault":100,"rejectionsPageMax":1000,"requestsPerMinutePerIp":120,"retryAfterSeconds":60}},"paths":{"/api/v1/recipients":{"post":{"operationId":"sendRecipients","tags":["Dynamic Audience API"],"summary":"Send recipients","description":"Sends up to 5,000 recipients (250 centers on a Neighborhood Mail campaign) to a dynamic campaign and returns a final result for each one. A live request mails every recipient it accepts and charges your team in the same request. Send the key in the Authorization: Bearer header (preferred), an X-API-Key header, or an api_key field in the JSON body for senders that cannot set headers. Never put a key in the URL. 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. A response that would be larger than 4 MB lists only the recipients that were not mailed or were mailed with a warning, with results_truncated: true. A body over the platform's own cap can get the platform's 413 without the { code, message } body.","security":[{"fullAccessKey":[]},{"fullAccessKeyHeader":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendRecipientsRequest"},"examples":{"twoRecipients":{"summary":"Two recipients","value":{"campaign_id":"PA-4H2K9C","recipients":[{"reference":"crm-1042","first_name":"Dana","last_name":"Whitfield","address_1":"18 Rowan St","city":"Hartford","state":"CT","zip":"06105-2214","Deal_Value":"1500"},{"reference":"crm-1043","first_name":"Sam","last_name":"Ortiz","address_1":"25 Gale Road","city":"Bloomfield","state":"CT","zip":"06002"}]}}}}}},"responses":{"200":{"description":"The payload was processed. Every recipient has a final result: mailed, or not mailed with a reason. A request where every recipient was not mailed still returns 200.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestResponse"},"examples":{"mixed":{"summary":"One mailed, one already mailed recently","value":{"summary":{"received":2,"mailed":1,"notMailed":1},"results":[{"reference":"crm-1042","address":"18 Rowan St, Hartford, CT 06105-2214","status":"mailed","detail":"Queued to send.","product_type":"handwritten_postcard","postage":"first_class","mail_by_date":"2026-10-07"},{"reference":"crm-1043","address":"25 Gale Road, Bloomfield, CT 06002","status":"not_mailed","reason":"duplicate_recent","detail":"We already mailed this address for this campaign recently."}]}},"withWarnings":{"summary":"Mailed with a fallback image","value":{"summary":{"received":1,"mailed":1,"notMailed":0},"results":[{"reference":"crm-1042","address":"18 Rowan St, Hartford, CT 06105-2214","status":"mailed","detail":"Queued to send.","product_type":"printed_postcard","postage":"standard","mail_by_date":"2026-10-07","warnings":[{"code":"variable_image_fallback","field":"Agent_Photo","detail":"Variable image \"Agent photo\": the default image will print (no image for this recipient)."}],"return_address_fallback":true,"return_address_reason":"missing_token_value"}]}},"truncated":{"summary":"A large request whose full response would pass 4 MB","value":{"summary":{"received":5000,"mailed":4999,"notMailed":1},"results":[{"reference":"crm-2210","address":"9 Elm St, Hartford, CT 06106-1830","status":"not_mailed","reason":"duplicate_recent","detail":"We already mailed this address for this campaign recently."}],"results_truncated":true}}}}}},"401":{"description":"The key was not accepted. Only a Full access key can send recipients.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestError401"},"examples":{"invalid_api_key":{"value":{"code":"invalid_api_key","message":"We could not verify that API key. Check the key and try again."}}}}}},"404":{"description":"No campaign with that campaign ID on this key's team.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestError404"},"examples":{"campaign_not_found":{"value":{"code":"campaign_not_found","message":"We could not find a campaign with that campaign ID."}}}}}},"409":{"description":"The campaign can't take recipients right now. Nothing was mailed or charged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestError409"},"examples":{"campaign_not_active":{"value":{"code":"campaign_not_active","message":"That campaign is not active, so we did not mail anyone."}},"design_cannot_render":{"value":{"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_is_ai_draft":{"summary":"A design is still an AI draft","value":{"code":"design_cannot_render","message":"Open and save this AI draft in the designer before using it in a campaign."}}}}}},"413":{"description":"Too many recipients, or a body over the body limit. A body over the platform's own cap can get the platform's 413 without this body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestError413"},"examples":{"payload_too_large":{"value":{"code":"payload_too_large","message":"That request was too large. Send up to 5,000 recipients per request."}}}}}},"422":{"description":"The body could not be read as a request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestError422"},"examples":{"invalid_payload":{"value":{"code":"invalid_payload","message":"We could not read that request. Check the format and try again."}}}}}},"429":{"description":"Over 60 requests a minute for this key or from this address.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestError429"},"examples":{"rate_limited":{"value":{"code":"rate_limited","message":"Too many requests. Wait a moment and try again."}}}}}},"500":{"description":"Something failed on our side. Nothing was mailed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestError500"},"examples":{"internal_error":{"value":{"code":"internal_error","message":"Something went wrong on our end. Nothing was mailed. Try again shortly."}}}}}},"503":{"description":"Address checks were unavailable, so nothing was mailed or charged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestError503"},"examples":{"validation_unavailable":{"value":{"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."}}}}}}}}},"/api/integrations/push/v1":{"get":{"operationId":"inspectIntegration","tags":["Integrations push API"],"summary":"Inspect the integration","description":"Who the key belongs to, the integration's state, any open sync, and the limits.","security":[{"integrationKey":[]}],"responses":{"200":{"description":"The integration.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InspectIntegrationResponse"},"examples":{"setupNeeded":{"value":{"integration":{"id":"int_ABCD1234","name":"Dandy CRM","status":"setup_needed","setupComplete":false},"openSync":null,"limits":{"recordsPerBatch":1000,"bytesPerBatch":4456448,"declaredFields":1000,"syncExpiryHours":24},"nextStep":"Push a first sync, then finish setup in Postaged to choose which fields to keep."}}}}}},"401":{"$ref":"#/components/responses/PushInvalidKey"},"403":{"$ref":"#/components/responses/PushIntegrationUnavailable"},"429":{"$ref":"#/components/responses/PushRateLimited"}}}},"/api/integrations/push/v1/schema":{"get":{"operationId":"readSchema","tags":["Integrations push API"],"summary":"Read the schema","description":"The fields declared for this integration.","security":[{"integrationKey":[]}],"responses":{"200":{"description":"The declared fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadSchemaResponse"},"examples":{"twoFields":{"value":{"fields":[{"key":"email","label":"Email","type":"text","semantic":"email"},{"key":"deal_value","label":"Deal value","type":"number","semantic":null}]}}}}}},"401":{"$ref":"#/components/responses/PushInvalidKey"},"403":{"$ref":"#/components/responses/PushIntegrationUnavailable"},"429":{"$ref":"#/components/responses/PushRateLimited"}}},"post":{"operationId":"declareSchema","tags":["Integrations push API"],"summary":"Declare the schema","description":"Declares the fields you will push. A declaration is a proposal: a field whose type the owner already set in Postaged keeps it, and the call fails with field_locked naming those fields.","security":[{"integrationKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeclareSchemaRequest"},"examples":{"twoFields":{"value":{"fields":[{"key":"email","label":"Email","type":"text","semantic":"email"},{"key":"deal_value","label":"Deal value","type":"number"}]}}}}}},"responses":{"200":{"description":"The declaration was applied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeclareSchemaResponse"},"examples":{"applied":{"value":{"ok":true,"added":1,"noLongerSent":[]}}}}}},"400":{"description":"The body could not be read, or declares too many fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushErrorDeclareSchema400"},"examples":{"invalid_json":{"$ref":"#/components/examples/InvalidJson"},"invalid_request":{"value":{"error":{"code":"invalid_request","message":"Send { fields: [...] } describing your columns."}}},"too_many_fields":{"value":{"error":{"code":"too_many_fields","message":"You declared 1001 fields. The limit is 1000.","details":{"limit":1000,"received":1001}}}}}}}},"401":{"$ref":"#/components/responses/PushInvalidKey"},"403":{"$ref":"#/components/responses/PushIntegrationUnavailable"},"409":{"description":"The owner already set these fields' types in Postaged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushErrorDeclareSchema409"},"examples":{"field_locked":{"value":{"error":{"code":"field_locked","message":"These fields have a type set in Postaged and cannot be changed from here.","details":{"fields":["deal_value"]}}}}}}}},"429":{"$ref":"#/components/responses/PushRateLimited"}}}},"/api/integrations/push/v1/sync":{"get":{"operationId":"readOpenSync","tags":["Integrations push API"],"summary":"Read the open sync","description":"The open sync's progress, or null when none is open.","security":[{"integrationKey":[]}],"responses":{"200":{"description":"The open sync, or null.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadOpenSyncResponse"},"examples":{"open":{"value":{"sync":{"id":"0b6f3c3e-8d2a-4f7e-9a51-2c4d8e6f1a20","mode":"full","status":"open","expiresAt":"2026-10-04T14:05:00.000Z","batchesReceived":2,"recordsUpserted":1998,"recordsDeleted":0,"recordsRejected":2}}},"none":{"value":{"sync":null}}}}}},"401":{"$ref":"#/components/responses/PushInvalidKey"},"403":{"$ref":"#/components/responses/PushIntegrationUnavailable"},"429":{"$ref":"#/components/responses/PushRateLimited"}}},"post":{"operationId":"beginSync","tags":["Integrations push API"],"summary":"Begin a sync","description":"Opens a sync. A full sync marks every contact it did not receive as removed when it completes; a delta sync removes only what you delete. One sync can be open at a time, and an unfinished sync expires after 24 hours.","security":[{"integrationKey":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyRequired"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BeginSyncRequest"},"examples":{"full":{"value":{"mode":"full"}}}}}},"responses":{"200":{"description":"The sync is open.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BeginSyncResponse"},"examples":{"opened":{"value":{"syncId":"0b6f3c3e-8d2a-4f7e-9a51-2c4d8e6f1a20","mode":"full","expiresAt":"2026-10-04T14:05:00.000Z"}}}}}},"400":{"description":"The body could not be read, or the Idempotency-Key header is missing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushErrorSync400"},"examples":{"invalid_json":{"$ref":"#/components/examples/InvalidJson"},"missing_idempotency_key":{"value":{"error":{"code":"missing_idempotency_key","message":"Send an Idempotency-Key header so a retry cannot open a second sync."}}}}}}},"401":{"$ref":"#/components/responses/PushInvalidKey"},"403":{"$ref":"#/components/responses/PushIntegrationUnavailable"},"409":{"description":"Another sync is open, or the Idempotency-Key was used for another request or is still running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushErrorBeginSync409"},"examples":{"sync_in_progress":{"value":{"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_reused":{"$ref":"#/components/examples/IdempotencyKeyReusedRequest"},"in_progress":{"$ref":"#/components/examples/InProgressRequest"}}}}},"429":{"$ref":"#/components/responses/PushRateLimited"}}},"patch":{"operationId":"completeSync","tags":["Integrations push API"],"summary":"Complete a sync","description":"Closes the open sync. A full sync then marks every contact it did not receive as removed. Always complete a sync, even when some records were rejected.","security":[{"integrationKey":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyRequired"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompleteSyncRequest"},"examples":{"named":{"value":{"syncId":"0b6f3c3e-8d2a-4f7e-9a51-2c4d8e6f1a20"}}}}}},"responses":{"200":{"description":"The sync is complete.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompleteSyncResponse"},"examples":{"completed":{"value":{"ok":true,"syncId":"0b6f3c3e-8d2a-4f7e-9a51-2c4d8e6f1a20","mode":"full","recordsUpserted":1998,"recordsDeleted":0,"recordsRejected":2,"markedRemoved":14}}}}}},"400":{"description":"The body could not be read, or the Idempotency-Key header is missing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushErrorSync400"},"examples":{"invalid_json":{"$ref":"#/components/examples/InvalidJson"},"missing_idempotency_key":{"value":{"error":{"code":"missing_idempotency_key","message":"Send an Idempotency-Key header so a retry cannot complete the sync twice."}}}}}}},"401":{"$ref":"#/components/responses/PushInvalidKey"},"403":{"$ref":"#/components/responses/PushIntegrationUnavailable"},"409":{"description":"No sync is open, or the Idempotency-Key was used for another request or is still running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushErrorCompleteSync409"},"examples":{"no_open_sync":{"value":{"error":{"code":"no_open_sync","message":"There is no open sync to complete."}}},"idempotency_key_reused":{"$ref":"#/components/examples/IdempotencyKeyReusedRequest"},"in_progress":{"$ref":"#/components/examples/InProgressRequest"}}}}},"429":{"$ref":"#/components/responses/PushRateLimited"}}}},"/api/integrations/push/v1/records":{"post":{"operationId":"pushRecords","tags":["Integrations push API"],"summary":"Push records","description":"Pushes a batch into the open sync. One bad record never stops the others: the response counts what was accepted and lists one rejection per refused record. Values dropped from accepted records are grouped by field and code, each group with a count and up to 5 example record ids; every dropped value is listed one per record by List sync rejections with type=warning. With ?validate=1 the batch is checked against the same rules and nothing is written; a dry run needs no open sync and no Idempotency-Key. A body over the platform's own cap can get the platform's 413 without the documented error body.","security":[{"integrationKey":[]}],"parameters":[{"name":"validate","in":"query","required":false,"description":"Send 1 to check the batch and write nothing.","schema":{"type":"string","enum":["1"]}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Required on a push; not read on a ?validate=1 dry run. A new value per request, the same value on a retry of that request.","schema":{"$ref":"#/components/schemas/IdempotencyKey"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushRecordsRequest"},"examples":{"twoRecords":{"value":{"records":[{"external_id":"crm-1042","fields":{"email":"dana@example.com","deal_value":"1500"}},{"external_id":"crm-0977","op":"delete"}]}}}}}},"responses":{"200":{"description":"The batch was checked (and, without ?validate=1, written).","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/PushRecordsResponse"},{"$ref":"#/components/schemas/ValidateRecordsResponse"}]},"examples":{"pushed":{"summary":"A push with one rejection and one dropped value","value":{"accepted":1,"deleted":1,"rejected":1,"rejections":[{"index":2,"externalId":null,"code":"missing_external_id","message":"Every record needs an external_id: it is how this record is recognized on the next sync."}],"warnings":[{"index":0,"externalId":"crm-1042","code":"invalid_type","field":"deal_value","message":"\"deal_value\" is declared as number and this value is not one. The record was accepted without it.","count":1,"externalIds":["crm-1042"]}]}},"validated":{"summary":"A ?validate=1 dry run","value":{"validated":true,"accepted":2,"rejected":0,"rejections":[],"warnings":[]}}}}}},"400":{"description":"The body could not be read, or the Idempotency-Key header is missing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushErrorRecords400"},"examples":{"invalid_json":{"$ref":"#/components/examples/InvalidJson"},"invalid_request":{"value":{"error":{"code":"invalid_request","message":"Send { records: [...] }."}}},"missing_idempotency_key":{"value":{"error":{"code":"missing_idempotency_key","message":"Send an Idempotency-Key header so a retried batch is not applied twice."}}}}}}},"401":{"$ref":"#/components/responses/PushInvalidKey"},"403":{"$ref":"#/components/responses/PushIntegrationUnavailable"},"409":{"description":"No sync is open, or the Idempotency-Key was used for another batch or is still running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushErrorRecords409"},"examples":{"no_open_sync":{"value":{"error":{"code":"no_open_sync","message":"Begin a sync before pushing records, so a full sync knows what it saw."}}},"idempotency_key_reused":{"value":{"error":{"code":"idempotency_key_reused","message":"That Idempotency-Key was used for a different batch. Use a new key."}}},"in_progress":{"value":{"error":{"code":"in_progress","message":"That batch is still being processed."}}}}}}},"413":{"description":"Too many records, or a body over the batch limit. A body over the platform's own cap can get the platform's 413 without this body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushErrorRecords413"},"examples":{"too_many_records":{"value":{"error":{"code":"too_many_records","message":"You sent 1001 records. Send at most 1000 per batch.","details":{"limit":1000,"received":1001}}}},"payload_too_large":{"value":{"error":{"code":"payload_too_large","message":"A batch may be at most 4456448 bytes.","details":{"limit":4456448}}}}}}}},"429":{"$ref":"#/components/responses/PushRateLimited"}}}},"/api/integrations/push/v1/sync/rejections":{"get":{"operationId":"listSyncRejections","tags":["Integrations push API"],"summary":"List sync rejections","description":"The records a sync refused, or with type=warning the values it dropped from accepted records, one page at a time. Pass nextAfter back as after until it is null.","security":[{"integrationKey":[]}],"parameters":[{"name":"syncId","in":"query","required":false,"description":"The sync to read. Leave it out to read the latest sync.","schema":{"type":"string","format":"uuid"}},{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["rejection","warning"],"default":"rejection"}},{"name":"after","in":"query","required":false,"description":"The nextAfter value of the previous page.","schema":{"type":"string","pattern":"^[0-9]{1,15}$"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000,"default":100}}],"responses":{"200":{"description":"One page of issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSyncRejectionsResponse"},"examples":{"page":{"value":{"syncId":"0b6f3c3e-8d2a-4f7e-9a51-2c4d8e6f1a20","type":"rejection","items":[{"id":7,"batch":1,"index":3,"externalId":"crm-1077","code":"invalid_email","field":"email","message":"\"email\" does not look like an email address."}],"nextAfter":null}}}}}},"400":{"description":"A query parameter could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushErrorRejections400"},"examples":{"invalid_sync_id":{"value":{"error":{"code":"invalid_sync_id","message":"syncId must be the id a sync call returned."}}},"invalid_type":{"value":{"error":{"code":"invalid_type","message":"type must be \"rejection\" or \"warning\"."}}},"invalid_cursor":{"value":{"error":{"code":"invalid_cursor","message":"after must be the nextAfter value of the previous page."}}},"invalid_limit":{"value":{"error":{"code":"invalid_limit","message":"limit must be a whole number from 1 to 1000."}}}}}}},"401":{"$ref":"#/components/responses/PushInvalidKey"},"403":{"$ref":"#/components/responses/PushIntegrationUnavailable"},"404":{"description":"No sync with that id on this integration.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushErrorRejections404"},"examples":{"sync_not_found":{"value":{"error":{"code":"sync_not_found","message":"There is no sync with that id on this integration."}}}}}}},"429":{"$ref":"#/components/responses/PushRateLimited"}}}}},"webhooks":{"qr.scanned":{"post":{"operationId":"qrScanned","summary":"A QR code was scanned","description":"Sent to every active endpoint for each recorded scan of a QR code on your mail, including repeat scans from the same device. Webhooks are not signed: Postaged doesn't sign webhook deliveries, so a request to your endpoint can't prove it came from us. Use an endpoint URL with a long, random path that only you and Postaged know, and treat every payload as untrusted input: check its values before you act on them. There is no signature header. Respond with any 2xx quickly and process the event afterwards; deduplicate by webhook-id, which repeats only on a retry or when you choose Redeliver. A test event carries a top-level \"test\": true.","parameters":[{"name":"webhook-id","in":"header","required":true,"description":"The event id, stable across retries and redeliveries. Use it to deduplicate.","schema":{"type":"string"}},{"name":"webhook-timestamp","in":"header","required":true,"description":"When this delivery attempt was sent, in Unix seconds.","schema":{"type":"string","pattern":"^[0-9]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QrScannedEvent"},"examples":{"scan":{"value":{"event":"qr.scanned","id":"evt_8c1f2b9d4a","scanned_at":"2026-08-09T18:41:22Z","campaign":{"id":"PA-4H2K9C","name":"Spring open house"},"recipient":{"first_name":"Dana","last_name":"Whitfield","email":"dana@example.com","phone":null,"address_1":"18 Rowan St","address_2":null,"city":"Hartford","state":"CT","zip":"06105-2214"},"location":{"city":"Hartford","region":"CT","latitude":41.76,"longitude":-72.69},"destination":"https://offers.example.com/SPRING24"}},"test":{"value":{"event":"qr.scanned","id":"evt_test_5d0a9c3e7f21b846","test":true,"scanned_at":"2026-08-09T18:41:22Z","campaign":{"id":"PA-EXAMPLE","name":"Example campaign"},"recipient":{"first_name":"Dana","last_name":"Whitfield","email":"dana@example.com","phone":null,"address_1":"18 Rowan St","address_2":null,"city":"Hartford","state":"CT","zip":"06105-2214"},"location":{"city":"Hartford","region":"CT","latitude":41.76,"longitude":-72.69},"destination":"https://example.com/offers"}}}}}},"responses":{"2XX":{"description":"Received. Any 2xx counts as delivered."}}}}},"components":{"securitySchemes":{"fullAccessKey":{"type":"http","scheme":"bearer","description":"A Full access key (it starts with pk_live_), created in Settings > API & Webhooks."},"fullAccessKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key","description":"The same Full access key, for senders that can set a header but not Authorization."},"integrationKey":{"type":"http","scheme":"bearer","description":"An integration key (it starts with pk_int_), created on the integration's Developer page."}},"headers":{"RetryAfter":{"description":"Seconds to wait before sending the request again.","required":true,"schema":{"type":"integer","const":60}},"BearerChallenge":{"description":"The authentication challenge.","required":true,"schema":{"type":"string","const":"Bearer realm=\"Postaged\""}}},"parameters":{"IdempotencyKeyRequired":{"name":"Idempotency-Key","in":"header","required":true,"description":"A new value per request, and the same value when you retry that request.","schema":{"$ref":"#/components/schemas/IdempotencyKey"}}},"responses":{"PushInvalidKey":{"description":"The key was not accepted. Only this integration's key (pk_int_) works here.","headers":{"WWW-Authenticate":{"$ref":"#/components/headers/BearerChallenge"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushError401"},"examples":{"invalid_key":{"value":{"error":{"code":"invalid_key","message":"That API key is not valid for this integration."}}}}}}},"PushIntegrationUnavailable":{"description":"The integration is paused or disconnected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushError403"},"examples":{"integration_unavailable":{"value":{"error":{"code":"integration_unavailable","message":"This integration is not accepting data. Contact support if you think that is wrong."}}}}}}},"PushRateLimited":{"description":"About 120 requests a minute from one source address, counted together with the Integrations MCP server.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushError429"},"examples":{"rate_limited":{"value":{"error":{"code":"rate_limited","message":"Too many requests. Slow down and retry."}}}}}}}},"examples":{"InvalidJson":{"value":{"error":{"code":"invalid_json","message":"The request body must be JSON."}}},"IdempotencyKeyReusedRequest":{"value":{"error":{"code":"idempotency_key_reused","message":"That Idempotency-Key was used for a different request. Use a new key."}}},"InProgressRequest":{"value":{"error":{"code":"in_progress","message":"That request is still being processed."}}}},"schemas":{"SendRecipientsRequest":{"type":"object","required":["campaign_id","recipients"],"properties":{"campaign_id":{"type":"string","description":"The campaign ID from the campaign's Dynamic Audience setup (PA- followed by 6 characters)."},"api_key":{"type":"string","description":"The Full access key, only for senders that cannot set a header."},"require_variable_images":{"type":"boolean","description":"When true, a recipient without a usable variable image is skipped (variable_image_missing) instead of mailed with the default image."},"field_types":{"type":"object","description":"Types for custom fields this request creates. Fields without one are text.","additionalProperties":{"type":"string","enum":["text","number","date","yes_no"]}},"recipients":{"type":"array","minItems":1,"maxItems":5000,"description":"Up to 5,000 recipients, or 250 centers on a Neighborhood Mail campaign.","items":{"$ref":"#/components/schemas/Recipient"}}}},"Recipient":{"type":"object","description":"One recipient. Any field not listed here is a custom field by name: a new one creates a column (10 per request, 50 API-created columns per team), and values are stored up to 600 characters.","properties":{"reference":{"type":"string","description":"Your own id for this recipient, echoed back in its result."},"first_name":{"type":"string"},"last_name":{"type":"string"},"address_1":{"type":"string"},"address_2":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"zip":{"type":"string"},"birthday":{"type":"string","description":"A birthday, stored in the address book's birthday fields."}},"additionalProperties":true},"IngestResponse":{"type":"object","required":["summary","results"],"properties":{"summary":{"type":"object","required":["received","mailed","notMailed"],"properties":{"received":{"type":"integer"},"mailed":{"type":"integer"},"notMailed":{"type":"integer"},"unparseableTypedValues":{"type":"integer","description":"Custom values a typed column could not parse. They are stored as sent."},"computedFieldsIgnored":{"type":"integer","description":"Values sent for formula or AI columns, which the API cannot set."}}},"results":{"type":"array","items":{"$ref":"#/components/schemas/IngestResult"}},"results_truncated":{"type":"boolean","const":true,"description":"Present 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 4 MB, in the order you sent them. summary still counts every recipient."}}},"IngestResult":{"type":"object","required":["reference","address","status","detail"],"properties":{"reference":{"type":["string","null"]},"address":{"type":"string","description":"The address as one line."},"status":{"type":"string","enum":["mailed","not_mailed"]},"reason":{"type":"string","description":"Why the recipient was not mailed.","enum":["invalid_address","do_not_mail","filtered_location","filtered_conditions","budget_exceeded","duplicate_recent","payment_failed","no_homes_found","request_capacity_reached","text_overflow","variable_image_missing","unsupported_characters","removed_by_sender"]},"detail":{"type":"string"},"homesFound":{"type":"integer","description":"Neighborhood Mail only. Homes found around this address."},"homesMailed":{"type":"integer","description":"Neighborhood Mail only. Homes mailed."},"product_type":{"type":"string","description":"The product mailed (the first step's on a multi-step campaign)."},"postage":{"type":"string","enum":["standard","first_class"]},"mail_by_date":{"type":"string","format":"date"},"warnings":{"type":"array","description":"Present when the recipient was mailed with a fallback.","items":{"type":"object","required":["code","field","detail"],"properties":{"code":{"type":"string","enum":["variable_image_fallback","variable_image_blank","qr_fallback_link","text_overlap","text_unmeasurable","text_curve_too_long"]},"field":{"type":"string"},"detail":{"type":"string"}}}},"return_address_fallback":{"type":"boolean","const":true},"return_address_reason":{"type":"string","enum":["missing_token_value","unverified_address","verification_limit_reached"]}}},"IngestError401":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["invalid_api_key"]},"message":{"type":"string"}}},"IngestError404":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["campaign_not_found"]},"message":{"type":"string"}}},"IngestError409":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["campaign_not_active","design_cannot_render"]},"message":{"type":"string"}}},"IngestError413":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["payload_too_large"]},"message":{"type":"string"}}},"IngestError422":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["invalid_payload"]},"message":{"type":"string"}}},"IngestError429":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["rate_limited"]},"message":{"type":"string"}}},"IngestError500":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["internal_error"]},"message":{"type":"string"}}},"IngestError503":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["validation_unavailable"]},"message":{"type":"string"}}},"PushErrorDetails":{"description":"Present on some codes, with machine-readable detail.","type":"object","additionalProperties":true},"PushError401":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["invalid_key"]},"message":{"type":"string"},"details":{"$ref":"#/components/schemas/PushErrorDetails"}}}}},"PushError403":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["integration_unavailable"]},"message":{"type":"string"},"details":{"$ref":"#/components/schemas/PushErrorDetails"}}}}},"PushError429":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["rate_limited"]},"message":{"type":"string"},"details":{"$ref":"#/components/schemas/PushErrorDetails"}}}}},"PushErrorDeclareSchema400":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["invalid_json","invalid_request","too_many_fields"]},"message":{"type":"string"},"details":{"$ref":"#/components/schemas/PushErrorDetails"}}}}},"PushErrorDeclareSchema409":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["field_locked"]},"message":{"type":"string"},"details":{"$ref":"#/components/schemas/PushErrorDetails"}}}}},"PushErrorSync400":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["invalid_json","missing_idempotency_key"]},"message":{"type":"string"},"details":{"$ref":"#/components/schemas/PushErrorDetails"}}}}},"PushErrorBeginSync409":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["sync_in_progress","idempotency_key_reused","in_progress"]},"message":{"type":"string"},"details":{"$ref":"#/components/schemas/PushErrorDetails"}}}}},"PushErrorCompleteSync409":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["no_open_sync","idempotency_key_reused","in_progress"]},"message":{"type":"string"},"details":{"$ref":"#/components/schemas/PushErrorDetails"}}}}},"PushErrorRecords400":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["invalid_json","invalid_request","missing_idempotency_key"]},"message":{"type":"string"},"details":{"$ref":"#/components/schemas/PushErrorDetails"}}}}},"PushErrorRecords409":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["no_open_sync","idempotency_key_reused","in_progress"]},"message":{"type":"string"},"details":{"$ref":"#/components/schemas/PushErrorDetails"}}}}},"PushErrorRecords413":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["too_many_records","payload_too_large"]},"message":{"type":"string"},"details":{"$ref":"#/components/schemas/PushErrorDetails"}}}}},"PushErrorRejections400":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["invalid_sync_id","invalid_type","invalid_cursor","invalid_limit"]},"message":{"type":"string"},"details":{"$ref":"#/components/schemas/PushErrorDetails"}}}}},"PushErrorRejections404":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["sync_not_found"]},"message":{"type":"string"},"details":{"$ref":"#/components/schemas/PushErrorDetails"}}}}},"IdempotencyKey":{"type":"string","minLength":1,"maxLength":200},"InspectIntegrationResponse":{"type":"object","required":["integration","openSync","limits","nextStep"],"properties":{"integration":{"type":"object","required":["id","name","status","setupComplete"],"properties":{"id":{"type":"string","description":"The integration's public id."},"name":{"type":"string"},"status":{"type":"string","description":"The integration's state, for example setup_needed, ready, or syncing."},"setupComplete":{"type":"boolean"}}},"openSync":{"oneOf":[{"type":"null"},{"type":"object","required":["id","mode","expiresAt","batchesReceived"],"properties":{"id":{"type":"string","format":"uuid"},"mode":{"$ref":"#/components/schemas/SyncMode"},"expiresAt":{"type":"string","format":"date-time"},"batchesReceived":{"type":"integer"}}}]},"limits":{"type":"object","required":["recordsPerBatch","bytesPerBatch","declaredFields","syncExpiryHours"],"properties":{"recordsPerBatch":{"type":"integer","const":1000},"bytesPerBatch":{"type":"integer","const":4456448},"declaredFields":{"type":"integer","const":1000},"syncExpiryHours":{"type":"integer","const":24}}},"nextStep":{"type":"string"}}},"FieldType":{"type":"string","enum":["text","number","date","yes_no","choice"]},"SyncMode":{"type":"string","enum":["full","delta"]},"ReadSchemaResponse":{"type":"object","required":["fields"],"properties":{"fields":{"type":"array","items":{"type":"object","required":["key","label","type","semantic"],"properties":{"key":{"type":"string"},"label":{"type":"string"},"type":{"$ref":"#/components/schemas/FieldType"},"semantic":{"type":["string","null"]}}}}}},"DeclareSchemaRequest":{"type":"object","required":["fields"],"properties":{"fields":{"type":"array","maxItems":1000,"items":{"type":"object","required":["key"],"properties":{"key":{"type":"string","minLength":1},"label":{"type":"string"},"type":{"$ref":"#/components/schemas/FieldType"},"options":{"type":"array","description":"The choices of a choice field.","items":{"type":"string"}},"semantic":{"type":"string","description":"The Postaged contact column this field fills.","enum":["first_name","last_name","address_1","address_2","city","state","zip","email","phone","organization"]}}}}}},"DeclareSchemaResponse":{"type":"object","required":["ok","added","noLongerSent"],"properties":{"ok":{"type":"boolean","const":true},"added":{"type":"integer","description":"Fields declared for the first time."},"noLongerSent":{"type":"array","description":"Declared fields this declaration no longer mentions. They keep their columns and values.","items":{"type":"string"}}}},"OpenSync":{"type":"object","required":["id","mode","status","expiresAt","batchesReceived","recordsUpserted","recordsDeleted","recordsRejected"],"properties":{"id":{"type":"string","format":"uuid"},"mode":{"$ref":"#/components/schemas/SyncMode"},"status":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"},"batchesReceived":{"type":"integer"},"recordsUpserted":{"type":"integer"},"recordsDeleted":{"type":"integer"},"recordsRejected":{"type":"integer"}}},"ReadOpenSyncResponse":{"type":"object","required":["sync"],"properties":{"sync":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/OpenSync"}]}}},"BeginSyncRequest":{"type":"object","properties":{"mode":{"$ref":"#/components/schemas/SyncMode","description":"full (the default) or delta."}}},"BeginSyncResponse":{"type":"object","required":["syncId","mode","expiresAt"],"properties":{"syncId":{"type":"string","format":"uuid"},"mode":{"$ref":"#/components/schemas/SyncMode"},"expiresAt":{"type":"string","format":"date-time"}}},"CompleteSyncRequest":{"type":"object","properties":{"syncId":{"type":"string","description":"The open sync's id. When sent, it must name the open sync."}}},"CompleteSyncResponse":{"type":"object","required":["ok","syncId","mode","recordsUpserted","recordsDeleted","recordsRejected","markedRemoved"],"properties":{"ok":{"type":"boolean","const":true},"syncId":{"type":"string","format":"uuid"},"mode":{"$ref":"#/components/schemas/SyncMode"},"recordsUpserted":{"type":"integer"},"recordsDeleted":{"type":"integer"},"recordsRejected":{"type":"integer"},"markedRemoved":{"type":"integer","description":"Contacts a full sync marked as removed. A delta sync reports 0."}}},"PushRecordsRequest":{"type":"object","required":["records"],"properties":{"records":{"type":"array","maxItems":1000,"items":{"$ref":"#/components/schemas/PushRecord"}}}},"PushRecord":{"type":"object","description":"One contact, at most 65,536 bytes as JSON.","required":["external_id"],"properties":{"external_id":{"type":"string","minLength":1,"maxLength":128,"description":"Your system's stable id for the contact."},"op":{"type":"string","enum":["upsert","delete"],"default":"upsert"},"fields":{"type":"object","description":"Values by declared field key. A null clears nothing; it is sent as blank.","additionalProperties":{"type":["string","number","boolean","null"]}}}},"Rejection":{"type":"object","required":["index","externalId","code","message"],"properties":{"index":{"type":"integer","description":"The record's position in the batch."},"externalId":{"type":["string","null"]},"code":{"type":"string","enum":["missing_external_id","invalid_external_id","unknown_field","invalid_type","invalid_email","duplicate_in_batch","record_too_large"]},"field":{"type":"string"},"message":{"type":"string"}}},"CellWarningGroup":{"type":"object","description":"The values one batch dropped for one field and code. A record was accepted without its value for this field.","required":["index","externalId","code","field","message","count","externalIds"],"properties":{"index":{"type":"integer","description":"The batch position of the group's first record."},"externalId":{"type":"string","description":"The group's first record's external_id."},"code":{"type":"string","enum":["invalid_type"]},"field":{"type":"string"},"message":{"type":"string"},"count":{"type":"integer","minimum":1,"description":"How many accepted records lost a value for this field."},"externalIds":{"type":"array","description":"Up to 5 of those records' external_id values, in batch order.","minItems":1,"maxItems":5,"items":{"type":"string"}}}},"PushRecordsResponse":{"type":"object","required":["accepted","deleted","rejected","rejections","warnings"],"properties":{"accepted":{"type":"integer"},"deleted":{"type":"integer"},"rejected":{"type":"integer"},"rejections":{"type":"array","items":{"$ref":"#/components/schemas/Rejection"}},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/CellWarningGroup"}}}},"ValidateRecordsResponse":{"type":"object","required":["validated","accepted","rejected","rejections","warnings"],"properties":{"validated":{"type":"boolean","const":true},"accepted":{"type":"integer"},"rejected":{"type":"integer"},"rejections":{"type":"array","items":{"$ref":"#/components/schemas/Rejection"}},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/CellWarningGroup"}}}},"ListSyncRejectionsResponse":{"type":"object","required":["syncId","type","items","nextAfter"],"properties":{"syncId":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["rejection","warning"]},"items":{"type":"array","items":{"type":"object","required":["id","batch","index","externalId","code","field","message"],"properties":{"id":{"type":"integer"},"batch":{"type":"integer"},"index":{"type":"integer"},"externalId":{"type":["string","null"]},"code":{"type":"string","description":"A rejection code, or invalid_type for a dropped value."},"field":{"type":["string","null"]},"message":{"type":"string"}}}},"nextAfter":{"type":["string","null"],"description":"Pass it back as after for the next page. Null on the last page."}}},"QrScannedEvent":{"type":"object","required":["event","id","scanned_at","campaign","recipient","location","destination"],"properties":{"event":{"type":"string","const":"qr.scanned"},"id":{"type":"string","description":"The event id, also sent as webhook-id."},"test":{"type":"boolean","const":true,"description":"Present only on a test event."},"scanned_at":{"type":"string","format":"date-time","description":"When the scan happened, in UTC (ISO 8601)."},"campaign":{"type":"object","description":"The campaign whose card was scanned.","required":["id","name"],"properties":{"id":{"type":"string","description":"The campaign ID (PA- followed by 6 characters)."},"name":{"type":"string","description":"The campaign's name."}}},"recipient":{"type":"object","description":"Who the card was mailed to. A value we don't have is null.","required":["first_name","last_name","email","phone","address_1","address_2","city","state","zip"],"properties":{"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"address_1":{"type":["string","null"]},"address_2":{"type":["string","null"]},"city":{"type":["string","null"]},"state":{"type":["string","null"]},"zip":{"type":["string","null"]}}},"location":{"type":"object","description":"The approximate scan location, from the scanning device's network.","required":["city","region","latitude","longitude"],"properties":{"city":{"type":["string","null"]},"region":{"type":["string","null"]},"latitude":{"type":["number","null"]},"longitude":{"type":["number","null"]}}},"destination":{"type":"string","description":"Where the QR code sent the person."}}}}}}