curl --request POST \
--url https://api.tented.ai/v1/contacts/upsert \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"items": [
{
"client_item_id": "<string>",
"email": "<string>",
"phone": "<string>",
"display_name": "<string>",
"first_name": "<string>",
"last_name": "<string>",
"email_domain": "<string>",
"company": "<string>",
"job_title": "<string>",
"address": "<string>",
"address2": "<string>",
"city": "<string>",
"state": "<string>",
"country": "<string>",
"zip_code": "<string>",
"original_source": "<string>",
"original_source_detail": "<string>",
"lead_status": "<string>",
"lifecycle_stage": "<string>",
"tented_score": 123,
"unsubscribed": true,
"marketing_email_subscribed": true,
"marketing_sms_subscribed": true,
"marketing_sms_invalid": true,
"custom_fields": {}
}
]
}
'import requests
url = "https://api.tented.ai/v1/contacts/upsert"
payload = { "items": [
{
"client_item_id": "<string>",
"email": "<string>",
"phone": "<string>",
"display_name": "<string>",
"first_name": "<string>",
"last_name": "<string>",
"email_domain": "<string>",
"company": "<string>",
"job_title": "<string>",
"address": "<string>",
"address2": "<string>",
"city": "<string>",
"state": "<string>",
"country": "<string>",
"zip_code": "<string>",
"original_source": "<string>",
"original_source_detail": "<string>",
"lead_status": "<string>",
"lifecycle_stage": "<string>",
"tented_score": 123,
"unsubscribed": True,
"marketing_email_subscribed": True,
"marketing_sms_subscribed": True,
"marketing_sms_invalid": True,
"custom_fields": {}
}
] }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
items: [
{
client_item_id: '<string>',
email: '<string>',
phone: '<string>',
display_name: '<string>',
first_name: '<string>',
last_name: '<string>',
email_domain: '<string>',
company: '<string>',
job_title: '<string>',
address: '<string>',
address2: '<string>',
city: '<string>',
state: '<string>',
country: '<string>',
zip_code: '<string>',
original_source: '<string>',
original_source_detail: '<string>',
lead_status: '<string>',
lifecycle_stage: '<string>',
tented_score: 123,
unsubscribed: true,
marketing_email_subscribed: true,
marketing_sms_subscribed: true,
marketing_sms_invalid: true,
custom_fields: {}
}
]
})
};
fetch('https://api.tented.ai/v1/contacts/upsert', 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.tented.ai/v1/contacts/upsert",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'items' => [
[
'client_item_id' => '<string>',
'email' => '<string>',
'phone' => '<string>',
'display_name' => '<string>',
'first_name' => '<string>',
'last_name' => '<string>',
'email_domain' => '<string>',
'company' => '<string>',
'job_title' => '<string>',
'address' => '<string>',
'address2' => '<string>',
'city' => '<string>',
'state' => '<string>',
'country' => '<string>',
'zip_code' => '<string>',
'original_source' => '<string>',
'original_source_detail' => '<string>',
'lead_status' => '<string>',
'lifecycle_stage' => '<string>',
'tented_score' => 123,
'unsubscribed' => true,
'marketing_email_subscribed' => true,
'marketing_sms_subscribed' => true,
'marketing_sms_invalid' => true,
'custom_fields' => [
]
]
]
]),
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.tented.ai/v1/contacts/upsert"
payload := strings.NewReader("{\n \"items\": [\n {\n \"client_item_id\": \"<string>\",\n \"email\": \"<string>\",\n \"phone\": \"<string>\",\n \"display_name\": \"<string>\",\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"email_domain\": \"<string>\",\n \"company\": \"<string>\",\n \"job_title\": \"<string>\",\n \"address\": \"<string>\",\n \"address2\": \"<string>\",\n \"city\": \"<string>\",\n \"state\": \"<string>\",\n \"country\": \"<string>\",\n \"zip_code\": \"<string>\",\n \"original_source\": \"<string>\",\n \"original_source_detail\": \"<string>\",\n \"lead_status\": \"<string>\",\n \"lifecycle_stage\": \"<string>\",\n \"tented_score\": 123,\n \"unsubscribed\": true,\n \"marketing_email_subscribed\": true,\n \"marketing_sms_subscribed\": true,\n \"marketing_sms_invalid\": true,\n \"custom_fields\": {}\n }\n ]\n}")
req, _ := http.NewRequest("POST", 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.post("https://api.tented.ai/v1/contacts/upsert")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"items\": [\n {\n \"client_item_id\": \"<string>\",\n \"email\": \"<string>\",\n \"phone\": \"<string>\",\n \"display_name\": \"<string>\",\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"email_domain\": \"<string>\",\n \"company\": \"<string>\",\n \"job_title\": \"<string>\",\n \"address\": \"<string>\",\n \"address2\": \"<string>\",\n \"city\": \"<string>\",\n \"state\": \"<string>\",\n \"country\": \"<string>\",\n \"zip_code\": \"<string>\",\n \"original_source\": \"<string>\",\n \"original_source_detail\": \"<string>\",\n \"lead_status\": \"<string>\",\n \"lifecycle_stage\": \"<string>\",\n \"tented_score\": 123,\n \"unsubscribed\": true,\n \"marketing_email_subscribed\": true,\n \"marketing_sms_subscribed\": true,\n \"marketing_sms_invalid\": true,\n \"custom_fields\": {}\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tented.ai/v1/contacts/upsert")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"items\": [\n {\n \"client_item_id\": \"<string>\",\n \"email\": \"<string>\",\n \"phone\": \"<string>\",\n \"display_name\": \"<string>\",\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"email_domain\": \"<string>\",\n \"company\": \"<string>\",\n \"job_title\": \"<string>\",\n \"address\": \"<string>\",\n \"address2\": \"<string>\",\n \"city\": \"<string>\",\n \"state\": \"<string>\",\n \"country\": \"<string>\",\n \"zip_code\": \"<string>\",\n \"original_source\": \"<string>\",\n \"original_source_detail\": \"<string>\",\n \"lead_status\": \"<string>\",\n \"lifecycle_stage\": \"<string>\",\n \"tented_score\": 123,\n \"unsubscribed\": true,\n \"marketing_email_subscribed\": true,\n \"marketing_sms_subscribed\": true,\n \"marketing_sms_invalid\": true,\n \"custom_fields\": {}\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"status": "<string>",
"created_count": 123,
"updated_count": 123,
"rejected_count": 123,
"items": [
{
"index": 123,
"status": "created",
"client_item_id": "<string>",
"contact_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"contact": {
"contact_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"display_name": "<string>",
"first_name": "<string>",
"last_name": "<string>",
"email": "<string>",
"phone": "<string>",
"email_domain": "<string>",
"company": "<string>",
"job_title": "<string>",
"address": "<string>",
"address2": "<string>",
"city": "<string>",
"state": "<string>",
"country": "<string>",
"zip_code": "<string>",
"original_source": "<string>",
"original_source_detail": "<string>",
"lead_status": "<string>",
"lifecycle_stage": "<string>",
"tented_score": 123,
"unsubscribed": true,
"marketing_email_subscribed": true,
"marketing_sms_subscribed": true,
"marketing_email_invalid": true,
"marketing_sms_invalid": true,
"last_activity_at": "2023-11-07T05:31:56Z",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z"
},
"error_code": "bad_request",
"message": "<string>",
"existing_contact": {
"contact_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"display_name": "<string>",
"email": "<string>",
"phone": "<string>"
}
}
]
}{
"error": "<string>",
"error_code": "<string>",
"message": "<string>",
"code": "<string>",
"details": {},
"limit": 123,
"current": 123
}{
"error": "<string>",
"error_code": "<string>",
"message": "<string>",
"code": "<string>",
"details": {},
"limit": 123,
"current": 123
}Upsert contacts
Create or update up to 100 contacts in one request (note: this endpoint’s batch limit is 100 items). Each item is matched against existing contacts by a single key: the normalized email when the item has a non-empty email, otherwise the normalized phone. A matched contact is updated with the item’s remaining fields (status: updated); an unmatched item creates a new contact (status: created, with original_source defaulting to Public API and original_source_detail to client_item_id unless supplied).
Because matching uses only that single key, an item whose email is new but whose phone already belongs to a different contact is not treated as an update — the creation collides on the phone and the item is rejected with error_code: conflict and the conflicting contact in existing_contact. (A create collision on the item’s own match key — e.g. from a concurrent insert — is resolved as an update of that contact instead.) Updates that would change an email or phone to collide with another contact are likewise rejected with conflict, and updates that would leave a contact with neither a valid email nor phone are rejected with bad_request.
Partial success is normal — inspect each item result; successful items include the full contact with custom_fields. Body-level validation failures (an invalid email or phone format, more than 100 items, unknown fields) reject the entire request with 400 before any item is processed.
curl --request POST \
--url https://api.tented.ai/v1/contacts/upsert \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"items": [
{
"client_item_id": "<string>",
"email": "<string>",
"phone": "<string>",
"display_name": "<string>",
"first_name": "<string>",
"last_name": "<string>",
"email_domain": "<string>",
"company": "<string>",
"job_title": "<string>",
"address": "<string>",
"address2": "<string>",
"city": "<string>",
"state": "<string>",
"country": "<string>",
"zip_code": "<string>",
"original_source": "<string>",
"original_source_detail": "<string>",
"lead_status": "<string>",
"lifecycle_stage": "<string>",
"tented_score": 123,
"unsubscribed": true,
"marketing_email_subscribed": true,
"marketing_sms_subscribed": true,
"marketing_sms_invalid": true,
"custom_fields": {}
}
]
}
'import requests
url = "https://api.tented.ai/v1/contacts/upsert"
payload = { "items": [
{
"client_item_id": "<string>",
"email": "<string>",
"phone": "<string>",
"display_name": "<string>",
"first_name": "<string>",
"last_name": "<string>",
"email_domain": "<string>",
"company": "<string>",
"job_title": "<string>",
"address": "<string>",
"address2": "<string>",
"city": "<string>",
"state": "<string>",
"country": "<string>",
"zip_code": "<string>",
"original_source": "<string>",
"original_source_detail": "<string>",
"lead_status": "<string>",
"lifecycle_stage": "<string>",
"tented_score": 123,
"unsubscribed": True,
"marketing_email_subscribed": True,
"marketing_sms_subscribed": True,
"marketing_sms_invalid": True,
"custom_fields": {}
}
] }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
items: [
{
client_item_id: '<string>',
email: '<string>',
phone: '<string>',
display_name: '<string>',
first_name: '<string>',
last_name: '<string>',
email_domain: '<string>',
company: '<string>',
job_title: '<string>',
address: '<string>',
address2: '<string>',
city: '<string>',
state: '<string>',
country: '<string>',
zip_code: '<string>',
original_source: '<string>',
original_source_detail: '<string>',
lead_status: '<string>',
lifecycle_stage: '<string>',
tented_score: 123,
unsubscribed: true,
marketing_email_subscribed: true,
marketing_sms_subscribed: true,
marketing_sms_invalid: true,
custom_fields: {}
}
]
})
};
fetch('https://api.tented.ai/v1/contacts/upsert', 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.tented.ai/v1/contacts/upsert",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'items' => [
[
'client_item_id' => '<string>',
'email' => '<string>',
'phone' => '<string>',
'display_name' => '<string>',
'first_name' => '<string>',
'last_name' => '<string>',
'email_domain' => '<string>',
'company' => '<string>',
'job_title' => '<string>',
'address' => '<string>',
'address2' => '<string>',
'city' => '<string>',
'state' => '<string>',
'country' => '<string>',
'zip_code' => '<string>',
'original_source' => '<string>',
'original_source_detail' => '<string>',
'lead_status' => '<string>',
'lifecycle_stage' => '<string>',
'tented_score' => 123,
'unsubscribed' => true,
'marketing_email_subscribed' => true,
'marketing_sms_subscribed' => true,
'marketing_sms_invalid' => true,
'custom_fields' => [
]
]
]
]),
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.tented.ai/v1/contacts/upsert"
payload := strings.NewReader("{\n \"items\": [\n {\n \"client_item_id\": \"<string>\",\n \"email\": \"<string>\",\n \"phone\": \"<string>\",\n \"display_name\": \"<string>\",\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"email_domain\": \"<string>\",\n \"company\": \"<string>\",\n \"job_title\": \"<string>\",\n \"address\": \"<string>\",\n \"address2\": \"<string>\",\n \"city\": \"<string>\",\n \"state\": \"<string>\",\n \"country\": \"<string>\",\n \"zip_code\": \"<string>\",\n \"original_source\": \"<string>\",\n \"original_source_detail\": \"<string>\",\n \"lead_status\": \"<string>\",\n \"lifecycle_stage\": \"<string>\",\n \"tented_score\": 123,\n \"unsubscribed\": true,\n \"marketing_email_subscribed\": true,\n \"marketing_sms_subscribed\": true,\n \"marketing_sms_invalid\": true,\n \"custom_fields\": {}\n }\n ]\n}")
req, _ := http.NewRequest("POST", 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.post("https://api.tented.ai/v1/contacts/upsert")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"items\": [\n {\n \"client_item_id\": \"<string>\",\n \"email\": \"<string>\",\n \"phone\": \"<string>\",\n \"display_name\": \"<string>\",\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"email_domain\": \"<string>\",\n \"company\": \"<string>\",\n \"job_title\": \"<string>\",\n \"address\": \"<string>\",\n \"address2\": \"<string>\",\n \"city\": \"<string>\",\n \"state\": \"<string>\",\n \"country\": \"<string>\",\n \"zip_code\": \"<string>\",\n \"original_source\": \"<string>\",\n \"original_source_detail\": \"<string>\",\n \"lead_status\": \"<string>\",\n \"lifecycle_stage\": \"<string>\",\n \"tented_score\": 123,\n \"unsubscribed\": true,\n \"marketing_email_subscribed\": true,\n \"marketing_sms_subscribed\": true,\n \"marketing_sms_invalid\": true,\n \"custom_fields\": {}\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tented.ai/v1/contacts/upsert")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"items\": [\n {\n \"client_item_id\": \"<string>\",\n \"email\": \"<string>\",\n \"phone\": \"<string>\",\n \"display_name\": \"<string>\",\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"email_domain\": \"<string>\",\n \"company\": \"<string>\",\n \"job_title\": \"<string>\",\n \"address\": \"<string>\",\n \"address2\": \"<string>\",\n \"city\": \"<string>\",\n \"state\": \"<string>\",\n \"country\": \"<string>\",\n \"zip_code\": \"<string>\",\n \"original_source\": \"<string>\",\n \"original_source_detail\": \"<string>\",\n \"lead_status\": \"<string>\",\n \"lifecycle_stage\": \"<string>\",\n \"tented_score\": 123,\n \"unsubscribed\": true,\n \"marketing_email_subscribed\": true,\n \"marketing_sms_subscribed\": true,\n \"marketing_sms_invalid\": true,\n \"custom_fields\": {}\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"status": "<string>",
"created_count": 123,
"updated_count": 123,
"rejected_count": 123,
"items": [
{
"index": 123,
"status": "created",
"client_item_id": "<string>",
"contact_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"contact": {
"contact_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"display_name": "<string>",
"first_name": "<string>",
"last_name": "<string>",
"email": "<string>",
"phone": "<string>",
"email_domain": "<string>",
"company": "<string>",
"job_title": "<string>",
"address": "<string>",
"address2": "<string>",
"city": "<string>",
"state": "<string>",
"country": "<string>",
"zip_code": "<string>",
"original_source": "<string>",
"original_source_detail": "<string>",
"lead_status": "<string>",
"lifecycle_stage": "<string>",
"tented_score": 123,
"unsubscribed": true,
"marketing_email_subscribed": true,
"marketing_sms_subscribed": true,
"marketing_email_invalid": true,
"marketing_sms_invalid": true,
"last_activity_at": "2023-11-07T05:31:56Z",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z"
},
"error_code": "bad_request",
"message": "<string>",
"existing_contact": {
"contact_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"display_name": "<string>",
"email": "<string>",
"phone": "<string>"
}
}
]
}{
"error": "<string>",
"error_code": "<string>",
"message": "<string>",
"code": "<string>",
"details": {},
"limit": 123,
"current": 123
}{
"error": "<string>",
"error_code": "<string>",
"message": "<string>",
"code": "<string>",
"details": {},
"limit": 123,
"current": 123
}Authorizations
Workspace API key, e.g. Authorization: Bearer tented_.... Create keys in Workspace Settings → API Keys.
Body
Contacts to create or update (1–100).
1 - 100 elementsShow child attributes
Show child attributes
Response
Batch processed (individual items may still be rejected).
Always completed once the batch has been processed.
Contacts created.
Contacts updated.
Number of rejected items.
Per-item results, in request order. Created and updated items include the full contact (with custom_fields).
Show child attributes
Show child attributes