Contacts API
Contacts represent the people who receive emails from your Synapse workspace. Each contact is identified by an external_id (your application's user ID) and has an email address, name, tags, and custom properties.
For a user guide on managing contacts in the dashboard, see Contacts.
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /v1/contacts | List and search contacts |
POST | /v1/contacts | Upsert a contact (create or merge) |
GET | /v1/contacts/{id} | Get a contact by ID |
PATCH | /v1/contacts/{external_id} | Partial update with operators |
DELETE | /v1/contacts/{external_id} | Soft delete a contact |
POST | /v1/contacts/bulk | Bulk import up to 1,000 contacts |
POST | /v1/contacts/bulk-action | Bulk action (delete, update status, export) |
POST | /v1/contacts/export | Export contacts as CSV or JSON |
GET | /v1/contacts/views | List saved views |
POST | /v1/contacts/views | Create a saved view |
PUT | /v1/contacts/views/{view_id} | Update a saved view |
DELETE | /v1/contacts/views/{view_id} | Delete a saved view |
GET | /v1/contacts/event-names | List distinct event names |
GET | /v1/contacts/property-keys | List distinct property keys |
GET | /v1/contacts/property-values | List distinct values for a property key |
GET | /v1/contacts/count | Count contacts (with optional audience filter) |
GET | /v1/contacts/{id}/events | List events for a contact (cursor-paginated) |
List Contacts
Auth: JWT (dashboard). Requires contacts:read permission.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number (1-indexed) |
per_page | integer | 50 | Items per page (max 200) |
search | string | Search by email, name, or external_id (substring match) | |
filters | string | URL-encoded JSON filter DSL (same format as segments) | |
segment_id | UUID | Filter contacts by segment membership | |
sort_by | string | created_at | Sort column: email, first_name, last_name, created_at, updated_at, subscription_status, product_type, or properties.{key} |
sort_order | string | desc | asc or desc |
Response
Upsert a Contact
Auth: API key. Requires scope: data, management, or full.
Creates a new contact or merges with an existing one if the external_id already exists. This is an idempotent operation -- safe to call repeatedly.
curl
Python
JavaScript
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
external_id | string | Yes | Your application's unique user identifier (unique per workspace) |
email | string | No | Contact's email address. Required for email delivery but not for contact creation. |
first_name | string | No | First name |
last_name | string | No | Last name |
phone | string | No | Phone number |
product_type | string | No | Product category (used in segment exclusion rules) |
subscription_status | string | No | Contact status: subscribed, unsubscribed, bounced, complained |
tags | string[] | No | List of tags for categorization and segmentation |
timezone | string | No | IANA timezone (e.g., Asia/Singapore, America/New_York) |
locale | string | No | Locale code (e.g., en-SG, vi-VN) for localized content |
properties | object | No | Custom key-value pairs (any valid JSON) |
Response (200 OK)
When the contact already exists (matched by external_id), the provided fields are merged:
Unlike a traditional POST that returns 409 Conflict on duplicates, this endpoint is designed as an upsert. It is safe to call repeatedly with the same external_id -- existing contacts are merged, not rejected. Use PATCH for partial updates with tag operators.
Partial Update
Auth: API key. Requires scope: data, management, or full.
Update specific fields on an existing contact. Supports tag operators ($add_tags, $remove_tags) and property shallow-merge.
curl
Python
JavaScript
Merge Behavior
- Top-level fields (
email,first_name,last_name,phone,timezone,locale): Overwritten if provided tags: Replaced entirely if provided. Use$add_tags/$remove_tagsfor surgical updates.$add_tags: Appends tags to the existing list (duplicates are ignored)$remove_tags: Removes specified tags from the existing listproperties: Shallow-merged with existing properties. Set a key tonullto remove it.
The external_id in the URL path identifies the contact. You cannot change a contact's external_id via PATCH.
Delete a Contact
Auth: API key. Requires scope: management or full.
Soft-deletes a contact by setting subscription_status to "deleted". The contact record is retained for audit purposes, and email logs are preserved.
Response (200 OK)
This is a soft delete. The contact's subscription_status is set to "deleted", which prevents future email delivery. Active flow trips for this contact are dropped. The contact record and email logs are retained for audit purposes -- no data is permanently removed.
Bulk Import
Auth: API key. Requires scope: data, management, or full.
Import up to 1,000 contacts in a single request. Supports three conflict resolution strategies.
curl
Python
JavaScript
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
contacts | array | Yes | Array of contact objects (max 1,000) |
on_conflict | string | No | Conflict resolution: "merge" (default), "skip", or "replace" |
Conflict Resolution
| Strategy | Behavior |
|---|---|
skip | If external_id exists, skip the contact entirely. No fields are updated. |
merge | If external_id exists, merge provided fields into the existing contact. Properties are shallow-merged, tags are appended. |
replace | If external_id exists, overwrite all fields with the provided values. Omitted fields are cleared. |
Response
Partial success is supported. If some contacts fail validation, they appear in errors while valid contacts are processed:
Bulk Action
Perform bulk operations on multiple contacts at once. Supports individual selection (up to 200 IDs) or select-all with filters.
Auth: JWT (dashboard). Requires contacts:write permission.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
action | string | Yes | "delete", "update_status", or "export" |
contact_ids | UUID[] | Conditional | Contact IDs to target (required unless select_all is true, max 200) |
select_all | boolean | No | Target all contacts matching filters instead of contact_ids |
filters | object | No | Filter DSL (only used when select_all is true) |
params | object | No | Action-specific parameters (e.g., {"subscription_status": "unsubscribed"} for update_status) |
Action: update_status
Valid statuses: subscribed, unsubscribed, bounced, complained.
Response (200 OK)
Export Contacts
Export contacts as CSV or JSON. Small exports (up to 5,000 contacts) download immediately. Larger exports create a background job.
Auth: JWT (dashboard). Requires contacts:read permission.
Request Body
| Field | Type | Default | Description |
|---|---|---|---|
format | string | "csv" | "csv" or "json" |
scope | string | "all" | "all", "filtered", or "selected" |
contact_ids | UUID[] | [] | Required when scope is "selected" |
filters | object | {} | Filter DSL, used when scope is "filtered" |
columns | string[] | [] | Column keys to include (empty = all columns) |
include_properties | boolean | true | Flatten custom properties into individual columns |
Response: Sync (up to 5,000 contacts)
Returns a streaming file download with Content-Disposition: attachment header. The X-Export-Count response header contains the total number of exported rows.
Response: Async (more than 5,000 contacts)
Poll GET /v1/jobs/{job_id} for status. When status is "completed", download the file with GET /v1/jobs/{job_id}/download. Export files expire after 24 hours.
Saved Views
Saved views store filter, column, and sort configurations. Each workspace can have up to 20 custom views.
Auth: JWT (dashboard). Read requires contacts:read, write requires contacts:write.
List Views
Create View
Response: 201 Created with the full ContactView object.
| Field | Type | Default | Description |
|---|---|---|---|
name | string | Required | View name (max 255 chars) |
filters | object | {} | Filter configuration |
columns | array | [] | Column configuration objects |
sort_by | string | "created_at" | Sort column |
sort_order | string | "desc" | "asc" or "desc" |
is_default | boolean | false | Load this view by default (unsets any other default) |
pin_order | integer | 0 | Pin position (higher = listed first) |
Update View
All fields are optional -- only provided fields are updated. System views cannot be modified (returns 403).
Delete View
Returns 204 No Content. System views cannot be deleted (returns 403).
Event Names
List distinct event names received for this workspace. Useful for populating event dropdowns in the UI.
Auth: JWT (dashboard). Requires contacts:read permission.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
source | string | Filter by event source: tenant_api, email, system, flow, sdk |
Response
Property Keys
List distinct custom property keys found across contacts. Samples up to 500 recent contacts and returns keys with occurrence counts and auto-detected data types.
Auth: JWT (dashboard). Requires contacts:read permission.
Response
Property Values
List distinct values for a specific property key. Samples up to 500 contacts and returns the top 100 values by frequency.
Auth: JWT (dashboard). Requires contacts:read permission.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
property_key | string | Yes | The property key to get values for |
Response
Count Contacts
Count contacts matching audience criteria. Used internally by the flow wizard to show audience size.
Auth: JWT (dashboard). Requires contacts:read permission.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | "all" | "all" (total count) or "filter" (filter by event behavior) |
filter_event | string | Event name to filter by (used when mode is "filter") | |
filter_operator | string | "has_executed" or "has_not_executed" |
Response
user_count: Contacts matching the filter criteriareachable_users/reachable_email: Contacts with a non-empty email address (reachable by email)total_contacts: Total contacts in the workspace (unfiltered)
Contact Events
List events for a specific contact with cursor-based pagination and filtering.
Auth: JWT (dashboard). Requires contacts:read permission.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
source | string | -- | Filter by source: tenant_api, email, system, flow, sdk |
event_name | string | -- | Filter by event name (substring match) |
cursor | string | -- | ISO timestamp cursor for pagination. Returns events older (or newer) than this timestamp. |
limit | integer | 100 | Items per page (max 500) |
order | string | desc | Sort order: desc (latest first) or asc (oldest first) |
Response
Use next_cursor as the cursor parameter in the next request to fetch the next page.
Plan Limits
| Plan | Max Contacts |
|---|---|
| Free | 1,000 |
| Starter | 10,000 |
| Growth | 100,000 |
| Enterprise | Unlimited |