curl --request PATCH \
--url https://api.teamfollowup.ai/api/contacts/{contactId} \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"locationId": "loc_123",
"isInternal": true
}'import requests
url = "https://api.teamfollowup.ai/api/contacts/{contactId}"
payload = {
"locationId": "loc_123",
"isInternal": True
}
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({locationId: 'loc_123', isInternal: true})
};
fetch('https://api.teamfollowup.ai/api/contacts/{contactId}', 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.teamfollowup.ai/api/contacts/{contactId}",
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([
'locationId' => 'loc_123',
'isInternal' => true
]),
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.teamfollowup.ai/api/contacts/{contactId}"
payload := strings.NewReader("{\n \"locationId\": \"loc_123\",\n \"isInternal\": true\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.teamfollowup.ai/api/contacts/{contactId}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"locationId\": \"loc_123\",\n \"isInternal\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.teamfollowup.ai/api/contacts/{contactId}")
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 \"locationId\": \"loc_123\",\n \"isInternal\": true\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"data": {
"contactId": "contact_123",
"isInternal": true
},
"actionResults": {
"isInternal": "saved"
}
}{
"success": false,
"error": "VALIDATION",
"code": "VALIDATION",
"message": "locationId is required."
}{
"success": false,
"error": "UNAUTHORIZED",
"code": "UNAUTHORIZED",
"message": "Unauthorized"
}{
"success": false,
"error": "FORBIDDEN",
"code": "FORBIDDEN",
"message": "Access denied to this project"
}{
"success": false,
"error": "NOT_FOUND",
"code": "NOT_FOUND",
"message": "Contact not found in the selected project."
}{
"success": false,
"error": "AMBIGUOUS_CONTACT",
"code": "AMBIGUOUS_CONTACT",
"message": "A phone number matches multiple contacts. Use a contact ID."
}{
"success": false,
"error": "Unauthorized",
"message": "Authentication required."
}{
"success": false,
"error": "INTERNAL_ERROR",
"code": "INTERNAL_ERROR",
"message": "The contact could not be read."
}{
"success": false,
"error": "CONTACT_UPDATE_INCOMPLETE",
"message": "The do-not-call flag was saved; the internal mark could not be confirmed.",
"actionResults": {
"doNotCall": "saved",
"isInternal": "unconfirmed"
}
}Update contact
Update calling controls on one contact. Send only the fields to change: doNotCall stops or resumes AI calling through the connected CRM; isInternal marks or clears your own test contact. Other fields are unchanged. CRM profile fields such as name and email are managed in your CRM. locationId and optional idType belong in the body. Requires contacts:write and an agency administrator or project user role.
The two controls save to different services. If a save is interrupted, a 502 CONTACT_UPDATE_INCOMPLETE response includes actionResults with saved and unconfirmed fields. Do not replay the entire request blindly.
Required API key scope: contacts:write.
curl --request PATCH \
--url https://api.teamfollowup.ai/api/contacts/{contactId} \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"locationId": "loc_123",
"isInternal": true
}'import requests
url = "https://api.teamfollowup.ai/api/contacts/{contactId}"
payload = {
"locationId": "loc_123",
"isInternal": True
}
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({locationId: 'loc_123', isInternal: true})
};
fetch('https://api.teamfollowup.ai/api/contacts/{contactId}', 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.teamfollowup.ai/api/contacts/{contactId}",
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([
'locationId' => 'loc_123',
'isInternal' => true
]),
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.teamfollowup.ai/api/contacts/{contactId}"
payload := strings.NewReader("{\n \"locationId\": \"loc_123\",\n \"isInternal\": true\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.teamfollowup.ai/api/contacts/{contactId}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"locationId\": \"loc_123\",\n \"isInternal\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.teamfollowup.ai/api/contacts/{contactId}")
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 \"locationId\": \"loc_123\",\n \"isInternal\": true\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"data": {
"contactId": "contact_123",
"isInternal": true
},
"actionResults": {
"isInternal": "saved"
}
}{
"success": false,
"error": "VALIDATION",
"code": "VALIDATION",
"message": "locationId is required."
}{
"success": false,
"error": "UNAUTHORIZED",
"code": "UNAUTHORIZED",
"message": "Unauthorized"
}{
"success": false,
"error": "FORBIDDEN",
"code": "FORBIDDEN",
"message": "Access denied to this project"
}{
"success": false,
"error": "NOT_FOUND",
"code": "NOT_FOUND",
"message": "Contact not found in the selected project."
}{
"success": false,
"error": "AMBIGUOUS_CONTACT",
"code": "AMBIGUOUS_CONTACT",
"message": "A phone number matches multiple contacts. Use a contact ID."
}{
"success": false,
"error": "Unauthorized",
"message": "Authentication required."
}{
"success": false,
"error": "INTERNAL_ERROR",
"code": "INTERNAL_ERROR",
"message": "The contact could not be read."
}{
"success": false,
"error": "CONTACT_UPDATE_INCOMPLETE",
"message": "The do-not-call flag was saved; the internal mark could not be confirmed.",
"actionResults": {
"doNotCall": "saved",
"isInternal": "unconfirmed"
}
}Authorizations
Send your API key as Authorization: Bearer YOUR_API_KEY.
Path Parameters
The contactId returned by List contacts. For contacts without one, use an encoded international phone number and idType=phone. This is not the list row id.
1