Skip to main content

SMS billing

Every message sent through this API is priced from your rate card and lands on your invoice. API traffic never consumes the portal's monthly free allowance — it is always priced.

The charge is in the response, so you never have to wait for an invoice:

"billing": {
"billed": true, "currency": "USD", "type": "sms",
"units": 1, "totalPrice": 0.0025, "recipientCount": 1, "skippedCount": 0
}

price = unitPrice × units, always.

The two models

smsmms
Whenone recipient, no attachmentstwo or more recipients, or any attachment
Rateyour SMS rateyour MMS rate
Chargedper segmentonce
Body length affects priceyesno
Recipient count affects pricen/ano
Attachment count affects pricen/ano

A 6-segment text to one person costs 6 × your SMS rate. The same body to eight people in a group thread costs one MMS rate.

:::caution Attachments alone make it an MMS A one-line text to a single person is sms. Attach a picture to that same message and it becomes mms — charged once at your MMS rate instead of per segment. Nothing about the recipient list changed; the file did it.

This cuts both ways. A long body that would cost 6 SMS segments costs one MMS rate once you attach something to it, which is sometimes cheaper. Read billing.type in the response rather than predicting it from to. :::

:::caution A group thread is a different product, not a bulk send Two or more numbers in to puts those people in a room together — one conversation they can all see and reply into. It does not send separate texts.

For N independent one-to-one messages, make N requests. That is also usually cheaper for short bodies. :::

If participants sit on different rates (one US, one UK), the highest applicable MMS rate is charged for the thread.

Segments (sms only)

SMS is billed in segments — the unit the carrier actually charges for. How many you use depends on both length and which characters you use:

ContentSingle segmentEach segment once it splits
Plain GSM-7 text (Latin letters, digits, common punctuation)up to 160 chars153 chars
Anything outside GSM-7up to 70 chars67 chars

:::danger The encoding is chosen per message, not per character One character outside GSM-7 anywhere in the body drops the whole message to the 70-character budget. This is the most common surprise on a bill. :::

Always takes the 70/67 path:

  • Hebrew, Arabic, Cyrillic, Greek, Chinese, Japanese, Korean — any non-Latin script
  • Emoji
  • Curly quotes ( ), en/em dashes ( ), , — these arrive invisibly from word processors and CMS fields

Character cost within a non-GSM-7 message differs too:

  • A Hebrew or Arabic letter costs 1 unit — 70 Hebrew characters is one segment, 71 is two.
  • Most emoji cost 2 units (surrogate pairs) — 69 Hebrew letters plus one emoji is already two segments.

Some accented Latin vowels (é è à ì ò ù) and some Greek capitals are in GSM-7 and do not force the switch. , [, ], {, }, \, ^, ~, | are in the GSM-7 extension table and cost 2 characters each while staying on the 160/153 budget.

Hebrew example: a 100-character Hebrew message is 2 segments (100 > 70, then 100 ÷ 67 rounds up to 2). The same 100 characters in English would be 1. Budget Hebrew and Arabic content at roughly 70 characters per segment.

There is no segment cap; a long body just costs more. The hard bound is 2000 bytes on the body.

Skipped recipients

Never sent to, never charged for. reason is a stable token you can branch on:

reasonMeaning
opted_outThe recipient replied STOP to this number. Carrier-mandated, not overridable.
duplicateThe number appeared more than once in to, after normalization.
invalidNot a dialable number.

If every recipient is skipped you get 200 — not 201 — with an empty messages array and totalPrice: 0.

skipped[].to echoes what you sent, so you can match it to your own records. messages[].to holds the normalized forms that were actually dialled.

Number normalization

Recipients are normalized before anything else happens:

  • Formatting stripped: (347) 555-01233475550123
  • A 10-digit number gets a US/Canada 1: 347555012313475550123
  • International numbers used as given: 442071234567

Deduplication runs after normalization, so 3475550123 and 13475550123 in the same request are one recipient — and the second is reported as duplicate.

Limits

  • 20 recipients per request (participants in one group thread).
  • 2000 bytes of body. Note bytes: Hebrew, Arabic and other non-Latin scripts take 2–3 bytes per character in UTF-8, so the effective limit is about 1000 characters for those.
  • The sender must be a number on your account with SMS enabled — same requirement as sending from the portal's message center. Anything else is a 403. (Bulk campaigns additionally need 10DLC registration; this endpoint does not.)
  • 10 attachments, 5 MB each decoded, image/jpeg|png|bmp|gif. Attachments make the message an mms — see the table above.

Retries and double-billing

There is no idempotency key. If a request times out, the message may or may not have been sent.

What you gotSafe to retryWhy
4xxYesRejected before sending. Nothing charged.
5xxYesNothing sent. Nothing charged, guaranteed.
No response at allNoCheck your SMS history first.

Errors specific to pricing

StatustypeCause
422invalid_requestNo active rate applies to a destination. The API will not send a message it cannot price — contact support to add it to your rate card.
5xxinternal_errorThe carrier gateway rejected the message or did not answer. Nothing sent, nothing charged.

Delivery is not confirmation

status: "sent" means the carrier gateway accepted the message. It does not mean a handset received it. Subscribe to the AccountSMS webhook for delivery events.