curl --request PATCH \
--url https://app.thinnest.ai/api/v1/contacts/{id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"consent": {
"status": "withdrawn",
"source": "Replied STOP on WhatsApp, 6 Oct"
},
"tags": [
"sky-towers"
]
}
'const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
consent: {status: 'withdrawn', source: 'Replied STOP on WhatsApp, 6 Oct'},
tags: ['sky-towers']
})
};
fetch('https://app.thinnest.ai/api/v1/contacts/{id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://app.thinnest.ai/api/v1/contacts/{id}"
payload = {
"consent": {
"status": "withdrawn",
"source": "Replied STOP on WhatsApp, 6 Oct"
},
"tags": ["sky-towers"]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text){
"id": "cust_4e7b1c9d-2a6f-4385-b0d2-9f6a3e8c1b74",
"phone": "919876543210",
"name": "Asha Rao",
"email": "asha.rao@gmail.com",
"externalId": "LSQ-42",
"tags": [
"sky-towers"
],
"language": "Hindi",
"note": "Wants a 3BHK, east-facing, ready by March.",
"consent": {
"status": "withdrawn",
"source": "Replied STOP on WhatsApp, 6 Oct",
"recordedAt": "2026-10-06T11:05:37.640Z"
},
"source": "api",
"acquiredFrom": null,
"createdAt": "2026-09-17T09:40:02.311Z"
}{
"error": "Nothing to change — the body named no field this API knows."
}{
"error": "Send a valid API key as `Authorization: Bearer <key>`."
}{
"error": "This API key is read-only: it can read everything but change nothing."
}{
"error": "That contact was not found."
}{
"error": "Another contact already has that `phone` or `externalId`."
}{
"error": "Over 240 requests a minute. Slow down and retry."
}Update Contact
Changes any of the fields Create Contact takes, consent included; fields you leave out keep their values, and null clears a field. tags replaces the whole list. A contact whose consent is withdrawn is never sent marketing, enrolled in a sequence or put in a campaign. A build key may do this.
curl --request PATCH \
--url https://app.thinnest.ai/api/v1/contacts/{id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"consent": {
"status": "withdrawn",
"source": "Replied STOP on WhatsApp, 6 Oct"
},
"tags": [
"sky-towers"
]
}
'const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
consent: {status: 'withdrawn', source: 'Replied STOP on WhatsApp, 6 Oct'},
tags: ['sky-towers']
})
};
fetch('https://app.thinnest.ai/api/v1/contacts/{id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://app.thinnest.ai/api/v1/contacts/{id}"
payload = {
"consent": {
"status": "withdrawn",
"source": "Replied STOP on WhatsApp, 6 Oct"
},
"tags": ["sky-towers"]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text){
"id": "cust_4e7b1c9d-2a6f-4385-b0d2-9f6a3e8c1b74",
"phone": "919876543210",
"name": "Asha Rao",
"email": "asha.rao@gmail.com",
"externalId": "LSQ-42",
"tags": [
"sky-towers"
],
"language": "Hindi",
"note": "Wants a 3BHK, east-facing, ready by March.",
"consent": {
"status": "withdrawn",
"source": "Replied STOP on WhatsApp, 6 Oct",
"recordedAt": "2026-10-06T11:05:37.640Z"
},
"source": "api",
"acquiredFrom": null,
"createdAt": "2026-09-17T09:40:02.311Z"
}{
"error": "Nothing to change — the body named no field this API knows."
}{
"error": "Send a valid API key as `Authorization: Bearer <key>`."
}{
"error": "This API key is read-only: it can read everything but change nothing."
}{
"error": "That contact was not found."
}{
"error": "Another contact already has that `phone` or `externalId`."
}{
"error": "Over 240 requests a minute. Slow down and retry."
}Authorizations
Your API key (ta_live_…) from Settings → API keys, sent as Authorization: Bearer <key>. Keep it on a server: it can message every customer you have. A key is full, build or read-only; a request its level does not allow is refused with 403.
Headers
Developers only: the customer workspace this request acts in — its org_… id from POST /customers. Leave it out to act in your own workspace.
"org_3fKq9TzQ1mN8vB2xR7cLpA"
Path Parameters
The contact's id (cust_…).
Body
Any subset of the create fields; at least one. Fields left out keep their values.
A new number for them. Normalised as on create.
Their name, or null to clear it.
120Their email, or null to clear it.
200Your CRM's id, or null to clear it.
200Replaces the whole list. Normalised as on create.
20The language to write to them in, auto, or null.
auto, English, Hindi, Assamese, Bengali, Bodo, Dogri, French, German, Gujarati, Indonesian, Italian, Japanese, Kannada, Kashmiri, Konkani, Korean, Maithili, Malayalam, Manipuri, Marathi, Nepali, Odia, Polish, Portuguese, Punjabi, Russian, Sanskrit, Santali, Sindhi, Spanish, Swahili, Tamil, Telugu, Thai, Turkish, Urdu, Vietnamese, null Free-text notes, or null to clear them.
2000Their word on marketing messages, recorded with the time and where it came from — what a regulator asks.
Show child attributes
Show child attributes
Response
The contact as saved.
The contact's id (cust_…).
"cust_4e7b1c9d-2a6f-4385-b0d2-9f6a3e8c1b74"
Digits only, with the country code — 919876543210.
Their name.
Their email, lower-cased.
Your CRM's id for them, unique in your workspace.
Their tags, lower-case and hyphenated.
The language to write to them in, e.g. Hindi, or auto to match theirs.
Free-text notes.
Show child attributes
Show child attributes
How you came to have them: agent (they spoke to an agent), import, api, whatsapp_app.
Where the lead came from, when recorded.
When the contact was created.