API reference
Messages
Send and receive SMS messages.
/v1/messagesList messages
messages:rNode.js SDKhonkio.messages.list()Requires messages:r.
Parameters
| Parameter | Type | Description |
|---|---|---|
qquery | string | Search by phone number (partial match on to/from) |
fromquery | string | |
toquery | string | |
statusquery | string |
|
directionquery | string |
|
pagequery | integer |
|
limitquery | integer |
|
date_fromquery | string |
|
date_toquery | string |
|
Responses
200A page of messages, newest first.
Field Type Description data[]requiredobject[] idrequiredstring directionrequiredstring - One of: OUTBOUND | INBOUND
fromrequiredstring torequiredstring bodyrequiredstring Null once the body is purged (see body_purged) and on a verification OTP, whose code is never stored.
- Can be null
body_purgedrequiredboolean statusrequiredstring - One of: QUEUED | SENDING | SENT | DELIVERED | FAILED | UNDELIVERED | RECEIVED
segment_countrequiredinteger Number of SMS parts the carrier split the body into (inbound: as received). Billing is per part.
- Can be null
moderequiredstring TEST messages are simulated: never sent to a carrier and never billed.
- One of: LIVE | TEST
is_verificationrequiredboolean The OTP SMS behind a verification. Its body is never stored.
auto_reply_keywordrequiredstring 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.
- One of: STOP | START | HELP
- Can be null
cost_centsrequiredinteger 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_coderequiredstring 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).
- Can be null
error_messagerequiredstring Human-readable reason for a FAILED or UNDELIVERED message, from the carrier when it gave one.
- Can be null
sent_atrequiredstring - Format: date-time
- Can be null
created_atrequiredstring - Format: date-time
casl_consent_typerequiredstring 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).
- One of: EXPRESS | IMPLIED
- Can be null
dncl_exemptionrequiredstring The CRTC Do Not Call List exemption the send relied on, when one applied.
- Can be null
delivered_atrequiredstring When the carrier reported the message delivered to the handset.
- Format: date-time
- Can be null
metarequiredobject Page-number pagination, returned as
metaby the messages listing.pagerequiredinteger limitrequiredinteger totalrequiredinteger pagesrequiredinteger - 401Missing, invalid, revoked or expired API key.The standard error body.
- 402The account has no balance, or a LIVE key was used before the first top-up.The standard error body.
- 403The API key lacks the permission this operation requires, or the account is suspended or closed.The standard error body.
- 422VALIDATION_ERROR: the request failed validation; details names the fields.The standard error body.
- 429Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).The standard error body.
- 500Unexpected server error.The standard error body.
Example
const { data, error } = await honkio.messages.list({ direction: 'INBOUND', limit: 20 })
if (error) throw new Error(error.message)
for (const message of data.data) console.log(message.from, message.body)curl https://api.honkio.ca/v1/messages \
-H "Authorization: Bearer mk_live_YOUR_KEY"/v1/messagesSend an SMS message
messages:wNode.js SDKhonkio.messages.send()Live sends are gated before any charge: 403 SENDING_PAUSED, 429 DAILY_LIMIT_REACHED / NUMBER_RATE_LIMITED / RECIPIENT_RATE_LIMITED (30 messages an hour and 100 a day to one recipient; Retry-After is set) / 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. A refused send costs nothing. 503 CARRIER_UNAVAILABLE means the carrier could not be reached; nothing was sent or charged, retry shortly. 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.
Requires messages:w.
Parameters
| Parameter | Type | Description |
|---|---|---|
Idempotency-Keyheader | string |
|
Request body
| Field | Type | Description |
|---|---|---|
fromrequired | string | Sending number (E.164, must be owned by account) |
torequired | string | Recipient number (E.164, Canadian numbers only) |
bodyrequired | string | 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_check | boolean | 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_exemptions | string[] | DNCL exemption reasons. CRTC DNCL checking is coming soon and not yet enforced, so this field is currently accepted but not acted upon.
|
Responses
201Message sent (LIVE) or simulated (TEST). With an Idempotency-Key already used for the same from, to and body, the original message.
Field Type Description idrequiredstring directionrequiredstring - One of: OUTBOUND | INBOUND
fromrequiredstring torequiredstring bodyrequiredstring Null once the body is purged (see body_purged) and on a verification OTP, whose code is never stored.
- Can be null
body_purgedrequiredboolean statusrequiredstring - One of: QUEUED | SENDING | SENT | DELIVERED | FAILED | UNDELIVERED | RECEIVED
segment_countrequiredinteger Number of SMS parts the carrier split the body into (inbound: as received). Billing is per part.
- Can be null
moderequiredstring TEST messages are simulated: never sent to a carrier and never billed.
- One of: LIVE | TEST
is_verificationrequiredboolean The OTP SMS behind a verification. Its body is never stored.
auto_reply_keywordrequiredstring 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.
- One of: STOP | START | HELP
- Can be null
cost_centsrequiredinteger 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_coderequiredstring 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).
- Can be null
error_messagerequiredstring Human-readable reason for a FAILED or UNDELIVERED message, from the carrier when it gave one.
- Can be null
sent_atrequiredstring - Format: date-time
- Can be null
created_atrequiredstring - Format: date-time
casl_consent_typerequiredstring 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).
- One of: EXPRESS | IMPLIED
- Can be null
dncl_exemptionrequiredstring The CRTC Do Not Call List exemption the send relied on, when one applied.
- Can be null
delivered_atrequiredstring When the carrier reported the message delivered to the handset.
- Format: date-time
- Can be null
- 401Missing, invalid, revoked or expired API key.The standard error body.
- 402INSUFFICIENT_BALANCE: the balance cannot cover the send. Also PAYMENT_REQUIRED when a LIVE key is used before the first top-up.The standard error body.
- 403FORBIDDEN (skip_consent_check on a live key, or the key lacks messages:w), ACCOUNT_NOT_VERIFIED, PHONE_NUMBER_NOT_OWNED, ALLOW_LIST_BLOCKED, DENY_LIST_BLOCKED or SENDING_PAUSED.The standard error body.
- 409PHONE_NUMBER_SUSPENDED (the from number is suspended for unpaid rent) or IDEMPOTENCY_KEY_REUSED (the key was used for a different from, to or body).The standard error body.
- 422VALIDATION_ERROR, NON_CANADIAN_NUMBER, RESERVED_DESTINATION, MESSAGE_TOO_LONG, LINK_SHORTENER_BLOCKED, UNDELIVERABLE_NUMBER, NOT_A_MOBILE_NUMBER or INVALID_PHONE_NUMBER (the carrier found the number invalid). A refused send costs nothing.The standard error body.
- 429DAILY_LIMIT_REACHED, FANOUT_LIMIT_REACHED, NUMBER_RATE_LIMITED or RECIPIENT_RATE_LIMITED (Retry-After set on the two rate limits), or RATE_LIMITED at 100 requests per second.The standard error body.
- 451The CASL gate refused the recipient: NO_CONSENT, OPT_OUT_BLOCKED or CONSENT_EXPIRED.The standard error body.
- 500Unexpected server error.The standard error body.
- 502CARRIER_ERROR: the carrier refused the send. Any charge is refunded.The standard error body.
- 503CARRIER_UNAVAILABLE: the carrier could not be reached. Nothing was sent or charged; retry shortly. CARRIER_TIMEOUT: the carrier did not answer in time, so the message may or may not have been sent. Nothing is charged. Check the message status before sending again: a retry with the same Idempotency-Key returns the failed message instead of sending it again.The standard error body.
Example
const { data, error } = await honkio.messages.send({
from: '+1416XXXXXXX', // one of your HonkIO numbers
to: '+1613XXXXXXX',
body: 'Your order has shipped.',
})
if (error) throw new Error(`${error.name}: ${error.message}`)
console.log(data.id, data.status)curl -X POST https://api.honkio.ca/v1/messages \
-H "Authorization: Bearer mk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "+1416XXXXXXX",
"to": "+1613XXXXXXX",
"body": "Hello from HonkIO!"
}'/v1/messages/{id}Retrieve a message by ID
messages:rNode.js SDKhonkio.messages.get()Requires messages:r.
Parameters
| Parameter | Type | Description |
|---|---|---|
idrequiredpath | string |
Responses
200The message.
Field Type Description idrequiredstring directionrequiredstring - One of: OUTBOUND | INBOUND
fromrequiredstring torequiredstring bodyrequiredstring Null once the body is purged (see body_purged) and on a verification OTP, whose code is never stored.
- Can be null
body_purgedrequiredboolean statusrequiredstring - One of: QUEUED | SENDING | SENT | DELIVERED | FAILED | UNDELIVERED | RECEIVED
segment_countrequiredinteger Number of SMS parts the carrier split the body into (inbound: as received). Billing is per part.
- Can be null
moderequiredstring TEST messages are simulated: never sent to a carrier and never billed.
- One of: LIVE | TEST
is_verificationrequiredboolean The OTP SMS behind a verification. Its body is never stored.
auto_reply_keywordrequiredstring 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.
- One of: STOP | START | HELP
- Can be null
cost_centsrequiredinteger 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_coderequiredstring 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).
- Can be null
error_messagerequiredstring Human-readable reason for a FAILED or UNDELIVERED message, from the carrier when it gave one.
- Can be null
sent_atrequiredstring - Format: date-time
- Can be null
created_atrequiredstring - Format: date-time
casl_consent_typerequiredstring 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).
- One of: EXPRESS | IMPLIED
- Can be null
dncl_exemptionrequiredstring The CRTC Do Not Call List exemption the send relied on, when one applied.
- Can be null
delivered_atrequiredstring When the carrier reported the message delivered to the handset.
- Format: date-time
- Can be null
- 401Missing, invalid, revoked or expired API key.The standard error body.
- 402The account has no balance, or a LIVE key was used before the first top-up.The standard error body.
- 403The API key lacks the permission this operation requires, or the account is suspended or closed.The standard error body.
- 404NOT_FOUND: no such message on this account. A test key finds TEST messages only.The standard error body.
- 429Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).The standard error body.
- 500Unexpected server error.The standard error body.
Example
const { data, error } = await honkio.messages.get('MESSAGE_ID')
if (error) throw new Error(error.message)
console.log(data.status, data.cost_cents)curl https://api.honkio.ca/v1/messages/ID \
-H "Authorization: Bearer mk_live_YOUR_KEY"
HonkIO