Developers · API v1
Send postcards from your code.
Point the Relay API at a campaign and every record you post becomes a printed, tracked piece of mail: you get a per-recipient QR and scan URL back on the spot, and delivery and scan data as it happens.
Authentication
Create keys at Settings → API keys (org owner or admin). Keys are scoped to your organization; make one per integration so you can revoke them independently. Pass the key as a bearer token on every request:
Authorization: Bearer rk_live_...
Endpoints
List your campaigns with id, format, status, fulfillment, and default destination. Use it to discover the campaign_id to send against. Campaigns and artwork are set up in the Relay app; the API adds recipients to them.
Submit up to 500 records per request. Each record is validated independently; failures come back per record and are never created. Accepted records get a tracked QR immediately. If the campaign is live (proof approved and sending), pieces print and mail automatically and the response says "sending": true; otherwise records stage on the campaign.
POST /api/v1/mailings
Authorization: Bearer rk_live_...
Content-Type: application/json
{
"campaign_id": "YOUR_CAMPAIGN_ID",
"records": [
{
"address": {
"full_name": "Dana Whitfield",
"company": null,
"address_1": "1420 Camelback Rd",
"address_2": null,
"city": "Phoenix",
"state": "AZ",
"postal_code": "85014"
},
"external_id": "customer-123",
"destination_url": "https://example.com/offer?src=mail",
"merge": { "first_name": "Dana" }
}
]
}{
"accepted": 1,
"rejected": 0,
"sending": true,
"records": [
{
"index": 0,
"ruid": "RMT4ZV6HFY2",
"external_id": "customer-123",
"tracking_code": "tfhu8PPj",
"status": "valid",
"address": {
"full_name": "Dana Whitfield",
"address_1": "1420 Camelback Rd",
"city": "Phoenix",
"state": "AZ",
"postal_code": "85014",
"...": "..."
},
"qr_url": "https://mailwithrelay.com/qr/CAMPAIGN/tfhu8PPj.png",
"scan_url": "https://postal-qr.com/tfhu8PPj"
}
],
"errors": []
}Validation: full_name, address_1, city, 2-letter state, and a 5-digit ZIP (ZIP+4 accepted) are required. destination_url is optional when the campaign has a default. external_id is your own customer id: unique per campaign, threaded across campaigns on the contact card.
One mailing's life so far: validation status, mail state (pending → submitted → printing → mailed → delivered), stamps, and the scan count.
{
"ruid": "RMT4ZV6HFY2",
"external_id": "customer-123",
"status": "valid",
"mail_state": "delivered",
"mailed_at": "2026-07-10T16:02:11Z",
"delivered_at": "2026-07-14T18:40:03Z",
"address": { "full_name": "Dana Whitfield", "...": "..." },
"tracking_code": "tfhu8PPj",
"qr_url": "https://mailwithrelay.com/qr/CAMPAIGN/tfhu8PPj.png",
"scan_url": "https://postal-qr.com/tfhu8PPj",
"scans": 3,
"campaign": { "id": "…", "name": "Spring Listing Blitz" }
}Errors
Every error is {code, message, field?}; per-record failures ride in the response's errors[] with the record index.
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_json · validation | Malformed body, missing campaign_id, or a bad records array |
| 401 | unauthorized | Missing, malformed, or revoked API key |
| 404 | not_found | Campaign or mailing not in your account |
| 422 | (per-record) | Every record in the request failed validation |
| 429 | rate_limited | Over the per-key rate limit |
Security model
Org-scoped keys, hashed at rest
Keys belong to your organization, not to individual users. Create one named key per integration. Relay stores only a sha256 hash; the plaintext is shown exactly once at creation and can never be retrieved again.
Role-gated management
Only org owners and admins can create or revoke keys. Revocation is immediate: the next request with a revoked key gets a 401.
Hard tenant isolation
Every query behind the API is scoped to the key's organization. A key can never read or write another account's campaigns, recipients, or scans.
Rate limiting + TLS
100 requests per minute per key (fixed window; batch up to 500 records per request, so the send ceiling is 50,000 pieces a minute). All traffic is HTTPS only.
Validation before anything else
Records that fail validation are rejected individually and never created, mailed, or charged. Duplicate customer IDs within a campaign are rejected; a known ID arriving with a new address is held as needs_review until you resolve it in the app.
Coming next
Signed outbound webhooks for scans, calls, and delivery events (HMAC-SHA256, Zapier-compatible), so you never have to poll for status.
Included with every Relay account; rate limits can be raised per key on request.