The Contacts API lets you store and organize the phone numbers you message. Contacts are tenant-scoped — each tenant has its own isolated contact book. Use contacts to track names, group recipients for campaigns, and avoid duplicate sends.
How Contacts Relate to Messaging
When you send a message via POST /v1/messages, senderZ checks whether the recipient already exists in your contacts. If the number is new, it counts against your daily new-contact quota. Messages to existing contacts are always unlimited, regardless of plan.
| Plan | New Contacts / Day | New Contacts / Month |
|---|---|---|
| Starter ($49/mo) | 10 | 250 |
| Growth ($249/mo) | 50 | 1,250 |
| Scale ($749/mo) | 500 | 10,000 |
Create a Contact
/v1/contacts Add a new contact to your tenant.
Parameters
phone_number string
Required
Phone number in E.164 format (e.g. +15551234567). Must be unique within your tenant.
name string Display name for the contact. Useful for template personalization with {{name}}.
email string Optional email address for the contact.
metadata object Arbitrary key-value pairs. Store custom fields like company, source, or notes.
group_ids string[] Array of contact group IDs to add this contact to at creation time.
curl -X POST https://api.senderz.com/v1/contacts \
-H "Authorization: Bearer sz_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "+15551234567",
"name": "Jane Smith",
"metadata": {
"company": "Acme Corp",
"source": "website_signup"
},
"group_ids": ["01JGRP001ABC"]
}' const res = await fetch('https://api.senderz.com/v1/contacts', {
method: 'POST',
headers: {
'Authorization': 'Bearer sz_live_YOUR_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
phone_number: '+15551234567',
name: 'Jane Smith',
metadata: { company: 'Acme Corp', source: 'website_signup' },
group_ids: ['01JGRP001ABC'],
}),
})
const data = await res.json() {
"data": {
"id": "01JCON123ABC",
"phone_number": "+15551234567",
"name": "Jane Smith",
"email": null,
"metadata": {
"company": "Acme Corp",
"source": "website_signup"
},
"groups": ["01JGRP001ABC"],
"created_at": "2026-04-15T10:30:00Z",
"updated_at": "2026-04-15T10:30:00Z"
}
} Deduplication
If you attempt to create a contact with a phone number that already exists in your tenant, the API returns 409 DUPLICATE_CONTACT with the existing contact’s ID. Use the update endpoint to modify an existing contact instead.
List Contacts
/v1/contacts Retrieve a paginated list of contacts.
Query Parameters
page number Page number, starting at 1. Defaults to 1.
limit number Results per page. Defaults to 25, maximum 100.
search string Search contacts by name or phone number. Partial matches are supported.
group_id string Filter to contacts in a specific group.
sort string Sort field. One of created_at, name, updated_at. Defaults to created_at.
order string Sort direction. asc or desc. Defaults to desc.
Example Request
curl "https://api.senderz.com/v1/contacts?search=Jane&limit=10" \
-H "Authorization: Bearer sz_live_YOUR_KEY"
{
"data": [
{
"id": "01JCON123ABC",
"phone_number": "+15551234567",
"name": "Jane Smith",
"email": null,
"metadata": { "company": "Acme Corp" },
"groups": ["01JGRP001ABC"],
"created_at": "2026-04-15T10:30:00Z",
"updated_at": "2026-04-15T10:30:00Z"
}
],
"meta": {
"total": 1,
"page": 1,
"limit": 10
}
} Get a Single Contact
/v1/contacts/:id Retrieve a contact by ID.
id string
Required
The ULID of the contact.
curl https://api.senderz.com/v1/contacts/01JCON123ABC \
-H "Authorization: Bearer sz_live_YOUR_KEY"
{
"data": {
"id": "01JCON123ABC",
"phone_number": "+15551234567",
"name": "Jane Smith",
"email": null,
"metadata": { "company": "Acme Corp", "source": "website_signup" },
"groups": ["01JGRP001ABC"],
"message_count": 47,
"last_messaged_at": "2026-04-14T18:22:00Z",
"created_at": "2026-04-15T10:30:00Z",
"updated_at": "2026-04-15T10:30:00Z"
}
} Update a Contact
/v1/contacts/:id Update an existing contact.
Only the fields you include in the request body are updated. Omitted fields remain unchanged.
name string Updated display name.
email string Updated email address.
metadata object Updated metadata. This replaces the entire metadata object — merge client-side if you want to preserve existing keys.
group_ids string[] Replace the contact’s group memberships entirely. Pass [] to remove from all groups.
curl -X PUT https://api.senderz.com/v1/contacts/01JCON123ABC \
-H "Authorization: Bearer sz_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Jane A. Smith",
"metadata": {
"company": "Acme Corp",
"source": "website_signup",
"tier": "enterprise"
}
}' const res = await fetch('https://api.senderz.com/v1/contacts/01JCON123ABC', {
method: 'PUT',
headers: {
'Authorization': 'Bearer sz_live_YOUR_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Jane A. Smith',
metadata: {
company: 'Acme Corp',
source: 'website_signup',
tier: 'enterprise',
},
}),
})
const data = await res.json() {
"data": {
"id": "01JCON123ABC",
"phone_number": "+15551234567",
"name": "Jane A. Smith",
"email": null,
"metadata": {
"company": "Acme Corp",
"source": "website_signup",
"tier": "enterprise"
},
"groups": ["01JGRP001ABC"],
"updated_at": "2026-04-15T11:00:00Z"
}
} Delete a Contact
/v1/contacts/:id Remove a contact permanently.
Deleting a contact removes it from all groups and removes its metadata. Message history referencing this contact’s phone number is retained for compliance purposes.
curl -X DELETE https://api.senderz.com/v1/contacts/01JCON123ABC \
-H "Authorization: Bearer sz_live_YOUR_KEY"
{
"data": {
"id": "01JCON123ABC",
"deleted": true
}
} Contact Groups
Contact groups let you organize recipients for campaigns and bulk sends. A contact can belong to multiple groups simultaneously.
Create a Group
/v1/contacts/groups Create a new contact group.
name string
Required
Group name (e.g. “VIP Customers”, “Newsletter Subscribers”).
description string Optional description of the group’s purpose.
curl -X POST https://api.senderz.com/v1/contacts/groups \
-H "Authorization: Bearer sz_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "VIP Customers", "description": "High-value accounts"}'
{
"data": {
"id": "01JGRP001ABC",
"name": "VIP Customers",
"description": "High-value accounts",
"member_count": 0,
"created_at": "2026-04-15T10:00:00Z"
}
} List Groups
/v1/contacts/groups List all contact groups for your tenant.
curl https://api.senderz.com/v1/contacts/groups \
-H "Authorization: Bearer sz_live_YOUR_KEY"
{
"data": [
{
"id": "01JGRP001ABC",
"name": "VIP Customers",
"description": "High-value accounts",
"member_count": 142,
"created_at": "2026-04-15T10:00:00Z"
},
{
"id": "01JGRP002DEF",
"name": "Newsletter",
"description": null,
"member_count": 2048,
"created_at": "2026-04-10T08:00:00Z"
}
]
} Add Members to a Group
/v1/contacts/groups/:id/members Add contacts to a group.
contact_ids string[]
Required
Array of contact IDs to add. Contacts already in the group are silently skipped.
curl -X POST https://api.senderz.com/v1/contacts/groups/01JGRP001ABC/members \
-H "Authorization: Bearer sz_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"contact_ids": ["01JCON123ABC", "01JCON456DEF"]}'
{
"data": {
"added": 2,
"skipped": 0,
"member_count": 144
}
} Bulk Import
For importing large contact lists, use the bulk import endpoint. It accepts up to 1,000 contacts per request and processes them asynchronously.
/v1/contacts/import Import contacts in bulk.
contacts array
Required
Array of contact objects, each with at least a phone_number field. Maximum 1,000 per request.
group_id string Automatically add all imported contacts to this group.
skip_duplicates boolean When true (default), duplicate phone numbers are silently skipped. When false, the entire batch fails if any duplicate is found.
curl -X POST https://api.senderz.com/v1/contacts/import \
-H "Authorization: Bearer sz_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{"phone_number": "+15551111111", "name": "Alice"},
{"phone_number": "+15552222222", "name": "Bob"},
{"phone_number": "+15553333333", "name": "Charlie"}
],
"group_id": "01JGRP001ABC",
"skip_duplicates": true
}' const res = await fetch('https://api.senderz.com/v1/contacts/import', {
method: 'POST',
headers: {
'Authorization': 'Bearer sz_live_YOUR_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
contacts: [
{ phone_number: '+15551111111', name: 'Alice' },
{ phone_number: '+15552222222', name: 'Bob' },
{ phone_number: '+15553333333', name: 'Charlie' },
],
group_id: '01JGRP001ABC',
skip_duplicates: true,
}),
})
const data = await res.json() {
"data": {
"import_id": "01JIMP789GHI",
"total": 3,
"created": 2,
"skipped": 1,
"failed": 0
}
} Error Responses
| Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR | Invalid phone number format or missing required field |
| 401 | INVALID_API_KEY | Bad or missing API key |
| 404 | CONTACT_NOT_FOUND | Contact ID does not exist or belongs to another tenant |
| 409 | DUPLICATE_CONTACT | Phone number already exists in your tenant |
| 429 | RATE_LIMIT_EXCEEDED | Too many requests |