Contacts API
Contacts are the people you email. All routes need a bearer API key (see API Overview).
The contact object
{
"id": "clx...",
"email": "ada@example.com",
"firstName": "Ada",
"lastName": "Lovelace",
"tags": ["customer", "vip"],
"status": "SUBSCRIBED",
"createdAt": "2026-10-04T12:00:00.000Z"
}
status is one of PENDING, SUBSCRIBED, UNSUBSCRIBED or BOUNCED. Only SUBSCRIBED contacts receive campaigns. See Double Opt-In.
Limits: a contact can have at most 50 tags, and each tag at most 100 characters.
List contacts
GET /api/v1/contacts
| Query | Meaning |
|---|---|
limit | Page size, default 20, maximum 100 |
after | Cursor from the previous page |
status | One of PENDING, SUBSCRIBED, UNSUBSCRIBED, BOUNCED. Anything else is a 422 |
tag | Only contacts that have this exact tag |
q | Case-insensitive search in email, first name and last name |
Results are newest first.
Create or update a contact
POST /api/v1/contacts
{
"email": "ada@example.com",
"firstName": "Ada",
"lastName": "Lovelace",
"tags": ["customer"],
"status": "SUBSCRIBED"
}
emailis required. It is trimmed and lowercased.statusdefaults toSUBSCRIBED. An unrecognized value also falls back toSUBSCRIBED.- This is an upsert on the email address. A new contact returns
201. An existing contact is updated and returns200. - On an existing contact, the
tagsyou send replace its tags (an omittedtagsbecomes an empty list). Always send the fields you want to keep. - A request can always lower a contact's status (to
UNSUBSCRIBEDorBOUNCED), but it never raises someone who unsubscribed back toSUBSCRIBEDon its own say-so. That takes"consent": "explicit", which records that the person themselves agreed (it is logged with the sourceapi)."consent": "declined"records a refusal. Any otherconsentvalue is a422. ABOUNCEDcontact is never raised, and aSUBSCRIBEDcontact is never moved back toPENDING. The same rule applies to re-adding an address that was deleted after opting out. - Setting a contact to
UNSUBSCRIBEDorBOUNCEDadds the address to the suppression list and removes the contact from every list. Setting it back toSUBSCRIBEDlater does not lift the suppression, so a suppressed address is still never mailed. - A contact saved as
SUBSCRIBEDstarts any live automation whose trigger is a new subscriber.
Get a contact
GET /api/v1/contacts/:id
Returns the contact object, or 404.
Update a contact
PATCH /api/v1/contacts/:id
Send only the fields to change: email, firstName, lastName, tags, status.
- A new
emailthat belongs to another contact in your workspace returns409with codeCONFLICT. - An invalid email, an invalid
status, atagsvalue that is not an array, or too many or too long tags returns422. - An empty
firstNameorlastNameclears the field. tagsreplaces the whole tag list. Adding a tag that the contact did not have starts any live automation triggered by that tag.UNSUBSCRIBEDandBOUNCEDbehave as in the create route above.
Delete a contact
DELETE /api/v1/contacts/:id
Returns 204. The contact is removed permanently.
Lists a contact is on
GET /api/v1/contacts/:id/lists
Returns an array in data (it is not paginated). Each item has the list's id, name and createdAt, plus addedAt, the time the contact joined it.
To add or remove a contact from a list, use the Lists API.
Example
curl -X POST https://flomailr.com/api/v1/contacts \
-H "Authorization: Bearer $FLOMAILR_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"ada@example.com","firstName":"Ada","tags":["customer"]}'