curl --request PATCH \
--url https://api.reply.io/v3/contacts/{id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"firstName": "Jane",
"company": "New Company Inc",
"stage": "Interested",
"ownerUserId": 55,
"customFields": [
{
"id": 1,
"value": "Updated Value"
},
{
"name": "leadSource",
"value": "Referral"
}
]
}
'import requests
url = "https://api.reply.io/v3/contacts/{id}"
payload = {
"firstName": "Jane",
"company": "New Company Inc",
"stage": "Interested",
"ownerUserId": 55,
"customFields": [
{
"id": 1,
"value": "Updated Value"
},
{
"name": "leadSource",
"value": "Referral"
}
]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
firstName: 'Jane',
company: 'New Company Inc',
stage: 'Interested',
ownerUserId: 55,
customFields: [{id: 1, value: 'Updated Value'}, {name: 'leadSource', value: 'Referral'}]
})
};
fetch('https://api.reply.io/v3/contacts/{id}', 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.reply.io/v3/contacts/{id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'firstName' => 'Jane',
'company' => 'New Company Inc',
'stage' => 'Interested',
'ownerUserId' => 55,
'customFields' => [
[
'id' => 1,
'value' => 'Updated Value'
],
[
'name' => 'leadSource',
'value' => 'Referral'
]
]
]),
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.reply.io/v3/contacts/{id}"
payload := strings.NewReader("{\n \"firstName\": \"Jane\",\n \"company\": \"New Company Inc\",\n \"stage\": \"Interested\",\n \"ownerUserId\": 55,\n \"customFields\": [\n {\n \"id\": 1,\n \"value\": \"Updated Value\"\n },\n {\n \"name\": \"leadSource\",\n \"value\": \"Referral\"\n }\n ]\n}")
req, _ := http.NewRequest("PATCH", 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.patch("https://api.reply.io/v3/contacts/{id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"firstName\": \"Jane\",\n \"company\": \"New Company Inc\",\n \"stage\": \"Interested\",\n \"ownerUserId\": 55,\n \"customFields\": [\n {\n \"id\": 1,\n \"value\": \"Updated Value\"\n },\n {\n \"name\": \"leadSource\",\n \"value\": \"Referral\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.reply.io/v3/contacts/{id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"firstName\": \"Jane\",\n \"company\": \"New Company Inc\",\n \"stage\": \"Interested\",\n \"ownerUserId\": 55,\n \"customFields\": [\n {\n \"id\": 1,\n \"value\": \"Updated Value\"\n },\n {\n \"name\": \"leadSource\",\n \"value\": \"Referral\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"id": 12345,
"email": "john.doe@company.com",
"firstName": "John",
"lastName": "Doe",
"title": "Senior Product Manager",
"company": "Tech Solutions Inc",
"domain": "company.com",
"companySize": "oneThousand",
"city": "San Francisco",
"state": "CA",
"country": "United States",
"timeZoneId": "America/Los_Angeles",
"phone": "+1-415-555-0123",
"phoneStatus": "valid",
"linkedInUrl": "https://www.linkedin.com/in/johndoe",
"linkedInSalesNavigatorUrl": "https://www.linkedin.com/sales/profile/123456",
"linkedInRecruiterUrl": "https://www.linkedin.com/recruiter/profile/789012",
"industry": "Software & Technology",
"notes": "Met at SaaS Conference 2024",
"ownerUserId": 42,
"accountId": 100,
"isOptedOut": false,
"callStatus": "toCall",
"meetingStatus": "none",
"stage": "Interested",
"addingDate": "2024-03-08T10:00:00+00:00",
"createdAt": "2024-03-08T10:00:00",
"lastModifiedAt": "2024-03-10T15:30:00",
"customFields": [
{
"key": "leadSource",
"value": "Conference"
},
{
"key": "budget",
"value": "100k-250k"
}
]
}Update a contact
contacts:write scope (or a broader one that includes it).
Change specific fields on a contact without resending the whole record. Omitted fields stay as they are; null clears one.
curl --request PATCH \
--url https://api.reply.io/v3/contacts/{id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"firstName": "Jane",
"company": "New Company Inc",
"stage": "Interested",
"ownerUserId": 55,
"customFields": [
{
"id": 1,
"value": "Updated Value"
},
{
"name": "leadSource",
"value": "Referral"
}
]
}
'import requests
url = "https://api.reply.io/v3/contacts/{id}"
payload = {
"firstName": "Jane",
"company": "New Company Inc",
"stage": "Interested",
"ownerUserId": 55,
"customFields": [
{
"id": 1,
"value": "Updated Value"
},
{
"name": "leadSource",
"value": "Referral"
}
]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
firstName: 'Jane',
company: 'New Company Inc',
stage: 'Interested',
ownerUserId: 55,
customFields: [{id: 1, value: 'Updated Value'}, {name: 'leadSource', value: 'Referral'}]
})
};
fetch('https://api.reply.io/v3/contacts/{id}', 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.reply.io/v3/contacts/{id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'firstName' => 'Jane',
'company' => 'New Company Inc',
'stage' => 'Interested',
'ownerUserId' => 55,
'customFields' => [
[
'id' => 1,
'value' => 'Updated Value'
],
[
'name' => 'leadSource',
'value' => 'Referral'
]
]
]),
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.reply.io/v3/contacts/{id}"
payload := strings.NewReader("{\n \"firstName\": \"Jane\",\n \"company\": \"New Company Inc\",\n \"stage\": \"Interested\",\n \"ownerUserId\": 55,\n \"customFields\": [\n {\n \"id\": 1,\n \"value\": \"Updated Value\"\n },\n {\n \"name\": \"leadSource\",\n \"value\": \"Referral\"\n }\n ]\n}")
req, _ := http.NewRequest("PATCH", 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.patch("https://api.reply.io/v3/contacts/{id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"firstName\": \"Jane\",\n \"company\": \"New Company Inc\",\n \"stage\": \"Interested\",\n \"ownerUserId\": 55,\n \"customFields\": [\n {\n \"id\": 1,\n \"value\": \"Updated Value\"\n },\n {\n \"name\": \"leadSource\",\n \"value\": \"Referral\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.reply.io/v3/contacts/{id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"firstName\": \"Jane\",\n \"company\": \"New Company Inc\",\n \"stage\": \"Interested\",\n \"ownerUserId\": 55,\n \"customFields\": [\n {\n \"id\": 1,\n \"value\": \"Updated Value\"\n },\n {\n \"name\": \"leadSource\",\n \"value\": \"Referral\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"id": 12345,
"email": "john.doe@company.com",
"firstName": "John",
"lastName": "Doe",
"title": "Senior Product Manager",
"company": "Tech Solutions Inc",
"domain": "company.com",
"companySize": "oneThousand",
"city": "San Francisco",
"state": "CA",
"country": "United States",
"timeZoneId": "America/Los_Angeles",
"phone": "+1-415-555-0123",
"phoneStatus": "valid",
"linkedInUrl": "https://www.linkedin.com/in/johndoe",
"linkedInSalesNavigatorUrl": "https://www.linkedin.com/sales/profile/123456",
"linkedInRecruiterUrl": "https://www.linkedin.com/recruiter/profile/789012",
"industry": "Software & Technology",
"notes": "Met at SaaS Conference 2024",
"ownerUserId": 42,
"accountId": 100,
"isOptedOut": false,
"callStatus": "toCall",
"meetingStatus": "none",
"stage": "Interested",
"addingDate": "2024-03-08T10:00:00+00:00",
"createdAt": "2024-03-08T10:00:00",
"lastModifiedAt": "2024-03-10T15:30:00",
"customFields": [
{
"key": "leadSource",
"value": "Conference"
},
{
"key": "budget",
"value": "100k-250k"
}
]
}Authorizations
Authenticate every request with a Bearer token. Pass your Reply API key in the
Authorization header:
Authorization: Bearer <your-api-key>
Get your API key from the Reply dashboard: Settings → API Key.
Path Parameters
Contact ID
Body
Request model for updating an existing contact. All fields are optional — only include the fields you want to change (PATCH semantics).
Primary email address
First name
Last name
Phone number
Secondary phone number
Job title
Organization name
Organization employee count range
Empty, SelfEmployed, Ten, Fifty, TwoHundred, FiveHundred, OneThousand, FiveThousand, TenThousand, OverTenThousand Business sector
City of residence
State/province
Country
Timezone identifier
LinkedIn profile URL
Sales Navigator URL
Recruiter URL
Additional information
Set or clear the contact's opted-out flag (suppresses all communications when true).
Contact-level call status. Pass none to clear.
none, toCall, called Contact-level meeting status. Pass none to clear.
none, meetingBooked Move the contact to this stage, named as the team has it configured. Matching ignores case and separators, so Not interested, notInterested and not-interested all select the same stage. Pass null to clear the stage; null is the only value that clears it. A name matching no stage — including an empty or blank one — fails the request with 400 and lists the names that are available.
Change the owner of this contact. The new owner must be a member of the caller's team; otherwise the request fails with 404.
ID of the contact account to link this contact to
Custom fields to update. Fields can be identified by id or name.
Unlike the response model which uses key/value, the patch model accepts id, name, and value.
Show child attributes
Show child attributes
Response
Contact updated successfully
Contact response model
Unique ID
Primary email address
Email domain (derived from email)
First name
Last name
Phone number
Job title
Organization name
Organization employee count range
empty, selfEmployed, ten, fifty, twoHundred, fiveHundred, oneThousand, fiveThousand, tenThousand, overTenThousand Business sector
City of residence
State/province
Country
Timezone identifier
LinkedIn profile URL
Sales Navigator URL
Recruiter URL
Phone validation status
pending, invalid, valid, validationFailed, notValidated Additional information
ID of the user who owns this contact
ID of the contact account this contact belongs to
Whether the contact has opted out of communications
Contact-level call status
none, toCall, called Contact-level meeting status
none, meetingBooked Pipeline stage the contact sits in, by name, or null when it has none. A workspace has eight stages, named New, Engaging, Replied, Interested, Not interested, Unresponsive, Do not contact and Bad contact info unless the team has renamed them. GET /v3/settings returns the current names under contacts.contactStages.
Date the contact was added
Creation timestamp
Last modification timestamp
User-defined fields. An entry is returned when it holds a value, or when an enrichment has left a status on it — so a field being enriched appears with a null value and an enrichmentStatus.
Show child attributes
Show child attributes