curl --request POST \
--url https://production-api.joinrings.com/v1/persons/batch \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"persons": [
{
"city": "<string>",
"country_code": "<string>",
"description": "<string>",
"email": "<string>",
"email_3": "<string>",
"email_4": "<string>",
"email_5": "<string>",
"facebook_url": "<string>",
"first_name": "<string>",
"job_title": "<string>",
"last_name": "<string>",
"linkedin_url": "<string>",
"logo_url": "<string>",
"mobile_phone": "<string>",
"name": "<string>",
"phone": "<string>",
"secondary_email": "<string>",
"secondary_phone": "<string>",
"state_code": "<string>",
"twitter_url": "<string>",
"website": "<string>"
}
],
"update_matched": false
}
'import requests
url = "https://production-api.joinrings.com/v1/persons/batch"
payload = {
"persons": [
{
"city": "<string>",
"country_code": "<string>",
"description": "<string>",
"email": "<string>",
"email_3": "<string>",
"email_4": "<string>",
"email_5": "<string>",
"facebook_url": "<string>",
"first_name": "<string>",
"job_title": "<string>",
"last_name": "<string>",
"linkedin_url": "<string>",
"logo_url": "<string>",
"mobile_phone": "<string>",
"name": "<string>",
"phone": "<string>",
"secondary_email": "<string>",
"secondary_phone": "<string>",
"state_code": "<string>",
"twitter_url": "<string>",
"website": "<string>"
}
],
"update_matched": False
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
persons: [
{
city: '<string>',
country_code: '<string>',
description: '<string>',
email: '<string>',
email_3: '<string>',
email_4: '<string>',
email_5: '<string>',
facebook_url: '<string>',
first_name: '<string>',
job_title: '<string>',
last_name: '<string>',
linkedin_url: '<string>',
logo_url: '<string>',
mobile_phone: '<string>',
name: '<string>',
phone: '<string>',
secondary_email: '<string>',
secondary_phone: '<string>',
state_code: '<string>',
twitter_url: '<string>',
website: '<string>'
}
],
update_matched: false
})
};
fetch('https://production-api.joinrings.com/v1/persons/batch', 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://production-api.joinrings.com/v1/persons/batch",
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([
'persons' => [
[
'city' => '<string>',
'country_code' => '<string>',
'description' => '<string>',
'email' => '<string>',
'email_3' => '<string>',
'email_4' => '<string>',
'email_5' => '<string>',
'facebook_url' => '<string>',
'first_name' => '<string>',
'job_title' => '<string>',
'last_name' => '<string>',
'linkedin_url' => '<string>',
'logo_url' => '<string>',
'mobile_phone' => '<string>',
'name' => '<string>',
'phone' => '<string>',
'secondary_email' => '<string>',
'secondary_phone' => '<string>',
'state_code' => '<string>',
'twitter_url' => '<string>',
'website' => '<string>'
]
],
'update_matched' => false
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$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://production-api.joinrings.com/v1/persons/batch"
payload := strings.NewReader("{\n \"persons\": [\n {\n \"city\": \"<string>\",\n \"country_code\": \"<string>\",\n \"description\": \"<string>\",\n \"email\": \"<string>\",\n \"email_3\": \"<string>\",\n \"email_4\": \"<string>\",\n \"email_5\": \"<string>\",\n \"facebook_url\": \"<string>\",\n \"first_name\": \"<string>\",\n \"job_title\": \"<string>\",\n \"last_name\": \"<string>\",\n \"linkedin_url\": \"<string>\",\n \"logo_url\": \"<string>\",\n \"mobile_phone\": \"<string>\",\n \"name\": \"<string>\",\n \"phone\": \"<string>\",\n \"secondary_email\": \"<string>\",\n \"secondary_phone\": \"<string>\",\n \"state_code\": \"<string>\",\n \"twitter_url\": \"<string>\",\n \"website\": \"<string>\"\n }\n ],\n \"update_matched\": false\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
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://production-api.joinrings.com/v1/persons/batch")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"persons\": [\n {\n \"city\": \"<string>\",\n \"country_code\": \"<string>\",\n \"description\": \"<string>\",\n \"email\": \"<string>\",\n \"email_3\": \"<string>\",\n \"email_4\": \"<string>\",\n \"email_5\": \"<string>\",\n \"facebook_url\": \"<string>\",\n \"first_name\": \"<string>\",\n \"job_title\": \"<string>\",\n \"last_name\": \"<string>\",\n \"linkedin_url\": \"<string>\",\n \"logo_url\": \"<string>\",\n \"mobile_phone\": \"<string>\",\n \"name\": \"<string>\",\n \"phone\": \"<string>\",\n \"secondary_email\": \"<string>\",\n \"secondary_phone\": \"<string>\",\n \"state_code\": \"<string>\",\n \"twitter_url\": \"<string>\",\n \"website\": \"<string>\"\n }\n ],\n \"update_matched\": false\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://production-api.joinrings.com/v1/persons/batch")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"persons\": [\n {\n \"city\": \"<string>\",\n \"country_code\": \"<string>\",\n \"description\": \"<string>\",\n \"email\": \"<string>\",\n \"email_3\": \"<string>\",\n \"email_4\": \"<string>\",\n \"email_5\": \"<string>\",\n \"facebook_url\": \"<string>\",\n \"first_name\": \"<string>\",\n \"job_title\": \"<string>\",\n \"last_name\": \"<string>\",\n \"linkedin_url\": \"<string>\",\n \"logo_url\": \"<string>\",\n \"mobile_phone\": \"<string>\",\n \"name\": \"<string>\",\n \"phone\": \"<string>\",\n \"secondary_email\": \"<string>\",\n \"secondary_phone\": \"<string>\",\n \"state_code\": \"<string>\",\n \"twitter_url\": \"<string>\",\n \"website\": \"<string>\"\n }\n ],\n \"update_matched\": false\n}"
response = http.request(request)
puts response.read_body{
"results": [
{
"index": 123,
"status": "<string>",
"error": "<string>",
"uuid": "<string>"
}
]
}Batch create persons
For each entry: if email or linkedin_url matches an existing
tenant-visible person, that person is returned (status MATCHED) without a
write; otherwise a new tenant-source record is created (status CREATED).
Set update_matched to write a matched row’s remaining fields onto the
person it matched (status UPDATED) — without it, fields like job_title
on a row for someone already in the tenant are silently discarded.
This mirrors the matching of GET /v1/persons/lookup, but is broader than
the dedupe on POST /v1/persons, which matches on email only — a
linkedin_url-only match against an existing person returns MATCHED here,
but would create a duplicate via a single POST /v1/persons call with no
email set. Items are processed independently — a single failing row
comes back as status ERROR without sinking the rest of the batch. Results
are returned in input order.
Duplicate identifiers within the same batch resolve to one record: a later row sharing an email/linkedin_url with an earlier row comes back MATCHED against the first row’s UUID rather than creating a second person (an earlier create isn’t yet visible to the matcher mid-batch).
This is the bulk primitive for an import flow: look people up first
(POST /v1/persons/lookup/batch), then create the unmatched remainder
here, then attach notes (POST /v1/notes/batch).
curl --request POST \
--url https://production-api.joinrings.com/v1/persons/batch \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"persons": [
{
"city": "<string>",
"country_code": "<string>",
"description": "<string>",
"email": "<string>",
"email_3": "<string>",
"email_4": "<string>",
"email_5": "<string>",
"facebook_url": "<string>",
"first_name": "<string>",
"job_title": "<string>",
"last_name": "<string>",
"linkedin_url": "<string>",
"logo_url": "<string>",
"mobile_phone": "<string>",
"name": "<string>",
"phone": "<string>",
"secondary_email": "<string>",
"secondary_phone": "<string>",
"state_code": "<string>",
"twitter_url": "<string>",
"website": "<string>"
}
],
"update_matched": false
}
'import requests
url = "https://production-api.joinrings.com/v1/persons/batch"
payload = {
"persons": [
{
"city": "<string>",
"country_code": "<string>",
"description": "<string>",
"email": "<string>",
"email_3": "<string>",
"email_4": "<string>",
"email_5": "<string>",
"facebook_url": "<string>",
"first_name": "<string>",
"job_title": "<string>",
"last_name": "<string>",
"linkedin_url": "<string>",
"logo_url": "<string>",
"mobile_phone": "<string>",
"name": "<string>",
"phone": "<string>",
"secondary_email": "<string>",
"secondary_phone": "<string>",
"state_code": "<string>",
"twitter_url": "<string>",
"website": "<string>"
}
],
"update_matched": False
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
persons: [
{
city: '<string>',
country_code: '<string>',
description: '<string>',
email: '<string>',
email_3: '<string>',
email_4: '<string>',
email_5: '<string>',
facebook_url: '<string>',
first_name: '<string>',
job_title: '<string>',
last_name: '<string>',
linkedin_url: '<string>',
logo_url: '<string>',
mobile_phone: '<string>',
name: '<string>',
phone: '<string>',
secondary_email: '<string>',
secondary_phone: '<string>',
state_code: '<string>',
twitter_url: '<string>',
website: '<string>'
}
],
update_matched: false
})
};
fetch('https://production-api.joinrings.com/v1/persons/batch', 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://production-api.joinrings.com/v1/persons/batch",
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([
'persons' => [
[
'city' => '<string>',
'country_code' => '<string>',
'description' => '<string>',
'email' => '<string>',
'email_3' => '<string>',
'email_4' => '<string>',
'email_5' => '<string>',
'facebook_url' => '<string>',
'first_name' => '<string>',
'job_title' => '<string>',
'last_name' => '<string>',
'linkedin_url' => '<string>',
'logo_url' => '<string>',
'mobile_phone' => '<string>',
'name' => '<string>',
'phone' => '<string>',
'secondary_email' => '<string>',
'secondary_phone' => '<string>',
'state_code' => '<string>',
'twitter_url' => '<string>',
'website' => '<string>'
]
],
'update_matched' => false
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$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://production-api.joinrings.com/v1/persons/batch"
payload := strings.NewReader("{\n \"persons\": [\n {\n \"city\": \"<string>\",\n \"country_code\": \"<string>\",\n \"description\": \"<string>\",\n \"email\": \"<string>\",\n \"email_3\": \"<string>\",\n \"email_4\": \"<string>\",\n \"email_5\": \"<string>\",\n \"facebook_url\": \"<string>\",\n \"first_name\": \"<string>\",\n \"job_title\": \"<string>\",\n \"last_name\": \"<string>\",\n \"linkedin_url\": \"<string>\",\n \"logo_url\": \"<string>\",\n \"mobile_phone\": \"<string>\",\n \"name\": \"<string>\",\n \"phone\": \"<string>\",\n \"secondary_email\": \"<string>\",\n \"secondary_phone\": \"<string>\",\n \"state_code\": \"<string>\",\n \"twitter_url\": \"<string>\",\n \"website\": \"<string>\"\n }\n ],\n \"update_matched\": false\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
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://production-api.joinrings.com/v1/persons/batch")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"persons\": [\n {\n \"city\": \"<string>\",\n \"country_code\": \"<string>\",\n \"description\": \"<string>\",\n \"email\": \"<string>\",\n \"email_3\": \"<string>\",\n \"email_4\": \"<string>\",\n \"email_5\": \"<string>\",\n \"facebook_url\": \"<string>\",\n \"first_name\": \"<string>\",\n \"job_title\": \"<string>\",\n \"last_name\": \"<string>\",\n \"linkedin_url\": \"<string>\",\n \"logo_url\": \"<string>\",\n \"mobile_phone\": \"<string>\",\n \"name\": \"<string>\",\n \"phone\": \"<string>\",\n \"secondary_email\": \"<string>\",\n \"secondary_phone\": \"<string>\",\n \"state_code\": \"<string>\",\n \"twitter_url\": \"<string>\",\n \"website\": \"<string>\"\n }\n ],\n \"update_matched\": false\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://production-api.joinrings.com/v1/persons/batch")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"persons\": [\n {\n \"city\": \"<string>\",\n \"country_code\": \"<string>\",\n \"description\": \"<string>\",\n \"email\": \"<string>\",\n \"email_3\": \"<string>\",\n \"email_4\": \"<string>\",\n \"email_5\": \"<string>\",\n \"facebook_url\": \"<string>\",\n \"first_name\": \"<string>\",\n \"job_title\": \"<string>\",\n \"last_name\": \"<string>\",\n \"linkedin_url\": \"<string>\",\n \"logo_url\": \"<string>\",\n \"mobile_phone\": \"<string>\",\n \"name\": \"<string>\",\n \"phone\": \"<string>\",\n \"secondary_email\": \"<string>\",\n \"secondary_phone\": \"<string>\",\n \"state_code\": \"<string>\",\n \"twitter_url\": \"<string>\",\n \"website\": \"<string>\"\n }\n ],\n \"update_matched\": false\n}"
response = http.request(request)
puts response.read_body{
"results": [
{
"index": 123,
"status": "<string>",
"error": "<string>",
"uuid": "<string>"
}
]
}Authorizations
Body
Show child attributes
Show child attributes
When true, a row matching an existing person also writes that row's other fields onto them (status UPDATED) instead of being returned untouched. Off by default: matching is a lookup, so a plain import cannot silently rewrite records it did not intend to. Turn it on when the submitted rows are authoritative -- e.g. importing job titles for people already in the tenant. email is never overwritten either way; a new address goes to the person's next free email slot.
Response
Batch create response, ordered to match the input persons.
Show child attributes
Show child attributes