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.
| Field | Type | Required | Description |
|---|---|---|---|
phone_number | string | Yes | E.164, US or Canada |
name | string | No | Display name. Defaults to first + last name. At most 120 characters. |
tags | array of string | No | At most 20 items. |
fields | object | No | Up to 20 string values (account id, plan, location...), usable in segments and as {{field:key}} merge fields |
notes | string | No | At most 2000 characters. |
first_name | string | No | At most 80 characters. |
last_name | string | No | At most 80 characters. |
email | string | No | At most 254 characters. |
company | string | No | At most 120 characters. |
state | string | No | At most 40 characters. Example: TN. |
source | string | No | Where the contact came from. Defaults to "api". At most 60 characters. |
opt_in | object | No | Import only (POST /v1/contacts/import). Stored as consent evidence; never lifts an opt-out. |
opt_in.status | boolean | No | true = opted in; false = unsubscribed (recorded as an opt-out) |
opt_in.at | string | No | When they opted in: ISO timestamp, date, or epoch ms |
opt_in.source | string | No | web_form, keyword, paper, checkout or verbal are stored as proof; anything else is kept as a note |
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | The existing contact, updated | Contact |
| 201 | The new contact | Contact |
| 400 | Invalid request | Error |
| 401 | Missing, malformed, or revoked API key | Error |
| 402 | Billable live calls only: the prepaid credit balance cannot cover the call (insufficient_credits); nothing was done or charged | Error |
| 429 | Rate limit or quota exceeded | Error |
200: Contact fields
| Field | Type | Description |
|---|---|---|
id | string | Example: ct_a1B2c3D4e5F6. |
object | contact | |
phone_number | string | Example: +14155550132. |
name | string | Nullable. |
tags | array of string | Free-form labels; they double as broadcast audiences |
fields | object | |
notes | string | Nullable. |
first_name | string | Nullable. |
last_name | string | Nullable. |
email | string | Nullable. |
company | string | Nullable. |
state | string | Nullable. |
source | string | Nullable. |
import_id | string | The last import that touched this contact Nullable. |
opt_in | object | Nullable. |
opt_in.source | string | Nullable. |
opt_in.collected_at | string (date-time) | Nullable. |
created_at | string (date-time) | |
updated_at | string (date-time) | |
consent_status | opted_out, opted_in, no_record | Only on retrieve |
topics | array of TopicSubscription | Only on retrieve |
segments | array of object | Only on retrieve |
segments[].id | string | |
segments[].name | string |
Errors
| Status | When |
|---|---|
| 400 | Invalid request |
| 401 | Missing, malformed, or revoked API key |
| 402 | Billable live calls only: the prepaid credit balance cannot cover the call (insufficient_credits); nothing was done or charged |
| 429 | Rate 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"])