Contacts

  • Overview
  • Getting started
  • Authentication
  • Rate limits & errors
  • Contacts
  • Interaction logs
  • Webhooks
  • API reference & playground
  • Changelog
  • Legacy endpoints

Contacts

A contact is a person or business in your CRM, addressed by its user-visible contactId such as CR-100042. Contacts are never addressed by an internal database id.

The contact shape

A contact is returned as a flat object. The important fields are:

FieldDescription
contactIdUser-visible id, for example CR-100042.
identityTypepersonal or business.
sourceWhere the contact came from, for example api. Nullable.
firstName, lastNamePerson name fields. Nullable.
email, phonePrimary contact channels. Nullable.
businessName, websiteSet for business contacts. Nullable.
addressObject with line1, line2, city, state, zip, country.
contactTypeThe type object ({ id, name, category }). Nullable.
tagsArray of { id, name } tag objects.
subscribed, smsSubscribedEmail and SMS subscription booleans.
notesFree-text notes. Nullable.
customFieldsMap of custom-field id to value.
createdAt, updatedAtISO 8601 timestamps.
duplicateSignalPresent on GET by id; counts of email and phone matches.

customFields is a flat map keyed by each custom field id, not by title. Discover the ids with GET /custom-fields.

Create a contact

POST /contacts always creates and never upserts. Requires contacts:write. When you omit contactTypeId the project default Customer type is used, and a businessName implies a business contact.

bash
curl -X POST https://api.craftify.ai/v1/contacts \
  -H "Authorization: Bearer cfy_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "firstName": "Jane",
    "lastName": "Nguyen",
    "email": "jane@example.com",
    "phone": "+16045551234",
    "tags": ["vip"],
    "customFields": { "665f3a1b2c3d4e5f6a7b8c9d": "High" }
  }'

To upsert, first GET /contacts?email= to check for an existing contact. If one is returned, PATCH it; otherwise POST a new one. Creating without this check can produce duplicates.

Update a contact

PATCH /contacts/{contactId} is a partial update. Only the fields you send change, and sending null clears a field. Include a new contactId in the body to relabel the contact, which must stay unique within the project. Requires contacts:write.

bash
curl -X PATCH https://api.craftify.ai/v1/contacts/CR-100042 \
  -H "Authorization: Bearer cfy_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+16045559999",
    "tags": ["vip", "newsletter"],
    "notes": null
  }'

Get a contact

GET /contacts/{contactId} returns the full contact plus a read-time duplicateSignal. Requires contacts:read.

bash
curl https://api.craftify.ai/v1/contacts/CR-100042 \
  -H "Authorization: Bearer cfy_live_xxx"
json
{
  "contactId": "CR-100042",
  "identityType": "personal",
  "email": "jane@example.com",
  "phone": "+16045559999",
  "tags": [{ "id": "t_1", "name": "vip" }],
  "duplicateSignal": { "emailMatches": 0, "phoneMatches": 1 }
}

List and search

GET /contacts returns contacts sorted by most recently updated, using cursor pagination. Filters combine with AND. Requires contacts:read.

FilterMatches
emailExact, case-insensitive email.
phoneFormat-insensitive phone.
contactTypeIdA specific contact type id.
contactTypeCategoryLead, Customer, Personnel, or Other.
tagTag name or id; repeatable.
updatedSinceISO 8601 timestamp; updated at or after.
searchFree-text over name, email, and contactId.
bash
curl "https://api.craftify.ai/v1/contacts?contactTypeCategory=Customer&tag=vip&limit=50" \
  -H "Authorization: Bearer cfy_live_xxx"
json
{
  "data": [ { "contactId": "CR-100042", "email": "jane@example.com" } ],
  "pagination": { "nextCursor": "eyJ1cGRhdGVkQXQiOiIuLi4ifQ", "hasMore": true }
}

Fetch the next page by passing pagination.nextCursor as ?cursor=. On the last page nextCursor is null and hasMore is false.

Custom fields

Discover custom field ids with GET /custom-fields, then write values in the customFields map keyed by field id (the exact field title also works). For select and radio fields, send one of the configured option values.

Image and offering-list fields have writable: false and are read-only in v1. Writes to them are rejected.

Tags

The tags array on create and update accepts tag names or ids. Unknown names are created automatically, so you do not need to pre-create a tag before using it.

Deduplication

Use duplicateSignal from a GET by id to decide whether a contact overlaps with others. emailMatches and phoneMatches count other contacts that share this contact email or phone using the same matching the CRM uses. A non-zero count is a signal to merge or reconcile rather than create another record.

  • Interaction logs — record events on a contact timeline.
  • API reference — full request and response schemas.

© 2026 Craftify AI. All rights reserved.