Update Lead
curl --request PATCH \
--url https://app.tuco.ai/api/leads/{id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"tags": [
"<string>"
],
"firstName": "<string>",
"lastName": "<string>",
"email": "<string>",
"companyName": "<string>",
"notes": "<string>",
"customFields": {}
}
'import requests
url = "https://app.tuco.ai/api/leads/{id}"
payload = {
"tags": ["<string>"],
"firstName": "<string>",
"lastName": "<string>",
"email": "<string>",
"companyName": "<string>",
"notes": "<string>",
"customFields": {}
}
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({
tags: ['<string>'],
firstName: '<string>',
lastName: '<string>',
email: '<string>',
companyName: '<string>',
notes: '<string>',
customFields: {}
})
};
fetch('https://app.tuco.ai/api/leads/{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://app.tuco.ai/api/leads/{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([
'tags' => [
'<string>'
],
'firstName' => '<string>',
'lastName' => '<string>',
'email' => '<string>',
'companyName' => '<string>',
'notes' => '<string>',
'customFields' => [
]
]),
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://app.tuco.ai/api/leads/{id}"
payload := strings.NewReader("{\n \"tags\": [\n \"<string>\"\n ],\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"email\": \"<string>\",\n \"companyName\": \"<string>\",\n \"notes\": \"<string>\",\n \"customFields\": {}\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://app.tuco.ai/api/leads/{id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"tags\": [\n \"<string>\"\n ],\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"email\": \"<string>\",\n \"companyName\": \"<string>\",\n \"notes\": \"<string>\",\n \"customFields\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.tuco.ai/api/leads/{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 \"tags\": [\n \"<string>\"\n ],\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"email\": \"<string>\",\n \"companyName\": \"<string>\",\n \"notes\": \"<string>\",\n \"customFields\": {}\n}"
response = http.request(request)
puts response.read_bodyCampaigns
Update Lead
Update a lead’s details or tags. Tags are what trigger your bots — a goal bot wakes on the tags listed in its includeTags.
PATCH
/
api
/
leads
/
{id}
Update Lead
curl --request PATCH \
--url https://app.tuco.ai/api/leads/{id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"tags": [
"<string>"
],
"firstName": "<string>",
"lastName": "<string>",
"email": "<string>",
"companyName": "<string>",
"notes": "<string>",
"customFields": {}
}
'import requests
url = "https://app.tuco.ai/api/leads/{id}"
payload = {
"tags": ["<string>"],
"firstName": "<string>",
"lastName": "<string>",
"email": "<string>",
"companyName": "<string>",
"notes": "<string>",
"customFields": {}
}
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({
tags: ['<string>'],
firstName: '<string>',
lastName: '<string>',
email: '<string>',
companyName: '<string>',
notes: '<string>',
customFields: {}
})
};
fetch('https://app.tuco.ai/api/leads/{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://app.tuco.ai/api/leads/{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([
'tags' => [
'<string>'
],
'firstName' => '<string>',
'lastName' => '<string>',
'email' => '<string>',
'companyName' => '<string>',
'notes' => '<string>',
'customFields' => [
]
]),
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://app.tuco.ai/api/leads/{id}"
payload := strings.NewReader("{\n \"tags\": [\n \"<string>\"\n ],\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"email\": \"<string>\",\n \"companyName\": \"<string>\",\n \"notes\": \"<string>\",\n \"customFields\": {}\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://app.tuco.ai/api/leads/{id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"tags\": [\n \"<string>\"\n ],\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"email\": \"<string>\",\n \"companyName\": \"<string>\",\n \"notes\": \"<string>\",\n \"customFields\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.tuco.ai/api/leads/{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 \"tags\": [\n \"<string>\"\n ],\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"email\": \"<string>\",\n \"companyName\": \"<string>\",\n \"notes\": \"<string>\",\n \"customFields\": {}\n}"
response = http.request(request)
puts response.read_bodyTags are the wiring. A goal bot wakes on the tags in its
includeTags, so adding a tag here is
how you put a lead in front of a bot from a script. See
PATCH /api/goal-bots/{id}.Authentication
Pass your workspace API key as a Bearer token, or use a Clerk session token.Authorization: Bearer tuco_xxxxxxxxxxxxx
Path parameter
string
required
The lead id. A malformed id returns
400 Invalid lead id.Request body
Exactly seven fields are writable. Anything else in the body is ignored — includingphone,
which is set when the lead is created and cannot be changed here.
string[]
Replaces the whole tag array — send the complete set, not a delta. Values are trimmed,
de-duplicated, and blanks are dropped. Maximum 50 tags.A non-array returns
400 tags must be an array of strings; more than 50 returns
400 A lead can carry at most 50 tags.string
First name. Trimmed. Pass
null to clear it to an empty string.string
Last name. Trimmed. Pass
null to clear it.string
Email address. Trimmed. Pass
null to clear it.string
Company name. Trimmed. Pass
null to clear it.string
Free-text notes about the lead. Trimmed. Pass
null to clear it.Not inert — your bots read this. A task agent sees it through get_lead_details, and
campaign personalization can reference it, so anything you write here can end up shaping a reply.object
Replaces the whole custom-field object. Pass
null to clear it to {}. An array or a
non-object returns 400 customFields must be an object.Read the keys already in use with GET /api/leads/custom-field-keys.A body with none of these six fields returns
400 — there is nothing to update. An empty
object {} counts as nothing.Example — tag a lead so a bot picks it up
curl -X PATCH "https://app.tuco.ai/api/leads/6ac3ea0d71adb32c88d56219" \
-H "Authorization: Bearer tuco_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "tags": ["demo-requested", "enterprise"] }'
Example — update details
curl -X PATCH "https://app.tuco.ai/api/leads/6ac3ea0d71adb32c88d56219" \
-H "Authorization: Bearer tuco_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"firstName": "Jane",
"companyName": "Acme Corp",
"customFields": { "industry": "Technology" }
}'
Success (200 OK)
Returns the full updated lead — the same shape as
GET /api/leads/{id}.
{
"lead": {
"_id": "6ac3ea0d71adb32c88d56219",
"firstName": "Jane",
"lastName": "Doe",
"phone": "+14152004145",
"email": "jane@example.com",
"companyName": "Acme Corp",
"tags": ["demo-requested", "enterprise"],
"customFields": { "industry": "Technology" },
"updatedAt": "2026-10-05T18:48:02.917Z"
}
}
Errors
| Status | When | Body |
|---|---|---|
400 | Malformed {id} | { "error": "Invalid lead id" } |
400 | Body is not a JSON object | { "error": "Body must be a JSON object" } |
400 | No editable field supplied | { "error": "Nothing to update. Editable fields: firstName, lastName, email, companyName, notes, tags, customFields" } |
400 | tags not an array of strings | { "error": "tags must be an array of strings" } |
400 | More than 50 tags | { "error": "A lead can carry at most 50 tags" } |
400 | customFields not an object | { "error": "customFields must be an object" } |
400 | A string field was sent as a number or object | { "error": "firstName must be a string" } |
401 | Missing or invalid API key | { "error": "Unauthorized" } |
404 | No lead with that id in your workspace | { "error": "Lead not found" } |