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.
/v1/crm/contactsAuthentication
Authorization: BearerSecret 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
emailstringEmail of the contact. It identifies the person and cannot be changed later.
externalIdstringYour own id for this person (up to 128 characters). The contact is found by it next time, even if the email changed.
sourcestringWhere 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.
apioverwritebooleanReplace the stored name, phones, image, website, address, type, price and stage instead of only filling the empty ones.
falsenamestringFull name (up to 120 characters). Or send firstName and lastName.
firstNamestringFirst name (up to 60 characters).
lastNamestringLast name (up to 60 characters).
phonestringOne phone number (up to 40 characters).
phonesarrayUp to 10 phone numbers.
imagestringPhoto URL.
websitestringWebsite URL.
streetAddressstringStreet address (up to 200 characters).
typestringContact type, for example buyer, seller or renter.
stagestringPipeline stage, for example new-lead.
pricenumberBudget or price of interest (0 or more).
tagsarrayTags to add (up to 50, each up to 64 characters). Existing tags are kept.
removeTagsarrayTags to remove (up to 50).
Responses
application/jsonoutcomestringcreated, updated or unchanged.
contactobjectThe 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 withoutcomeupdatedorunchanged. - Only empty fields are filled on an existing contact. Pass
overwrite: trueto 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-leadstage. - Send an
Idempotency-Keyheader (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.
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"}'
{"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"}}