Contacts

Manage contacts, groups, and bulk imports with the senderZ Contacts API.

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.

PlanNew Contacts / DayNew Contacts / Month
Starter ($49/mo)10250
Growth ($249/mo)501,250
Scale ($749/mo)50010,000

Create a Contact

POST /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()
201 Created
{
  "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

GET /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"
200 OK
{
  "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

GET /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"
200 OK
{
  "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

PUT /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()
200 OK
{
  "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

DELETE /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"
200 OK
{
  "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

POST /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"}'
201 Created
{
  "data": {
    "id": "01JGRP001ABC",
    "name": "VIP Customers",
    "description": "High-value accounts",
    "member_count": 0,
    "created_at": "2026-04-15T10:00:00Z"
  }
}

List Groups

GET /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"
200 OK
{
  "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

POST /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"]}'
200 OK
{
  "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.

POST /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()
202 Accepted
{
  "data": {
    "import_id": "01JIMP789GHI",
    "total": 3,
    "created": 2,
    "skipped": 1,
    "failed": 0
  }
}

Error Responses

StatusCodeDescription
400VALIDATION_ERRORInvalid phone number format or missing required field
401INVALID_API_KEYBad or missing API key
404CONTACT_NOT_FOUNDContact ID does not exist or belongs to another tenant
409DUPLICATE_CONTACTPhone number already exists in your tenant
429RATE_LIMIT_EXCEEDEDToo many requests