Create or Update Contact
Create a contact if they don't exist, or update them if they do — matched by email_address.
Endpoint
POST https://api.campaignlark.com/v1/contacts/upsert
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
email_address | string | required | Email address used to look up the contact. Always written to the contact record — cannot be changed via this endpoint. |
fields | object | optional | Key-value map of additional field values keyed by merge_tag. email_address is ignored here if provided — use the top-level email_address field instead. |
tags | array of integers | optional | Tag IDs to assign. On full replace, replaces existing tags. On partial update, preserves existing tags if omitted. |
status | string | optional | Contact status. One of SUBSCRIBED, UNSUBSCRIBED, UNCONFIRMED, CLEANED, COMPLAINED. Defaults to SUBSCRIBED if omitted on create or full replace. On partial update, preserves the existing status if omitted. |
partial_update | boolean | optional | Defaults to false. If true, only the provided fields are merged into existing data — unspecified fields, tags, and status are preserved. If false, all fields are replaced with the provided values. |
Sample Request
POST https://api.campaignlark.com/v1/contacts/upsert
Content-Type: application/json
{
"email_address": "tywin@example.com",
"fields": {
"first_name": "Tywin",
"last_name": "Lannister"
},
"tags": [1, 2],
"status": "SUBSCRIBED",
"partial_update": true
}
Response
Success — 200 OK
{
"data": {
"id": "64f1a2b3c4d5e6f7a8b9c0d1",
"status": "SUBSCRIBED",
"tags": [
{ "id": 1, "name": "newsletter" },
{ "id": 2, "name": "vip" }
],
"data": {
"email_address": "tywin@example.com",
"first_name": "Tywin",
"last_name": "Lannister"
},
"statistics": {
"total_opens": 0,
"total_clicks": 0,
"total_bounces": 0,
"total_delivered": 0
},
"created_at": "2024-02-15T10:30:00Z",
"updated_at": "2024-02-15T10:30:00Z"
}
}
Behaviour
| Scenario | Action |
|---|---|
| Contact not found | Creates a new contact. Status defaults to SUBSCRIBED if not provided. |
Contact found, partial_update: false | Replaces all field values with those provided. |
Contact found, partial_update: true | Merges provided fields into existing data. Unspecified fields, tags, and status are preserved. |
Automation Behaviour
- Create path: fires
CONTACT_SUBSCRIBED_PENDINGif status isUNCONFIRMED. No other automation fires for other statuses. - Update path: fires
TAG_ADDED/TAG_REMOVEDif tags change andFIELD_UPDATEDif tracked fields change.
Errors
| Status | Message | Cause |
|---|---|---|
400 | Email address is required for upsert operation. | email_address is missing. |
400 | We were unable to find a field with the merge_tag <tag> | A key in fields doesn't match any workspace merge tag. |
400 | The status you provided is not valid. | Invalid status value. |
400 | Field '<label>' is required. | A workspace-required field was omitted on a full replace or create. |