Skip to content

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.

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

{
"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"
}
FieldTypeRequiredDescription
externalIdstringYesUnique identifier from your system (max 255 chars)
emailstringNoContact email (normalized to lowercase)
firstNamestringNoFirst name (max 255 chars)
lastNamestringNoLast name (max 255 chars)
phonestringNoPhone number (max 50 chars, E.164 recommended)
embeddedDataobjectNoCustom key-value attributes; merged with existing data on update (new keys override)
preferredLanguagestringNoISO 639-1 language code (max 10 chars)
{
"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 created
  • isNew: false — contact was updated
  • canContact — whether the contact is eligible for distributions (based on opt-out status and frequency rules)

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" }

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

{
"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.

{
"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"
}
]
}
FieldTypeDescription
creatednumberCount of newly created contacts
updatednumberCount of updated existing contacts
errorsarrayPer-contact errors with index, externalId, and message