Contact Management

Contacts are the people you message. Every phone number you send to can be stored as a contact with a name, email, group memberships, and custom fields. Contacts are required for campaigns (which target groups) and useful for transactional messaging when you want to track recipients.

The API gives you three resources: Contacts (individual people), Contact Groups (named collections for campaign targeting), and Contact Fields (custom data fields you define).

Create or update a contact

POST /v1/contactsuses upsert behavior — if a contact with the same phone number exists, it's updated rather than duplicated. You can safely call this without checking whether the contact exists first.

curl --request POST \
     --url https://a.eztexting.com/v1/contacts \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --user 'your_username:your_password' \
     --data '{
  "phoneNumber": "15551234567",
  "firstName": "Jane",
  "lastName": "Smith",
  "email": "jane@example.com",
  "groupIdsAdd": [
    "987654"
  ]
}'

Contact fields

FieldTypeDescription
phoneNumberstringPhone number in E.164 format (e.g., 15551234567)
firstNamestringFirst name
lastNamestringLast name
emailstringEmail address
notestringFree-text note
custom1custom5stringCustom field values (define fields via Contact Fields API)
groupIdsAddstring[]Group IDs to add this contact to
groupIdsRemovestring[]Group IDs to remove this contact from

Filtering contacts

The GET /v1/contacts endpoint supports filters on phoneNumber, firstName, lastName, email, and groupName (all partial-match), plus exact-match filters on optOut (boolean) and source. The source field tells you where a contact originated:

SourceDescription
UnknownOrigin not determined
WebInterfaceAdded via the EZ Texting dashboard
UploadImported via file upload
WebWidgetOpted in via an embeddable web widget
APICreated through the REST API
KeywordOpted in by texting a keyword

Sort results by field name, ascending or descending (prefix with -). For example: sort=lastName or sort=-firstName. Pagination uses page (1-based) and size (10, 20, 50, 100, or 200).

Batch import contacts

Use POST /v1/contacts/batchwhen syncing multiple contacts at once — importing a subscriber list, syncing from your CRM, or processing a file. The contacts array is required. Both groupIdsAdd and groupIdsRemove apply to all contacts in the batch.

curl --request POST \
     --url https://a.eztexting.com/v1/contacts/batch \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --user 'your_username:your_password' \
     --data '{
  "contacts": [
    {
      "phoneNumber": "15551234567",
      "firstName": "Jane",
      "lastName": "Smith"
    },
    {
      "phoneNumber": "15559876543",
      "firstName": "Bob",
      "lastName": "Jones"
    },
    {
      "phoneNumber": "15551112222",
      "firstName": "Alice",
      "lastName": "Lee"
    }
  ],
  "groupIdsAdd": [
    "876543"
  ]
}'

Contact groups

Groups organize contacts for campaign targeting. A campaign sends to one or more groups via the groupIds parameter on POST /v1/messages. Contacts can belong to multiple groups simultaneously.

# Create a group
curl --request POST \
     --url https://a.eztexting.com/v1/contact-groups \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --user 'your_username:your_password' \
     --data '{"name": "VIP Customers", "note": "High-value customers for exclusive offers"}'

# Add contacts to the group (phone numbers are query parameters)
curl --request POST \
     --url 'https://a.eztexting.com/v1/contact-groups/987654/contacts?phoneNumbers=15551234567&phoneNumbers=15559876543' \
     --header 'accept: application/json' \
     --user 'your_username:your_password'

# Remove contacts from the group (phone numbers are query parameters)
curl --request DELETE \
     --url 'https://a.eztexting.com/v1/contact-groups/987654/contacts?phoneNumbers=15559876543' \
     --header 'accept: application/json' \
     --user 'your_username:your_password'

Group design patterns

  • Lifecycle groups — "New Subscribers", "Active Customers", "At Risk", "Churned". Move contacts between groups as their status changes.
  • Segment groups — "VIP Customers", "West Coast", "Enterprise". A contact can be in multiple segment groups. Target campaigns at any combination.
  • Campaign-specific groups — "Black Friday 2026", "Product Launch". Add contacts as they qualify, then target the campaign.

Custom fields

Every contact has built-in fields: phoneNumber, firstName, lastName, email, and note.

