Contacts
Create, retrieve, update, search and delete the people your business talks to.
A contact is a person or company your business talks to. Ody creates contacts automatically the first time an unknown number texts or calls in. You can also create them yourself, for example to sync a CRM. Every conversation thread is attached to the contact whose phone number it involves.
#The contact object
iduuid — Unique identifier.firstNamestring | null — Given name.lastNamestring | null — Family name.companystring | null — Company or organisation.phonesarray of {value, label?} — Phone numbers in E.164. The first one is the primary number.emailsarray of {value, label?} — Email addresses.notesarray of strings — Free-text notes, oldest first.propertiesobject — Your own string key/value fields (for example{"crmId": "A-1042"}).tagsarray of strings — Labels used for filtering in the app.sourcestring | null — How the contact was created:manual(the app or this API),inbound(auto-created from a text or call), or the integration it was synced from (for examplehubspot).createdAttimestamp — ISO 8601.
List endpoints return a lighter contact summary: id, firstName, lastName, company, phone (the primary number) and createdAt.
{
"id": "8b1d6f3e-2c4a-4e1b-9f0d-7a5c3e2b1a90",
"firstName": "Ada",
"lastName": "Lovelace",
"company": "Analytical Engines",
"phones": [{ "value": "+15125550123", "label": "mobile" }],
"emails": [{ "value": "[email protected]" }],
"notes": ["Prefers texts after 5pm"],
"properties": { "crmId": "A-1042" },
"tags": ["vip"],
"source": "manual",
"createdAt": "2026-10-14T18:02:11.000Z"
}#List contacts
/v1/api/contactsReturns the workspace's contacts, newest first. Pass q to run a full-text search over names, company, phones and emails. Results are cursor-paginated.
Scope: contacts:read
Query parameters
qstring — Full-text search query.limitinteger — 1–200, default 50.cursorstring —nextCursorfrom the previous page.
Returns { contacts: ContactSummary[], nextCursor: string | null }.
curl "https://api.ody.co/v1/api/contacts?q=ada&limit=20" \
-H "Authorization: Bearer $ODY_API_KEY"const { contacts, nextCursor } = await ody.contacts.list({ q: "ada", limit: 20 });page = client.contacts.list(q="ada", limit=20)
contacts, next_cursor = page["contacts"], page["nextCursor"]{
"contacts": [
{
"id": "8b1d6f3e-2c4a-4e1b-9f0d-7a5c3e2b1a90",
"firstName": "Ada",
"lastName": "Lovelace",
"company": "Analytical Engines",
"phone": "+15125550123",
"createdAt": "2026-10-14T18:02:11.000Z"
}
],
"nextCursor": null
}#Create a contact
/v1/api/contactsCreates a contact. You must send at least one identity field: firstName, lastName, company, phone/phones or email/emails.
If the phone matches a contact Ody auto-created from an inbound text or call (one that has no name yet), that contact is adopted and updated instead of duplicated. Its existing threads keep their history.
Scope: contacts:write
Body
firstNamestringlastNamestringcompanystringphonestring — Convenience field, merged intophones. E.164.emailstring — Convenience field, merged intoemails.phonesarray — Strings, or{ "value", "label" }objects.emailsarray — Strings, or{ "value", "label" }objects.notesarray of stringspropertiesobject — String values only.tagsarray of strings
Returns 201 with { contact }.
curl https://api.ody.co/v1/api/contacts \
-H "Authorization: Bearer $ODY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"firstName": "Ada",
"lastName": "Lovelace",
"phone": "+15125550123",
"properties": { "crmId": "A-1042" },
"tags": ["vip"]
}'const contact = await ody.contacts.create({
firstName: "Ada",
lastName: "Lovelace",
phone: "+15125550123",
properties: { crmId: "A-1042" },
tags: ["vip"],
});contact = client.contacts.create(
first_name="Ada",
last_name="Lovelace",
phone="+15125550123",
properties={"crmId": "A-1042"},
tags=["vip"],
){
"contact": {
"id": "8b1d6f3e-2c4a-4e1b-9f0d-7a5c3e2b1a90",
"firstName": "Ada",
"lastName": "Lovelace",
"company": null,
"phones": [{ "value": "+15125550123" }],
"emails": [],
"notes": [],
"properties": { "crmId": "A-1042" },
"tags": ["vip"],
"source": "manual",
"createdAt": "2026-10-14T18:02:11.000Z"
}
}#Retrieve a contact
/v1/api/contacts/:idReturns the full contact.
Scope: contacts:read
Returns { contact }, or 404 not_found.
curl https://api.ody.co/v1/api/contacts/8b1d6f3e-2c4a-4e1b-9f0d-7a5c3e2b1a90 \
-H "Authorization: Bearer $ODY_API_KEY"const contact = await ody.contacts.get("8b1d6f3e-2c4a-4e1b-9f0d-7a5c3e2b1a90");contact = client.contacts.get("8b1d6f3e-2c4a-4e1b-9f0d-7a5c3e2b1a90")#Update a contact
/v1/api/contacts/:idChanges only the fields you send. Array and object fields (phones, emails, notes, properties, tags) are replaced wholesale. To add a tag, read the contact, append to the array, then send the whole array back.
Scope: contacts:write
Body: any field from Create a contact. Send null to clear firstName, lastName or company.
Returns { contact }.
curl -X PATCH https://api.ody.co/v1/api/contacts/8b1d6f3e-2c4a-4e1b-9f0d-7a5c3e2b1a90 \
-H "Authorization: Bearer $ODY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"company":"Analytical Engines","tags":["vip","renewal-q4"]}'await ody.contacts.update("8b1d6f3e-2c4a-4e1b-9f0d-7a5c3e2b1a90", {
company: "Analytical Engines",
tags: ["vip", "renewal-q4"],
});client.contacts.update(
"8b1d6f3e-2c4a-4e1b-9f0d-7a5c3e2b1a90",
company="Analytical Engines",
tags=["vip", "renewal-q4"],
)#Delete a contact
/v1/api/contacts/:idPermanently deletes the contact. Conversation threads are not deleted: they stay in the inbox under the raw phone number, and a new text from that number creates a fresh contact.
Scope: contacts:write
Returns { "deleted": true }.
curl -X DELETE https://api.ody.co/v1/api/contacts/8b1d6f3e-2c4a-4e1b-9f0d-7a5c3e2b1a90 \
-H "Authorization: Bearer $ODY_API_KEY"await ody.contacts.delete("8b1d6f3e-2c4a-4e1b-9f0d-7a5c3e2b1a90");client.contacts.delete("8b1d6f3e-2c4a-4e1b-9f0d-7a5c3e2b1a90")