Contacts

Merge duplicate contacts

Merge one or more groups of duplicate contacts. The request body is `{ contactIds: Group[] }` where each group is either a plain array of ≥2 contact IDs (the OLDEST record survives) or `{ contact_ids:

POST /v2/contacts/merge

Parameters

Header parameters

Idempotency-Key
string required
Required. UUID per logical operation, reused on every retry. See apps/rest-api/src/shared/idempotency.ts for the full contract. Missing → 400 BadRequest; same key + different body → 409 Conflict.

Request body

Content-Type: application/json

contactIds
array<array> required

Example

{
  "contactIds": [
    [
      "00000000-0000-0000-0000-000000000000"
    ]
  ]
}

Responses

200 — Successful response

Content-Type: application/json

data
object required
data.merged
array<object> required
data.merged[].id
string (uuid) required
data.merged[].user_id
string
data.merged[].first_name
string | null
data.merged[].last_name
string | null
data.merged[].full_name
string | null
data.merged[].description
string | null
data.merged[].job_title
string | null
data.merged[].company
string | null
data.merged[].education
string | null
data.merged[].legacy_location
string | null
data.merged[].image_url
string | null
data.merged[].business_card_url
string | null
data.merged[].website
string | null
data.merged[].image_source
string | null
data.merged[].birthday
string (date-time)
data.merged[].birthday_year
integer | null
data.merged[].linkedin
string | null
data.merged[].twitter
string | null
data.merged[].facebook
string | null
data.merged[].instagram
string | null
data.merged[].telegram
string | null
data.merged[].tiktok
string | null
data.merged[].youtube
string | null
data.merged[].starred
boolean
data.merged[].is_archived
boolean
data.merged[].ignore_merge
boolean
data.merged[].never_keep_in_touch
boolean
data.merged[].source
string
data.merged[].priority_tier
integer | null
data.merged[].frequency
string | null
data.merged[].frequency_text
string | null
data.merged[].last_seen_at
string (date-time)
data.merged[].last_reminder_at
string (date-time)
data.merged[].next_reminder_at
string (date-time)
data.merged[].first_met_at
string (date-time)
data.merged[].first_interaction_at
string (date-time)
data.merged[].linkedin_companies
string | null
data.merged[].linkedin_education
string | null
data.merged[].linkedin_enhance_date
string (date-time)
data.merged[].linkedin_last_message_at
string (date-time)
data.merged[].linkedin_last_message_snippet
string | null
data.merged[].linkedin_message_link
string | null
data.merged[].linkedin_urn
string | null
data.merged[].linkedin_headline
string | null
data.merged[].whatsapp_message_link
string | null
data.merged[].whatsapp_message_snippet
string | null
data.merged[].whatsapp_last_message_at
string (date-time)
data.merged[].imessage_message_link
string | null
data.merged[].imessage_message_snippet
string | null
data.merged[].imessage_last_message_at
string (date-time)
data.merged[].instagram_message_link
string | null
data.merged[].instagram_message_snippet
string | null
data.merged[].instagram_last_message_at
string (date-time)
data.merged[].gmail_last_interaction_at
string (date-time)
data.merged[].gmail_last_interaction_provider
string enum
Possible values: "GOOGLE", "OFFICE365"
data.merged[].gmail_last_interaction_provider_id
string | null
data.merged[].gmail_last_interaction_subject
string | null
data.merged[].gcal_last_interaction_at
string (date-time)
data.merged[].gcal_last_interaction_provider
string enum
Possible values: "GOOGLE", "OFFICE365"
data.merged[].gcal_last_interaction_provider_id
string | null
data.merged[].gcal_last_interaction_title
string | null
data.merged[].phone_call_interaction_snippet
string | null
data.merged[].phone_call_last_interaction_at
string (date-time)
data.merged[].web_search_summary
any | null
data.merged[].created_at
string (date-time)
data.merged[].updated_at
string (date-time)
data.merged[].geocode_attempts
integer
data.merged[].geocode_failed_at
string (date-time)
data.merged[]._count
object
data.merged[]._count.groups_contacts
integer
data.merged[].frequency_interval
any
data.merged[].linkedin_data
object | null
data.merged[].latitude
number | null
data.merged[].longitude
number | null
data.merged[].last_note
object | null
data.merged[].last_note.id
string (uuid)
data.merged[].last_note.preview
string
data.merged[].last_note.event_time
string (date-time)
data.merged[].last_note.emoji
string | null
data.merged[].last_note.meeting_type
string | null
data.merged[].contact_emails
array<object>
data.merged[].contact_phone_numbers
array<object>
data.merged[].contact_birthdays
object | null
data.merged[].contact_locations
array<object>
data.merged[].contacts_custom_fields
array<object>
data.merged[].legacy_contact_addresses
array<object>
data.merged[].groups_contacts
array<object>
data.merged[].tags_contacts
array<object>
data.merged[].timeline_items_contacts
array<object>
data.merged[].reminders_contacts
array<object>
data.merged[].contact_relations_contact_relations_sourceTocontacts
array<object>
data.merged[].contact_relations_contact_relations_destinationTocontacts
array<object>
data.merged[].feed_items
array<object>
data.merged[].keep_in_touch_reminders
object | null
data.failed
array<object> required
data.failed[].contactIds
array<string (uuid)> required
data.failed[].error
object required
data.failed[].error.code
string required
data.failed[].error.message
string required

Example

{
  "data": {
    "merged": [
      {
        "id": "00000000-0000-0000-0000-000000000000"
      }
    ],
    "failed": [
      {
        "contactIds": [
          "00000000-0000-0000-0000-000000000000"
        ],
        "error": {
          "code": "string",
          "message": "string"
        }
      }
    ]
  }
}

400 — Request body failed validation.

Content-Type: application/json

(body)
any

401 — Missing or invalid API key.

Content-Type: application/json

(body)
any

403 — Valid key, insufficient permission.

Content-Type: application/json

(body)
any

404 — Resource does not exist.

Content-Type: application/json

(body)
any

409 — Request conflicts with the current state of the resource.

Content-Type: application/json

(body)
any

429 — Rate limit exceeded.

Content-Type: application/json

(body)
any

500 — Unexpected server error.

Content-Type: application/json

(body)
any

Code samples

curl https://api.prod.getdex.com/v2/contacts/merge \
  --request POST \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "contactIds": [
    [
      "00000000-0000-0000-0000-000000000000"
    ]
  ]
}'
Copyright © 2026