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
QueryMeaning
limitPage size, default 20, maximum 100
afterCursor from the previous page
statusOne of PENDING, SUBSCRIBED, UNSUBSCRIBED, BOUNCED. Anything else is a 422
tagOnly contacts that have this exact tag
qCase-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"
}
  • email is required. It is trimmed and lowercased.
  • status defaults to SUBSCRIBED. An unrecognized value also falls back to SUBSCRIBED.
  • This is an upsert on the email address. A new contact returns 201. An existing contact is updated and returns 200.
  • On an existing contact, the tags you send replace its tags (an omitted tags becomes an empty list). Always send the fields you want to keep.
  • A request can always lower a contact's status (to UNSUBSCRIBED or BOUNCED), but it never raises someone who unsubscribed back to SUBSCRIBED on its own say-so. That takes "consent": "explicit", which records that the person themselves agreed (it is logged with the source api). "consent": "declined" records a refusal. Any other consent value is a 422. A BOUNCED contact is never raised, and a SUBSCRIBED contact is never moved back to PENDING. The same rule applies to re-adding an address that was deleted after opting out.
  • Setting a contact to UNSUBSCRIBED or BOUNCED adds the address to the suppression list and removes the contact from every list. Setting it back to SUBSCRIBED later does not lift the suppression, so a suppressed address is still never mailed.
  • A contact saved as SUBSCRIBED starts 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 email that belongs to another contact in your workspace returns 409 with code CONFLICT.
  • An invalid email, an invalid status, a tags value that is not an array, or too many or too long tags returns 422.
  • An empty firstName or lastName clears the field.
  • tags replaces the whole tag list. Adding a tag that the contact did not have starts any live automation triggered by that tag.
  • UNSUBSCRIBED and BOUNCED behave 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"]}'