API and delivery

Send a message with the API

One endpoint accepts asynchronously. A success code means the platform took the message — not that a handset received it.

6 min read · updated 2026-10-13

The call

Endpoint and headers below. All request and response bodies are UTF-8 JSON.

ItemValue
Method and pathPOST /open/v1/sms/send
Base URLhttps://api.whaleal.com/open/v1
AuthAuthorization: Bearer sk_… (or X-API-Key)
Content-Typeapplication/json

Fields worth reading twice

FieldRequiredWhy it matters
toYesE.164 format. Opt-out matching compares digits after stripping + and separators, so inconsistent formatting is what makes a STOP fail to match
contentYesNot stored on the platform, for compliance. Keep your own copy if you need one
fromNoOmit it and the platform picks a sender from the connection
messageTypeNoMARKETING / INFORMATIONAL / TRANSACTIONAL. Omitted means MARKETING, the strictest setting
templateIdNoOnly for providers that bill or approve per template — pass a template ID you created in your own provider account
Note. The platform sends the content you give it. It does not render template variables for you, and template definitions live in your provider account, not here.

Start with TRANSACTIONAL, not with the default

messageType is the only input to the suppression gate. The platform does not read your message body — so an alert that happens to contain promotional wording will pass as TRANSACTIONAL even though, under rules like the US TCPA, it may still be legally marketing. Declaring the type honestly is the caller's responsibility.

An unrecognised value is rejected with HTTP 400 and code 40000 rather than silently downgraded. That is deliberate: a typo should surface as a broken integration, not as verification codes that quietly stop arriving.

Scope. Suppression applies to marketing traffic only, and opt-out handling is an SMS concept here. Email and WhatsApp connect on the same model, but they do not carry this suppression gate today.

What comes back

The response repeats the recipient, names the provider that actually carried the message, and returns the fields you will want at reconciliation time.

FieldMeaning
messageIdPlatform message ID. Use it for status lookups and for de-duplicating callbacks
statusWhere the message stands right now — see "What each message status means"
providerMessageIdProvider-side ID. Null means the provider did not accept it, which also means it is not billed
rawStatusThe provider's own wording before normalisation — diagnostic value only
encodingGSM7 / UCS2 / UTF8 / GBK — the encoding actually used
segmentsBilled segments for this message

The one rule that prevents most duplicate sends

There is no idempotency key. Sending the same request twice sends two messages and bills for two. Because acceptance is asynchronous, a network timeout means the send may already have happened — treat the timeout as unknown, not as failure.

  • Got a messageId: query its status or wait for the callback. Do not resend.
  • Did not get a messageId: look the send up in the console log by time window and recipient number. Do not resend.
  • A client-side timeout never rolls back a message that was already submitted.

Keep reading

Create, bind and rotate API keys

A key proves which tenant is calling and which channels it may use. It is shown in full once, and a key with no binding cannot send at all.

Read this article →

What each message status means

Four states, and one of them does not belong in the same bucket as the others. Reading suppressed as a failure sends you looking in the wrong place.

Read this article →

Receive delivery receipts

The platform POSTs to a URL you configure, with a token you generate. Two implementation details decide whether you lose events.

Read this article →