Create or Update Contact

Creates a contact in your CRM, or updates it when it already exists. The contact is found by externalId (inside its source) and then by email, so sending the same person twice never creates a duplicate.

POST/v1/crm/contacts

Authentication

API keyAuthorization: Bearer

Secret API key as Authorization: Bearer sk_live_…. Create it in the dashboard under CRM › Contacts › API Keys. Keys are for servers only. Needs the contacts:write scope.

Body Parameters

emailstring
required

Email of the contact. It identifies the person and cannot be changed later.

externalIdstring
optional

Your own id for this person (up to 128 characters). The contact is found by it next time, even if the email changed.

sourcestring
optional

Where the lead comes from, for example zillow or zapier (lowercase letters, digits, _ and -, up to 32). It is shown in the CRM and is the namespace of externalId.

Default:api
overwriteboolean
optional

Replace the stored name, phones, image, website, address, type, price and stage instead of only filling the empty ones.

Default:false
namestring
optional

Full name (up to 120 characters). Or send firstName and lastName.

firstNamestring
optional

First name (up to 60 characters).

lastNamestring
optional

Last name (up to 60 characters).

phonestring
optional

One phone number (up to 40 characters).

phonesarray
optional

Up to 10 phone numbers.

imagestring
optional

Photo URL.

websitestring
optional

Website URL.

streetAddressstring
optional

Street address (up to 200 characters).

typestring
optional

Contact type, for example buyer, seller or renter.

stagestring
optional

Pipeline stage, for example new-lead.

pricenumber
optional

Budget or price of interest (0 or more).

tagsarray
optional

Tags to add (up to 50, each up to 64 characters). Existing tags are kept.

removeTagsarray
optional

Tags to remove (up to 50).

Responses

200Success
application/json
outcomestring

created, updated or unchanged.

contactobject

The contact as stored after the call.

Errors

401Missing, invalid or revoked API key. The body is { error: { code, message } }.
403The key lacks the required scope (contacts:read or contacts:write), or the account is not active.
429More than 120 requests per minute for this key. Wait for the Retry-After header.
422Validation failed. error.details maps each field to a message; unknown fields are rejected.

Notes

  • A new contact answers 201 with outcome: "created". An existing one answers 200 with outcome updated or unchanged.
  • Only empty fields are filled on an existing contact. Pass overwrite: true to replace name, phones, image, website, address, type, price and stage.
  • The email, the stage of a contact already in the pipeline and its email unsubscribe status are never changed.
  • New contacts start in the new-lead stage.
  • Send an Idempotency-Key header (up to 255 characters) so a retried request returns the first answer for 24 hours. The same key with a different body answers 422. In the tester, add it in the Headers box.
  • The body is strict: unknown fields answer 422.
Request
curl --request POST \
--url "https://api.pandaidx.com/v1/crm/contacts" \
--header "Authorization: Bearer {{api_key}}" \
--header "Content-Type: application/json" \
--data '{"email":"ana@example.com","source":"zapier","name":"Ana Torres"}'
Response200 OK
{
"outcome": "created",
"contact": {
"id": "6ad0c3f2e4b1a2c3d4e5f607",
"email": "ana@example.com",
"name": "Ana Torres",
"phones": [
"+13055550100"
],
"image": null,
"website": null,
"streetAddress": null,
"type": "buyer",
"stage": "new-lead",
"price": null,
"source": "zapier",
"tags": [
"buyer"
],
"externalIds": [
{
"source": "zapier",
"id": "abc-1"
}
],
"createdAt": "2026-10-14T13:40:12.000Z",
"updatedAt": "2026-10-14T13:40:12.000Z"
}
}