Who receives a statement
Every contact has two possible address lists, and exactly one of them is used. Getting this wrong is the one mistake in this API that stops statements arriving without anything looking broken, so it is worth five minutes before you write any code.
The two lists
| List | Where it comes from | Survives a sync from Xero? |
|---|---|---|
| Xero | The contact's email address and contact people in Xero | No — replaced every time we sync |
| Statey | Addresses added in Statey, or through this API | Yes |
GET /api/v1/contacts/{id} shows you both, and tells you which one is live:
{
"contact": {
"id": "9f8e7d6c-...",
"name": "Acme Ltd",
"recipient_source": "xero",
"email_addresses": ["ap@acme.test"],
"contact_people": {
"xero": [{ "first_name": "Ann", "email_address": "ap@acme.test", "included": true, "primary": true }],
"statey": [{ "id": "b3f1...", "first_name": "Ada", "last_name": "Lovelace", "email_address": "ada@acme.test" }]
}
}
}
recipient_sourceis which list is live —"xero"or"statey".email_addressesis who a statement would actually go to right now. It is always the live list, resolved.contact_peopleshows both lists in full, so you can see what you would be switching to.
Xero's list includes people marked not to receive email in Xero. They are flagged with "included": false rather than hidden, because if you are comparing the two lists you need to see everything that is there.
You cannot write to Xero's list
Only the Statey list is writable here. Xero's is replaced on every sync, so anything written to it would disappear without warning — usually within the hour. Manage those addresses in Xero.
Adding addresses never changes who gets emailed
This is the part worth reading twice.
POST /api/v1/contacts/{id}/people adds to the Statey list. If the contact's recipient_source is "xero" — which is the default — then adding an address changes nothing about who receives a statement. The address sits there, unused, until you switch the source.
curl -X POST https://app.statey.app/api/v1/contacts/{id}/people \
-H "Authorization: Bearer stky_your_key_here" \
-H "Content-Type: application/json" \
-d '{"first_name": "Ada", "last_name": "Lovelace", "email_address": "ada@acme.test"}'
PATCH and DELETE /api/v1/contacts/{id}/people/{person_id} edit and remove them. All three need the write permission.
This separation is deliberate. If adding an address also switched the source, a contact with three Xero addresses would drop to emailing one person the moment you added your first Statey address — and nothing would tell you that two people had stopped receiving statements.
Switching the source is its own call
curl -X PATCH https://app.statey.app/api/v1/contacts/{id} \
-H "Authorization: Bearer stky_your_key_here" \
-H "Content-Type: application/json" \
-d '{"recipient_source": "statey"}'
Switching copies nothing. The Statey list is whatever you have put in it. If it is empty and you switch to it, that contact now emails nobody.
The response shows you the consequence immediately — check it:
{ "contact": { "recipient_source": "statey", "email_addresses": [] } }
An empty email_addresses after a switch means you have just silenced that contact. Switch back, or add addresses, before your next send.
A safe order of operations
GET /api/v1/contacts/{id}— readcontact_people.xeroand note who is there.POST .../peoplefor each address you want on the Statey list.GET /api/v1/contacts/{id}again — checkcontact_people.stateyis what you intended.PATCH /api/v1/contacts/{id}withrecipient_source: "statey".- Check
email_addressesin the response is not empty.
Then confirm with a dry run before a real send:
curl -X POST https://app.statey.app/api/v1/statement_sends/preview \
-H "Authorization: Bearer stky_your_key_here" \
-H "Content-Type: application/json" \
-d '{"contact_ids": ["9f8e7d6c-..."]}'
A contact that now emails nobody comes back under skipped with the reason no_email_address . See Asynchronous work.
Finding contacts that email nobody
GET /api/v1/contacts?filter=missing_email lists contacts whose live list is empty, whichever source they are on. Worth running after any bulk change to recipients.
There is a short delay after a write before this filter reflects it — the underlying figures are recalculated in the background. See Data freshness.