Beyond those, there are two flavors of custom data — custom fields (the five built-in slots custom1custom5) and additional custom fields (anything you define via the Contact Fields API). Both work, but they sit in different places on the contact record — knowing the difference is the single most common stumbling block in the Contacts API.

Custom fields (custom1custom5) are top-level properties, like firstName. Additional custom fields (anything beyond the first five, created via the Contact Fields API) live inside a nested values object keyed by field ID. They are not a continuation of the customN numbering.

Side-by-side on the contact record

Field typeWhere it livesHow manyHow to set
Built-inTop-level5 fixed (phoneNumber, firstName, lastName, email, note)Set directly on the contact body
Custom fields (custom1custom5)Top-levelExactly 5, always presentSet directly on the contact body as custom1, custom2, etc.
Additional custom fields (Contact Fields API)Nested inside valuesUnlimited, each with its own IDCreate the field with POST /v1/contact-fields, then set the value in values[<fieldId>]

A contact with both kinds of custom data

This is what a contact body looks like when it uses a built-in slot (custom1) and an additional custom field (fld_loyalty_points) at the same time:

{
  "phoneNumber": "15551234567",
  "firstName": "Jane",
  "lastName": "Doe",
  "email": "jane@example.com",
  "note": "VIP customer",

  "custom1": "Gold",          // custom field — top level
  "custom2": "West Coast",    // custom field — top level
  "custom3": null,
  "custom4": null,
  "custom5": null,

  "values": {                 // additional custom fields created via
    "fld_loyalty_points": "2450",   //   POST /v1/contact-fields
    "fld_last_purchase":  "2026-03-15"
  }
}

Picking which to use

  • Use custom1custom5when five slots is enough. They're the simplest path — no setup, just set the value on the contact body.
  • Use additional custom fields (Contact Fields API) when you need more than five, want a human-readable field name instead of customN, or want typed fields (text, number, date). Create the field once with a label and type, get back an id, then reference that id inside the contact's values object.
  • Mixing both is fine. A contact can have custom1 set AND entries in values— they don't collide.

Creating and using an additional custom field

# Create a custom field
curl --request POST \
     --url https://a.eztexting.com/v1/contact-fields \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --user 'your_username:your_password' \
     --data '{"label": "Membership Tier", "type": "TEXT"}'

# Use the field when creating a contact
curl --request POST \
     --url https://a.eztexting.com/v1/contacts \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --user 'your_username:your_password' \
     --data '{"phoneNumber": "15551234567", "firstName": "Jane", "custom1": "Gold"}'

Listing and filtering

GET /v1/contacts returns contacts with pagination via page (zero-indexed) and size parameters. Use query parameters to filter rather than pulling all contacts and filtering client-side. Retrieve a specific contact by phone number with GET /v1/contacts/{phoneNumber}.

See Pagination for details on navigating large result sets.

Endpoints reference

EndpointMethodDescription
/v1/contactsPOSTCreate or update a contact (upsert)
/v1/contactsGETList contacts with pagination
/v1/contacts/batchPOSTBatch create or update contacts
/v1/contacts/{phoneNumber}GETGet a specific contact
/v1/contacts/{phoneNumber}DELETEDelete a contact
/v1/contact-groupsPOSTCreate a group
/v1/contact-groupsGETList all groups
/v1/contact-groups/{id}GET / PUT / DELETEGet, update, or delete a group
/v1/contact-groups/{id}/contactsPOSTAdd contacts to a group
/v1/contact-groups/{id}/contactsDELETERemove contacts from a group
/v1/contact-fieldsPOSTCreate a custom field
/v1/contact-fieldsGETList all custom fields
/v1/contact-fields/{id}PUT / DELETEUpdate or delete a custom field

Error handling

StatusMeaningWhat to do
400Bad RequestCheck required fields. Verify phone number format (E.164).
401UnauthorizedCredentials invalid or token expired. Refresh your token.
404Not FoundThe contact, group, or field doesn't exist. Verify the ID.
409ConflictOperation conflicts with current state (e.g., contact already in group).
422UnprocessableInvalid phone number format or field value.
429Rate Limited200 req/min exceeded. Wait for X-Rate-Limit-Retry-After-Milliseconds header.
500Server ErrorRetry with exponential backoff. Contact support if it persists.

Next steps