Assert a CRM Person
Create or update a CRM person via the Zeeg CRM API, matching on any standard or custom attribute slug to upsert contacts in bulk imports and sync jobs
curl --request PUT \
--url https://api.zeeg.me/v2/crm/people \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"firstName": "Jane",
"lastName": "Doe",
"salutation": "<string>",
"emails": [
"jane.doe@acme.com"
],
"jobTitle": "<string>",
"description": "<string>",
"phoneNumber": "<string>",
"primaryLocation": "<string>",
"avatarUrl": "<string>",
"companyId": "<string>",
"socials": {
"linkedin": "<string>",
"twitter": "<string>",
"facebook": "<string>",
"instagram": "<string>"
}
}
'import requests
url = "https://api.zeeg.me/v2/crm/people"
payload = {
"firstName": "Jane",
"lastName": "Doe",
"salutation": "<string>",
"emails": ["jane.doe@acme.com"],
"jobTitle": "<string>",
"description": "<string>",
"phoneNumber": "<string>",
"primaryLocation": "<string>",
"avatarUrl": "<string>",
"companyId": "<string>",
"socials": {
"linkedin": "<string>",
"twitter": "<string>",
"facebook": "<string>",
"instagram": "<string>"
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
firstName: 'Jane',
lastName: 'Doe',
salutation: '<string>',
emails: ['jane.doe@acme.com'],
jobTitle: '<string>',
description: '<string>',
phoneNumber: '<string>',
primaryLocation: '<string>',
avatarUrl: '<string>',
companyId: '<string>',
socials: {
linkedin: '<string>',
twitter: '<string>',
facebook: '<string>',
instagram: '<string>'
}
})
};
fetch('https://api.zeeg.me/v2/crm/people', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.zeeg.me/v2/crm/people",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'firstName' => 'Jane',
'lastName' => 'Doe',
'salutation' => '<string>',
'emails' => [
'jane.doe@acme.com'
],
'jobTitle' => '<string>',
'description' => '<string>',
'phoneNumber' => '<string>',
'primaryLocation' => '<string>',
'avatarUrl' => '<string>',
'companyId' => '<string>',
'socials' => [
'linkedin' => '<string>',
'twitter' => '<string>',
'facebook' => '<string>',
'instagram' => '<string>'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.zeeg.me/v2/crm/people"
payload := strings.NewReader("{\n \"firstName\": \"Jane\",\n \"lastName\": \"Doe\",\n \"salutation\": \"<string>\",\n \"emails\": [\n \"jane.doe@acme.com\"\n ],\n \"jobTitle\": \"<string>\",\n \"description\": \"<string>\",\n \"phoneNumber\": \"<string>\",\n \"primaryLocation\": \"<string>\",\n \"avatarUrl\": \"<string>\",\n \"companyId\": \"<string>\",\n \"socials\": {\n \"linkedin\": \"<string>\",\n \"twitter\": \"<string>\",\n \"facebook\": \"<string>\",\n \"instagram\": \"<string>\"\n }\n}")
req, _ := http.NewRequest("PUT", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.put("https://api.zeeg.me/v2/crm/people")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"firstName\": \"Jane\",\n \"lastName\": \"Doe\",\n \"salutation\": \"<string>\",\n \"emails\": [\n \"jane.doe@acme.com\"\n ],\n \"jobTitle\": \"<string>\",\n \"description\": \"<string>\",\n \"phoneNumber\": \"<string>\",\n \"primaryLocation\": \"<string>\",\n \"avatarUrl\": \"<string>\",\n \"companyId\": \"<string>\",\n \"socials\": {\n \"linkedin\": \"<string>\",\n \"twitter\": \"<string>\",\n \"facebook\": \"<string>\",\n \"instagram\": \"<string>\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.zeeg.me/v2/crm/people")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"firstName\": \"Jane\",\n \"lastName\": \"Doe\",\n \"salutation\": \"<string>\",\n \"emails\": [\n \"jane.doe@acme.com\"\n ],\n \"jobTitle\": \"<string>\",\n \"description\": \"<string>\",\n \"phoneNumber\": \"<string>\",\n \"primaryLocation\": \"<string>\",\n \"avatarUrl\": \"<string>\",\n \"companyId\": \"<string>\",\n \"socials\": {\n \"linkedin\": \"<string>\",\n \"twitter\": \"<string>\",\n \"facebook\": \"<string>\",\n \"instagram\": \"<string>\"\n }\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"status": 200,
"person": {
"id": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
"firstName": "Jane",
"lastName": "Doe",
"salutation": "Ms",
"emails": [
"jane.doe@acme.com"
],
"jobTitle": "Head of Product",
"description": "Key decision-maker at Acme Corp.",
"phoneNumber": "+4930123456789",
"primaryLocation": "Berlin, Germany",
"avatarUrl": null,
"company": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"domain": "<string>"
},
"socials": {
"linkedin": "https://linkedin.com/in/janedoe",
"twitter": null,
"facebook": null,
"instagram": null
},
"customAttributes": {},
"createdAt": "2025-01-15T09:00:00+00:00",
"updatedAt": "2025-06-01T12:00:00+00:00"
}
}{
"success": true,
"status": 201,
"person": {
"id": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
"firstName": "Jane",
"lastName": "Doe",
"salutation": "Ms",
"emails": [
"jane.doe@acme.com"
],
"jobTitle": "Head of Product",
"description": "Key decision-maker at Acme Corp.",
"phoneNumber": "+4930123456789",
"primaryLocation": "Berlin, Germany",
"avatarUrl": null,
"company": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"domain": "<string>"
},
"socials": {
"linkedin": "https://linkedin.com/in/janedoe",
"twitter": null,
"facebook": null,
"instagram": null
},
"customAttributes": {},
"createdAt": "2025-01-15T09:00:00+00:00",
"updatedAt": "2025-06-01T12:00:00+00:00"
}
}{
"message": "Unauthenticated."
}{
"success": true,
"message": "<string>",
"status": 123
}{
"message": "The given data was invalid.",
"errors": {
"field_name": [
"The field_name field is required."
]
}
}- If a person with the same value for
matchingAttributeis found → it is updated and200is returned. - If no match is found → a new person is created and
201is returned.
Matching attribute
ThematchingAttribute query parameter specifies which field to use for the lookup. It can be any standard or custom attribute slug on the people object.
PUT /v2/crm/people?matchingAttribute=email
PUT /v2/crm/people?matchingAttribute=phone_number
PUT /v2/crm/people?matchingAttribute=my_custom_attribute
Email matching
WhenmatchingAttribute is email or emails, the lookup checks both the primary email and all secondary email addresses, so a person is found regardless of which email slot holds the value.
Other attributes
All other standard attributes (e.g.phone_number) and custom attributes are matched using exact equality.
Use case
Assert is the recommended endpoint for bulk imports and sync jobs. Send your full person payload and let the API decide whether to create or update — no need to pre-check for duplicates.{
"firstName": "Jane",
"lastName": "Doe",
"emails": ["jane.doe@acme.com"],
"jobTitle": "Head of Product",
"primaryLocation": "Berlin, Germany",
"socials": {
"linkedin": "https://linkedin.com/in/janedoe"
}
}
Authorizations
Query Parameters
Attribute slug to use when searching for an existing person. Can be any standard or custom attribute slug.
Use email (or emails) to match against the person's primary email and secondary emails.
All other attributes are matched using an exact equality check.
Body
First name. Required if lastName is not provided.
"Jane"
Last name. Required if firstName is not provided.
"Doe"
List of email addresses. The first entry is the primary (matching) email.
["jane.doe@acme.com"]Response
Updated — a person with the matching email was found and updated
true
200
A CRM person record.
Hide child attributes
Hide child attributes
Unique identifier for the person record.
"b2c3d4e5-f6a7-8901-bcde-f23456789012"
First name.
"Jane"
Last name.
"Doe"
Salutation or title (e.g. Mr, Ms, Dr).
"Ms"
List of email addresses. The first entry is the primary email.
["jane.doe@acme.com"]Job title or role.
"Head of Product"
Free-text description or notes about the person.
"Key decision-maker at Acme Corp."
Primary phone number.
"+4930123456789"
Primary location or city.
"Berlin, Germany"
URL of the person's avatar image.
null
Key/value pairs for custom attributes defined on the people object.
{}ISO 8601 timestamp when the record was created.
"2025-01-15T09:00:00+00:00"
ISO 8601 timestamp when the record was last updated.
"2025-06-01T12:00:00+00:00"
curl --request PUT \
--url https://api.zeeg.me/v2/crm/people \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"firstName": "Jane",
"lastName": "Doe",
"salutation": "<string>",
"emails": [
"jane.doe@acme.com"
],
"jobTitle": "<string>",
"description": "<string>",
"phoneNumber": "<string>",
"primaryLocation": "<string>",
"avatarUrl": "<string>",
"companyId": "<string>",
"socials": {
"linkedin": "<string>",
"twitter": "<string>",
"facebook": "<string>",
"instagram": "<string>"
}
}
'import requests
url = "https://api.zeeg.me/v2/crm/people"
payload = {
"firstName": "Jane",
"lastName": "Doe",
"salutation": "<string>",
"emails": ["jane.doe@acme.com"],
"jobTitle": "<string>",
"description": "<string>",
"phoneNumber": "<string>",
"primaryLocation": "<string>",
"avatarUrl": "<string>",
"companyId": "<string>",
"socials": {
"linkedin": "<string>",
"twitter": "<string>",
"facebook": "<string>",
"instagram": "<string>"
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
firstName: 'Jane',
lastName: 'Doe',
salutation: '<string>',
emails: ['jane.doe@acme.com'],
jobTitle: '<string>',
description: '<string>',
phoneNumber: '<string>',
primaryLocation: '<string>',
avatarUrl: '<string>',
companyId: '<string>',
socials: {
linkedin: '<string>',
twitter: '<string>',
facebook: '<string>',
instagram: '<string>'
}
})
};
fetch('https://api.zeeg.me/v2/crm/people', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.zeeg.me/v2/crm/people",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'firstName' => 'Jane',
'lastName' => 'Doe',
'salutation' => '<string>',
'emails' => [
'jane.doe@acme.com'
],
'jobTitle' => '<string>',
'description' => '<string>',
'phoneNumber' => '<string>',
'primaryLocation' => '<string>',
'avatarUrl' => '<string>',
'companyId' => '<string>',
'socials' => [
'linkedin' => '<string>',
'twitter' => '<string>',
'facebook' => '<string>',
'instagram' => '<string>'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.zeeg.me/v2/crm/people"
payload := strings.NewReader("{\n \"firstName\": \"Jane\",\n \"lastName\": \"Doe\",\n \"salutation\": \"<string>\",\n \"emails\": [\n \"jane.doe@acme.com\"\n ],\n \"jobTitle\": \"<string>\",\n \"description\": \"<string>\",\n \"phoneNumber\": \"<string>\",\n \"primaryLocation\": \"<string>\",\n \"avatarUrl\": \"<string>\",\n \"companyId\": \"<string>\",\n \"socials\": {\n \"linkedin\": \"<string>\",\n \"twitter\": \"<string>\",\n \"facebook\": \"<string>\",\n \"instagram\": \"<string>\"\n }\n}")
req, _ := http.NewRequest("PUT", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.put("https://api.zeeg.me/v2/crm/people")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"firstName\": \"Jane\",\n \"lastName\": \"Doe\",\n \"salutation\": \"<string>\",\n \"emails\": [\n \"jane.doe@acme.com\"\n ],\n \"jobTitle\": \"<string>\",\n \"description\": \"<string>\",\n \"phoneNumber\": \"<string>\",\n \"primaryLocation\": \"<string>\",\n \"avatarUrl\": \"<string>\",\n \"companyId\": \"<string>\",\n \"socials\": {\n \"linkedin\": \"<string>\",\n \"twitter\": \"<string>\",\n \"facebook\": \"<string>\",\n \"instagram\": \"<string>\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.zeeg.me/v2/crm/people")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"firstName\": \"Jane\",\n \"lastName\": \"Doe\",\n \"salutation\": \"<string>\",\n \"emails\": [\n \"jane.doe@acme.com\"\n ],\n \"jobTitle\": \"<string>\",\n \"description\": \"<string>\",\n \"phoneNumber\": \"<string>\",\n \"primaryLocation\": \"<string>\",\n \"avatarUrl\": \"<string>\",\n \"companyId\": \"<string>\",\n \"socials\": {\n \"linkedin\": \"<string>\",\n \"twitter\": \"<string>\",\n \"facebook\": \"<string>\",\n \"instagram\": \"<string>\"\n }\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"status": 200,
"person": {
"id": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
"firstName": "Jane",
"lastName": "Doe",
"salutation": "Ms",
"emails": [
"jane.doe@acme.com"
],
"jobTitle": "Head of Product",
"description": "Key decision-maker at Acme Corp.",
"phoneNumber": "+4930123456789",
"primaryLocation": "Berlin, Germany",
"avatarUrl": null,
"company": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"domain": "<string>"
},
"socials": {
"linkedin": "https://linkedin.com/in/janedoe",
"twitter": null,
"facebook": null,
"instagram": null
},
"customAttributes": {},
"createdAt": "2025-01-15T09:00:00+00:00",
"updatedAt": "2025-06-01T12:00:00+00:00"
}
}{
"success": true,
"status": 201,
"person": {
"id": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
"firstName": "Jane",
"lastName": "Doe",
"salutation": "Ms",
"emails": [
"jane.doe@acme.com"
],
"jobTitle": "Head of Product",
"description": "Key decision-maker at Acme Corp.",
"phoneNumber": "+4930123456789",
"primaryLocation": "Berlin, Germany",
"avatarUrl": null,
"company": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"domain": "<string>"
},
"socials": {
"linkedin": "https://linkedin.com/in/janedoe",
"twitter": null,
"facebook": null,
"instagram": null
},
"customAttributes": {},
"createdAt": "2025-01-15T09:00:00+00:00",
"updatedAt": "2025-06-01T12:00:00+00:00"
}
}{
"message": "Unauthenticated."
}{
"success": true,
"message": "<string>",
"status": 123
}{
"message": "The given data was invalid.",
"errors": {
"field_name": [
"The field_name field is required."
]
}
}