Guides

Creating and changing contacts

A contact you create is handled like one that arrives from a sync. Here is where it goes, and what we do with it.

Where a created contact goes

We save the contact in one of your two-way accounts. We choose it: the account you linked first that keeps an address book and isn’t paused. Then the sync sends the contact to your other two-way accounts. It never sends it to a read-only account.

If you have no such account, the call fails with 422 no_account_to_write_to.

How a created contact is matched

We compare the new contact with the contacts you have, the way we compare any contact that arrives from an account. What happens next depends on how close the match is and on your Auto-Merge level. Each answer has an outcome:

outcomeStatusWhat happened
created201A new contact. It matched no contact you have.
merged200The contact matched one you have, and your Auto-Merge level covers the match. The values were added to that contact. Nothing new was created.
needs_review201A new contact. It matched contacts you have, but your Auto-Merge level doesn’t merge them. The pair waits in Review duplicates, and the answer lists the possible matches.
updated200An existing contact was changed (PATCH).

When a create is merged, the values you sent are added to the contact you already have. We don’t overwrite or remove a value it holds. History shows it as a change you made through the key, and you can roll it back like any change. It is not shown as a merge, and the contacts.merged trigger doesn’t fire for it.

A needs_review answer lists possibleMatches: each has a contact and a confidence from 0 to 100.

Over your plan’s contact limit

We never refuse a create because of your plan’s contact limit. Over the limit, the create still answers 201, and the new contact is frozen: the answer has "frozen": true.

Answer, 201 (shortened)
{
  "outcome": "created",
  "contact": {
    "id": "…",
    "displayName": "Ada Lovelace",
    "frozen": true
  }
}

ContactZapp keeps a frozen contact, but doesn’t send it to your accounts until you upgrade or free a place under the limit. frozen is on every contact the API returns, in reads and in trigger items, so you can see which contacts are held back. It is read-only: you can’t set it.

The fields of a contact

A write takes these fields, under the same names and in the same shapes that a read returns them. Everything else is read-only: id, publicId, displayName, updatedAt and frozen.

FieldType
firstNamestring
middleNamestring
lastNamestring
prefixstring
suffixstring
nicknamestring
organizationstring
jobTitlestring
emailslist of LabeledValue
phoneslist of LabeledValue
addresseslist of LabeledValue
urlslist of LabeledValue
birthdayPartialDate or null
notesstring
labelslist of string

An email, phone, address or url is { "label": "work", "value": "…" }. The label can be empty. preferred: true marks the one to call or write to; leave it out if your tool has no such idea. A date is { "year": 1815, "month": 12, "day": 10 }, and a part you don’t know is 0. A birthday with no year is { "year": 0, "month": 3, "day": 15 }.

If the body has a field that isn’t in the table, we refuse it with 400 unknown_field and write nothing. That includes the read-only fields. To change a contact, send only the fields you change, not the whole contact you read.

Changing a contact

PATCH /api/v1/contacts/{id} changes only the fields in the body. A field you leave out keeps its value. The change is written to every two-way account the contact is in, never to a read-only one. History records it as yours, via the key.

In the bodyWhat happens
"jobTitle": "Analyst"The job title is set.
A field that isn’t thereIt keeps its value.
"organization": "" or "phones": []The field is cleared.
A list, such as emailsThe list replaces the stored list. Send the whole list you want.
"birthday": nullThe birthday is cleared. A birthday that isn’t in the body is kept. A date whose year, month and day are all 0 clears it too.
Clear a birthday
curl -s -X PATCH https://app.contactz.app/api/v1/contacts/772189b9-ef30-44a8-ace6-3955d061633c \
  -H "Authorization: Bearer $CZ_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "birthday": null }'

null clears the birthday only. On any other field, null means “not sent”, so use "" or [] to clear it.

The id in the path is the contact’s id, or its publicId such as CS-0042.