API reference

Messages

Send and receive SMS messages.

get/v1/messages

List messages

Requires messages:r.

Parameters

ParameterTypeDescription
qquerystring

Search by phone number (partial match on to/from)

fromquerystring
toquerystring
statusquerystring
  • One of: QUEUED | SENDING | SENT | DELIVERED | FAILED | UNDELIVERED | RECEIVED
directionquerystring
  • One of: OUTBOUND | INBOUND
pagequeryinteger
  • Minimum: 1
  • Default: 1
limitqueryinteger
  • Minimum: 1
  • Maximum: 100
  • Default: 20
date_fromquerystring
  • Format: date
date_toquerystring
  • Format: date

Responses

  • 200A page of messages, newest first.
    FieldTypeDescription
    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 meta by 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)
post/v1/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 (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

ParameterTypeDescription
Idempotency-Keyheaderstring
  • At most 255 characters

Request body

FieldTypeDescription
fromrequiredstring

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

torequiredstring

Recipient number (E.164, Canadian numbers only)

bodyrequiredstring

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.

  • At most 1600 characters
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.

  • One of: existing_business_relationship | registered_charity | political | survey | newspaper_subscription | personal

Responses

  • 201Message sent (LIVE) or simulated (TEST). With an Idempotency-Key already used for the same from, to and body, the original message.
    FieldTypeDescription
    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)
get/v1/messages/{id}

Retrieve a message by ID

Requires messages:r.

Parameters

ParameterTypeDescription
idrequiredpathstring

Responses

  • 200The message.
    FieldTypeDescription
    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)