Contacts API
These endpoints allow external systems to create or update contacts using their own identifiers (externalId). Contacts are matched by the combination of (organizationId, externalId) — the organizationId is automatically derived from the API key.
Upsert Contact
Section titled “Upsert Contact”Create or update a single contact by externalId. If a contact with the given externalId exists in your organization, it is updated (with embeddedData merged). Otherwise, a new contact is created.
Endpoint: POST /api/directory/contacts/upsert
Request
Section titled “Request”{ "externalId": "PAT-12345", "email": "john.doe@example.com", "firstName": "John", "lastName": "Doe", "phone": "+14155551234", "embeddedData": { "department": "Cardiology", "visitDate": "2026-03-05", "doctorName": "Dr. Smith" }, "preferredLanguage": "en"}| Field | Type | Required | Description |
|---|---|---|---|
externalId | string | Yes | Unique identifier from your system (max 255 chars) |
email | string | No | Contact email (normalized to lowercase) |
firstName | string | No | First name (max 255 chars) |
lastName | string | No | Last name (max 255 chars) |
phone | string | No | Phone number (max 50 chars, E.164 recommended) |
embeddedData | object | No | Custom key-value attributes; merged with existing data on update (new keys override) |
preferredLanguage | string | No | ISO 639-1 language code (max 10 chars) |
Response
Section titled “Response”{ "id": "0195e3a1-0000-7000-8000-000000000001", "organizationId": "0195e3a1-0000-7000-8000-000000000000", "externalId": "PAT-12345", "email": "john.doe@example.com", "firstName": "John", "lastName": "Doe", "phone": "+14155551234", "embeddedData": { "department": "Cardiology", "visitDate": "2026-03-05", "doctorName": "Dr. Smith" }, "optOutStatus": "active", "canContact": true, "isNew": true, "createdAt": "2026-03-05T12:00:00.000Z", "updatedAt": "2026-03-05T12:00:00.000Z"}isNew: true— contact was createdisNew: false— contact was updatedcanContact— whether the contact is eligible for distributions (based on opt-out status and frequency rules)
Embedded Data Merging
Section titled “Embedded Data Merging”On update, embeddedData is shallow-merged — new keys override existing ones, but existing keys not present in the request are preserved:
Existing: { "department": "Cardiology", "ward": "3A" }Request: { "department": "Oncology", "visitDate": "2026-03-05" }Result: { "department": "Oncology", "ward": "3A", "visitDate": "2026-03-05" }Bulk Upsert Contacts
Section titled “Bulk Upsert Contacts”Upsert up to 1,000 contacts in a single call. Requests with more than 1,000 contacts are rejected with a validation error.
All operations run in a single database transaction. Contacts that succeed are committed, while contacts that fail (e.g., due to a unique constraint violation) are skipped and reported in the errors array — they do not prevent the rest of the batch from being saved.
Endpoint: POST /api/directory/contacts/bulk-upsert
Request
Section titled “Request”{ "contacts": [ { "externalId": "PAT-12345", "email": "john.doe@example.com", "firstName": "John", "lastName": "Doe", "embeddedData": { "ward": "ICU" } }, { "externalId": "PAT-67890", "email": "jane.smith@example.com", "firstName": "Jane", "lastName": "Smith" } ]}Each contact in the array follows the same schema as the upsert endpoint. externalId is required for every entry.
Response
Section titled “Response”{ "created": 1, "updated": 1, "errors": []}If individual contacts fail (e.g., duplicate email within the org), they appear in the errors array. The remaining contacts are still saved:
{ "created": 1, "updated": 0, "errors": [ { "index": 1, "externalId": "PAT-67890", "message": "Unique constraint violation on email" } ]}| Field | Type | Description |
|---|---|---|
created | number | Count of newly created contacts |
updated | number | Count of updated existing contacts |
errors | array | Per-contact errors with index, externalId, and message |