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

GET /api/v1/campaigns

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.

POST /api/v1/mailings

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.

GET /api/v1/mailings/{ruid}

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.

StatusCodeMeaning
400invalid_json · validationMalformed body, missing campaign_id, or a bad records array
401unauthorizedMissing, malformed, or revoked API key
404not_foundCampaign or mailing not in your account
422(per-record)Every record in the request failed validation
429rate_limitedOver 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.

Get your API key

Included with every Relay account; rate limits can be raised per key on request.