Customers

For platforms (SaaS, agent builders, agencies) that send on behalf of other businesses. A customer is a label you control: assign numbers to it, and every message, verification, event and webhook from those numbers carries its customer_id, with usage you can read back per customer to rebill.

Entirely optional. Accounts that never create a customer see no change.

Create a customer

POST /v1/customers

bash
curl -X POST https://api.joinsimplesms.com/v1/customers \
  -H "Authorization: Bearer ssms_sk_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Acme Dental", "external_id": "acct_42", "metadata": { "plan": "pro" } }'
json
{
  "id": "cus_a1B2c3D4e5F6g7",
  "object": "customer",
  "name": "Acme Dental",
  "external_id": "acct_42",
  "metadata": { "plan": "pro" },
  "created_at": "2026-10-01T12:00:00.000Z",
  "updated_at": "2026-10-01T12:00:00.000Z"
}
ParameterTypeNotes
namestringOptional, up to 200 characters.
external_idstringOptional. Your id for them; unique per account (409 if taken).
metadataobjectOptional. Up to 50 string values. Keys up to 40 characters, no . # $ / [ ].

Retrieve, update, delete, list

  • GET /v1/customers/{id} also returns phone_numbers assigned to it.
  • PATCH /v1/customers/{id} changes only the fields you send; metadata replaces the whole object, null clears a field.
  • DELETE /v1/customers/{id} deletes it and unassigns its numbers (they stay on your account). Messages, events and usage already attributed keep the id: they record what happened.
  • GET /v1/customers?limit=25&cursor=... lists newest first; ?external_id=acct_42 finds one by your id.

Attribute traffic

  • Numbers: POST /v1/numbers with customer_id, or PATCH /v1/numbers/{number} with { "customer_id": "cus_..." }.
  • Outbound messages inherit the customer of their from number, or take an explicit customer_id. A number assigned to one customer can't send as another (400); unassigned numbers can send for anyone.
  • Inbound messages (and STOP/HELP replies) inherit the customer of the number they arrived on.
  • Verifications take an optional customer_id on POST /v1/verify.
  • Events and webhooks about any of the above carry data.customer_id.
  • GET /v1/messages?customer_id=cus_... lists one customer's messages.

Usage

GET /v1/customers/{id}/usage?start=2026-10-01&end=2026-10-31

json
{
  "object": "customer_usage",
  "customer_id": "cus_a1B2c3D4e5F6g7",
  "mode": "live",
  "start": "2026-10-01",
  "end": "2026-10-31",
  "messages_sent": 1204,
  "messages_failed": 3,
  "messages_received": 377,
  "verifications_sent": 88,
  "verifications_approved": 81,
  "numbers": 2,
  "phone_numbers": ["+14155550132", "+14155550133"],
  "daily": [{ "date": "2026-10-01", "messages_sent": 40, "...": 0 }]
}

UTC days, inclusive, up to 366 days; the default is this month to date. Usage follows the key you call with: a test key reports sandbox traffic and a live key live traffic, so test runs never show up in what you rebill. numbers is the count assigned right now.

With the SDKs

js
const acme = await sms.customers.create({ name: 'Acme Dental', externalId: 'acct_42' });
await sms.numbers.update('+14155550132', { customerId: acme.id });
await sms.messages.list({ customerId: acme.id });
await sms.customers.usage(acme.id, { start: '2026-10-01', end: '2026-10-31' });
python
acme = client.customers.create(name="Acme Dental", external_id="acct_42")
client.numbers.update("+14155550132", customer_id=acme["id"])
client.messages.list(customer_id=acme["id"])
client.customers.usage(acme["id"], start="2026-10-01", end="2026-10-31")

Responsibility

A customer label is bookkeeping for you. It does not register the business with carriers, make it our customer, or change who is responsible for its traffic: you are, as if it were your own (Terms §2.4, Messaging Policy §7). Each end business still needs its own brand and campaign registration.