Create / Upsert Contact
Create a new contact or upsert on email collision.
/io/contacts/contactsCreates a contact. Add ?upsert=1 to upsert on (user_id, email) instead of returning 409 on duplicate.
Request body
emailstringbodyrequiredThe contact's email address (RFC 5322, max 254 chars).
firstNamestringbodyFirst name (max 100 chars).
lastNamestringbodyLast name (max 100 chars).
phonestringbodyPhone number including country code (max 50 chars).
companystringbodyCompany or organisation name (max 150 chars).
jobTitlestringbodyJob title / designation (max 100 chars).
socialProfilesobjectbodySocial-network handles or profile URLs. Allowed keys: linkedin, twitter, instagram, telegram, whatsapp, reddit, youtube. Each value is a string ≤ 500 chars or null. Unknown keys are silently dropped.
statusstringbodysubscribed (default) | unsubscribed | bounced | complained.
customFieldsobjectbodyArbitrary key-value metadata (max 20 keys, 8 KB total JSON).
sourcestringbodyHow the contact was acquired (e.g. "import", "signup"). Max 100 chars.
workspaceIdstringbodyWorkspace UUID to scope the contact to.
{
"email": "[email protected]",
"firstName": "Bob",
"lastName": "Smith",
"phone": "+1 555 000 1234",
"company": "Acme Corp",
"jobTitle": "Product Manager",
"socialProfiles": {
"linkedin": "https://linkedin.com/in/bobsmith",
"twitter": "@bobsmith"
},
"status": "subscribed",
"source": "signup"
}{
"data": {
"id": "cnt_def456",
"email": "[email protected]",
"first_name": "Bob",
"last_name": "Smith",
"phone": "+1 555 000 1234",
"company": "Acme Corp",
"job_title": "Product Manager",
"social_profiles": {
"linkedin": "https://linkedin.com/in/bobsmith",
"twitter": "@bobsmith"
},
"status": "subscribed",
"lead_score": 0,
"created_at": "2026-06-11T12:00:00Z"
}
}Error cases
| Code | Error |
|---|---|
| 400 | A valid email is required |
| 400 | firstName must be 100 characters or fewer (and similar for other fields) |
| 400 | status must be subscribed, unsubscribed, bounced, or complained |
| 409 | A contact with this email already exists (plain insert only) |
| 429 | Too many requests — 200 writes per user per 60 seconds |