Goal
By the end of this guide you will be able to perform full CRUD operations on contacts: retrieve your contact structure (to get field IDs), create new contacts, retrieve them individually, update their fields or status, and delete them.Prerequisites
- An API key with
contacts:writescope (for full CRUD) orcontacts:readscope (for read-only access) - Your API base URL (found on the Account Settings > API Keys page)
About contact status
Every contact has astatus field that tracks whether they’re reachable. The shape depends on the contact’s state.
Active contacts have only a primary status:
- Status values are case-sensitive.
Active,Inactive, andUnsubscribemust use exact capitalization. Lowercase values such as"active"are rejected with a validation error. - Customers can only set
Unsubscribeas the secondary value via the API. This is the only customer-initiated reason for inactivating a contact. Other secondary values appear on contacts the system has inactivated automatically based on email events, and cannot be set through API calls. The values you may see on contacts the system has inactivated include:Bounce— the contact’s email address hard-bouncedComplaint-FBL— the contact’s mailbox provider reported a spam complaint via the Feedback Loop (FBL) mechanism
- When transitioning a contact to Inactive, both
primaryandsecondaryare required. Sending onlyprimary: "Inactive"returns a validation error. - Once a contact is Inactive, it cannot be reactivated via the API. This is a deliberate compliance protection — customers who unsubscribe or hard-bounce should not be re-emailed. To re-add a contact, they must opt in again through your sign-up flow.
Steps
1. Fetch your contact structure
Before creating contacts, retrieve your contact structure to get the field IDs you will need. Every contact belongs to a contact structure, and field values are set by referencing each field’s_id.
200 OK):
_id and the field _id values. You will use these when creating and updating contacts. See Manage Custom Fields for more details on contact structures.
2. Create a contact
Send aPOST request with the contact’s email address (key), the contact structure it belongs to, and optionally any custom field values and list assignments.
200 OK):
accountId, createdBy, and modifiedBy fields, which are auto-populated by the system and read-only. They’re omitted from the samples here for clarity.
Key points:
keyis the contact’s email address (required)contactStructureIdis required — it determines which custom fields are availablefieldsuses the field_idfrom your contact structure (see Step 1)listsoptionally assigns the contact to one or more lists at creation time- New contacts start with
__v: 0. You’ll need this version value when updating the contact via PUT.
3. Get a contact by ID
Retrieve a single contact using its_id.
200 OK):
__v field is the contact’s current version. You’ll need this exact value when updating the contact via PUT (see Step 5).
4. Update a contact’s status
UsePATCH to update a contact’s status. The most common case is unsubscribing a contact:
200 OK):
PATCHonly accepts thestatusfield. To update other fields (email, custom fields, lists, tags), usePUT(see Step 5).- Both
primaryandsecondaryare required when transitioning a contact to Inactive. - Inactive contacts cannot be reactivated via the API. See About contact status above.
5. Replace a contact (full update)
UsePUT to update any field on a contact: email, custom fields, lists, tags, or status. Unlike PATCH, PUT accepts the full contact body.
Important: PUT replaces the entire contact. Any field you omit from the request body will be removed from the contact’s stored data. To avoid accidentally losing data, follow these three steps:
- (If needed) Find the contact’s
_idvia Search Contacts. - Send a
GETrequest to retrieve the contact’s full data, including its current__vversion. - Send a
PUTrequest using the GET response as your starting body. Change only the fields you want to update, and include__vexactly as you received it from the GET.
200 OK):
__v:
__vis required on every PUT request. It’s the contact’s optimistic-concurrency version, which prevents one update from silently overwriting another.- Always use the
__vfrom a fresh GET — not a value cached from earlier. Successful PATCH or PUT calls increment__v, so a stale value will be rejected. - If
__vdoes not match the contact’s current version, the API returns a400 ConcurrencyError. Re-run the GET, rebuild your PUT body, and retry.
- Status follows the same rules as PATCH: capitalized values, both
primaryandsecondaryrequired for Inactive. - For an Active contact, send
"status": { "primary": "Active" }(nosecondary). - PUT cannot change an Inactive contact back to Active — see About contact status.
6. Delete a contact
200 OK): Returns the deleted contact object.
7. List all contacts
Retrieve all contacts for your account. This endpoint returns all matching contacts in a single response (no pagination). For large contact databases, use Search Contacts instead, which supports pagination and filtering.200 OK):
Common errors
| Status | Error | Cause | Fix |
|---|---|---|---|
401 | UnauthorizedError | Invalid, expired, or inactive API key | Verify your key is correct and active in Settings > API Keys |
403 | ForbiddenError | Key lacks the required scope | Use a key with contacts:write for create/update/delete operations |
400 | RequiredFieldError | A required field is missing from the request body | Check the error’s field and message for the specific field name |
400 | ModelValidationError | A field value fails validation (wrong type, invalid enum value, missing case-paired field, blocked transition) | Read the message and debug for specifics. Common cases: lowercase status values, transitioning Inactive → Active, missing secondary when setting primary: "Inactive" |
400 | ConcurrencyError | The __v value in your PUT body does not match the contact’s current version | Run a fresh GET, rebuild your PUT body using the new __v, and retry |
400 | DuplicateFieldError | A contact with the same email already exists in this structure | Use Search Contacts to look up the existing contact, then update it instead of creating a new one |
404 | RecordNotFound | Contact ID does not exist | Verify the contact ID; it may have been deleted |
429 | TooManyRequestsError | Rate limit or monthly quota exceeded | Wait for the Retry-After period and retry. See Rate Limits |
Next steps
- Search Contacts — find contacts by email or custom field values
- Manage Lists — organize contacts into lists
- Manage Custom Fields — define the fields available on your contacts
- Migration from Legacy — end-to-end workflow for importing your data