Create or update a contact

POST /v1/contacts

One contact per phone number. Creates the contact (201) or updates the one that already has this number (200). Values you leave out are kept and tags are added, never removed; use PATCH to replace them.

Send your API key as a bearer token: Authorization: Bearer ssms_sk_.... Test keys run this endpoint against the sandbox; see Authentication for key modes and scopes.

Request body

JSON (Content-Type: application/json).

A ContactInput object.

FieldTypeRequiredDescription
phone_numberstringYesE.164, US or Canada
namestringNoDisplay name. Defaults to first + last name. At most 120 characters.
tagsarray of stringNoAt most 20 items.
fieldsobjectNoUp to 20 string values (account id, plan, location...), usable in segments and as {{field:key}} merge fields
notesstringNoAt most 2000 characters.
first_namestringNoAt most 80 characters.
last_namestringNoAt most 80 characters.
emailstringNoAt most 254 characters.
companystringNoAt most 120 characters.
statestringNoAt most 40 characters. Example: TN.
sourcestringNoWhere the contact came from. Defaults to "api". At most 60 characters.
opt_inobjectNoImport only (POST /v1/contacts/import). Stored as consent evidence; never lifts an opt-out.
opt_in.statusbooleanNotrue = opted in; false = unsubscribed (recorded as an opt-out)
opt_in.atstringNoWhen they opted in: ISO timestamp, date, or epoch ms
opt_in.sourcestringNoweb_form, keyword, paper, checkout or verbal are stored as proof; anything else is kept as a note

Responses

StatusMeaningBody
200The existing contact, updatedContact
201The new contactContact
400Invalid requestError
401Missing, malformed, or revoked API keyError
402Billable live calls only: the prepaid credit balance cannot cover the call (insufficient_credits); nothing was done or chargedError
429Rate limit or quota exceededError

200: Contact fields

FieldTypeDescription
idstringExample: ct_a1B2c3D4e5F6.
objectcontact
phone_numberstringExample: +14155550132.
namestringNullable.
tagsarray of stringFree-form labels; they double as broadcast audiences
fieldsobject
notesstringNullable.
first_namestringNullable.
last_namestringNullable.
emailstringNullable.
companystringNullable.
statestringNullable.
sourcestringNullable.
import_idstringThe last import that touched this contact Nullable.
opt_inobjectNullable.
opt_in.sourcestringNullable.
opt_in.collected_atstring (date-time)Nullable.
created_atstring (date-time)
updated_atstring (date-time)
consent_statusopted_out, opted_in, no_recordOnly on retrieve
topicsarray of TopicSubscriptionOnly on retrieve
segmentsarray of objectOnly on retrieve
segments[].idstring
segments[].namestring

Errors

StatusWhen
400Invalid request
401Missing, malformed, or revoked API key
402Billable live calls only: the prepaid credit balance cannot cover the call (insufficient_credits); nothing was done or charged
429Rate limit or quota exceeded

Every error has the same JSON shape, and request_id matches the X-Request-Id response header. Errors lists every code and what to do about it.

json
{
  "error": {
    "code": "invalid_request",
    "message": "What went wrong, in plain words.",
    "param": "the_field",
    "request_id": "req_a1B2c3D4e5F6g7H8"
  }
}

Examples

curl

bash
curl -X POST "https://api.joinsimplesms.com/v1/contacts" \
  -H "Authorization: Bearer $SIMPLESMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "phone_number": "+14155550132",
  "state": "TN"
}'

Node.js

javascript
import { SimpleSMS } from 'joinsimplesms'; // npm install joinsimplesms

const sms = new SimpleSMS(process.env.SIMPLESMS_API_KEY);

const contact = await sms.contacts.upsert({
  phoneNumber: '+14155550132',
  name: 'Jane Doe',
  tags: ['customers'],
});

console.log(contact.id);

Python

python
import os
from joinsimplesms import SimpleSMS  # pip install joinsimplesms

client = SimpleSMS(os.environ["SIMPLESMS_API_KEY"])

contact = client.contacts.upsert(
    "+14155550132",
    name="Jane Doe",
    tags=["customers"],
)

print(contact["id"])