Messages

Send an SMS message

Live sends are gated before any charge: 403 SENDING_PAUSED, 429 DAILY_LIMIT_REACHED / NUMBER_RATE_LIMITED / RECIPIENT_RATE_LIMITED (NUMBER_RATE_LIMITED: the sending number already has a minute of message parts waiting for the carrier, which sends 10 parts a minute from a local number; RECIPIENT_RATE_LIMITED: 30 messages an hour and 100 a day to one recipient; Retry-After is set on both) / FANOUT_LIMIT_REACHED, 422 UNDELIVERABLE_NUMBER (three consecutive carrier failures to the number, from any customer, list it for 90 days), 422 NOT_A_MOBILE_NUMBER (a landline or VoIP destination, refused by the carrier before sending), 422 RESERVED_DESTINATION (a reserved exchange such as 555-XXXX, N11 or a carrier test code, refused here in both modes), 422 LINK_SHORTENER_BLOCKED, and the CASL codes. From a toll-free number: 403 TOLL_FREE_NOT_VERIFIED until the number's toll-free verification is approved (test keys too), and 451 DOUBLE_OPT_IN_REQUIRED until the recipient has replied YES to an opt-in confirmation from that number (POST /v1/compliance/opt-in-confirmations). A refused send costs nothing. 503 CARRIER_UNAVAILABLE means the carrier could not be reached; nothing was sent or charged, retry shortly. 503 CARRIER_THROTTLED means the carrier asked us to slow down and did not take the message: nothing was charged; retry after Retry-After with the same Idempotency-Key, which sends it. 503 CARRIER_TIMEOUT means the carrier did not answer in time: the message may or may not have been sent, nothing was charged, and a retry with the same Idempotency-Key returns the failed message rather than sending it again. Send an Idempotency-Key header from anything that retries: the same key returns the original message. A live send answers estimated_send_at, an estimate of when the carrier will send it.

Requires messages:w.

post/v1/messages

Request

  • Base URL: https://api.honkio.ca
  • URL: https://api.honkio.ca/v1/messages
  • Auth: one of:
    • HTTP bearer
    • API key in header X-API-Key

Headers

idempotency-keystring

Request body

fromstring required

Sending number (E.164, must be owned by account)

tostring required

Recipient number (E.164, Canadian numbers only)

bodystring required

Up to 1600 characters and 10 SMS parts (about 1,530 GSM-7 or 670 Unicode characters). Longer bodies are rejected with 422 MESSAGE_TOO_LONG before any charge. Billed per part as the carrier splits it.

skip_consent_checkboolean

Skip the CASL consent gate. Test-mode keys only: a live key gets 403 FORBIDDEN. Use only when you have consent recorded outside HonkIO.

dncl_exemptionsstring[]

DNCL exemption reasons. CRTC DNCL checking is coming soon and not yet enforced, so this field is currently accepted but not acted upon.

Response

Message sent (LIVE) or simulated (TEST). With an Idempotency-Key already used for the same from, to and body, the original message.

idstring required
direction'OUTBOUND' | 'INBOUND' required
fromstring required
tostring required
bodystring nullable required

Null once the body is purged (see body_purged) and on a verification OTP, whose code is never stored.

body_purgedboolean required
status'QUEUED' | 'SENDING' | 'SENT' | 'DELIVERED' | 'FAILED' | 'UNDELIVERED' | 'RECEIVED' required
segment_countinteger nullable required

Number of SMS parts the carrier split the body into (inbound: as received). Billing is per part.

mode'LIVE' | 'TEST' required

TEST messages are simulated: never sent to a carrier and never billed.

is_verificationboolean required

The OTP SMS behind a verification. Its body is never stored.

auto_reply_keyword'STOP' | 'START' | 'HELP' | 'null' nullable required

Set on the automatic confirmation sent when a recipient texts STOP, START or HELP. You did not send it and are never charged for it. Its body can be null for a few minutes after it is sent, until the carrier reports the text. Null on every other message.

cost_centsinteger required

What this message actually cost, in cents CAD: message_cost_cents times the parts the carrier split it into, settled to the carrier's count once it accepts the send (so it can differ from a pre-send estimate). 0 when the carrier refused the send outright (refunded or never charged), and on an automatic STOP/START/HELP confirmation (auto_reply_keyword), which is never charged. A message the carrier accepted but could not deliver keeps its charge. Inbound messages carry the inbound charge. On a TEST message this is what the send would have cost; nothing was charged.

error_codestring nullable required

Set on FAILED and UNDELIVERED messages: the carrier's code from the delivery receipt (e.g. 30003), or the platform code for a synchronous rejection (CARRIER_ERROR, INVALID_PHONE_NUMBER, NOT_A_MOBILE_NUMBER, INSUFFICIENT_BALANCE). CARRIER_THROTTLED when the carrier asked us to slow down, or dropped the message from the sending number's full queue before trying the recipient: refunded, and safe to send again.

error_messagestring nullable required

Human-readable reason for a FAILED or UNDELIVERED message, from the carrier when it gave one.

sent_atstring date-time nullable required
created_atstring date-time required
casl_consent_type'EXPRESS' | 'IMPLIED' | 'null' nullable required

The CASL consent an outbound send went out on. Null for inbound and test-mode messages, carrier-mandated STOP/HELP/START replies, and sends made before this was recorded (18 September 2026).

dncl_exemptionstring nullable required

The CRTC Do Not Call List exemption the send relied on, when one applied.

delivered_atstring date-time nullable required

When the carrier reported the message delivered to the handset.

estimated_send_atstring date-time nullable required

An estimate of when the carrier will start sending this message, from the parts already waiting on the sending number (a local number sends 10 parts a minute). Close to now when nothing is waiting. Null in test mode and on a retry that returned an earlier send.

Changes

No recorded changes to this operation across all 1 revision of this API.