Contacts
Create-or-merge contacts by phone or email, and read them back.
A contact is identified by its addresses — a phone number, an email, or both. That's what makes pushing idempotent: send the same person twice and you update one record instead of minting a twin.
The contact object
{
"id": "k97c2fq0v6bz8kj3n1xw5d4rhm",
"name": "Tyler West",
"phone": "+13232500210",
"email": "tyler@example.com",
"notes": "Wants the XLT",
"tags": ["hot lead", "f-150"],
"createdAt": 1756640000000
}Phones are normalized to E.164 (+1…); emails are lowercased; tags are
lowercased and capped at 16 per contact.
Create or merge a contact
POST /api/v1/contactscurl https://api.inboxtender.com/api/v1/contacts \
-H "Authorization: Bearer it_live_…" \
-H "Content-Type: application/json" \
-d '{
"name": "Tyler West",
"phone": "(323) 250-0210",
"email": "tyler@example.com",
"tags": ["hot lead"],
"notes": "Wants the XLT"
}'| Field | Type | Notes |
|---|---|---|
phone | string | 10 US digits or full E.164. At least one of phone / email is required. |
email | string | Lowercased on save. |
name | string | Up to 120 characters. |
notes | string | Up to 500 characters. |
tags | string[] | Up to 16, each up to 30 characters. |
Answers 201 when a contact was created, 200 when it merged into an
existing one; both return { "contact": …, "created": true|false }.
Merge rules
- A matching phone or email updates the existing contact and adds the other address when it is unclaimed. Email-only contacts are supported.
- If the phone and email belong to different contacts, the request returns
409with error codeidentifier_conflict. Neither contact is changed. - Each normalized email and phone belongs to one contact per workspace.
- Merges fill blanks only: a
nameornotesyour team already wrote is never overwritten. Tags are added, never removed. - Re-running the same request is always safe.
List contacts
GET /api/v1/contacts?limit=50&cursor=…&tag=hot%20leadNewest first. limit is 1–100 (default 50). Page with the returned
nextCursor until it comes back null. tag filters within each page, so
a filtered page can come back short while nextCursor still advances.
{ "contacts": [ … ], "nextCursor": "…" }Get one contact
GET /api/v1/contacts/:idAnswers { "contact": … }, or 404 if the id is unknown — including ids
that belong to a different workspace.