Getting started

Send your first Canadian SMS in under 5 minutes.

Quickstart

  1. Create a free account — get live and test API keys instantly.
  2. Record CASL consent for each phone number you'll message.
  3. Optionally provision a Canadian long code number to send from.
  4. Send your first message using the REST API.

Authentication

All API requests require a Bearer token in the Authorization header. Use test keys (mk_test_...) for development — no real SMS are sent, no charges. Use live keys (mk_live_...) for production.

⚠️ Never expose API keys in client-side code or public repositories.

Phone Numbers

Search for available Canadian numbers, provision one, and use it as the from field when sending.

bash
curl https://api.honkio.ca/v1/phone-numbers/search?area_codes=416 \
  -H "Authorization: Bearer mk_live_YOUR_KEY"

# Response 200 (per result): what buying it charges now and monthly, in CAD cents
# { "phone_number": "+14165550100", "region": "Ontario",
#   "upfront_cost_cents": 250, "activation_fee_cents": 100, "monthly_cost_cents": 250, ... }

# Provision a number
curl -X POST https://api.honkio.ca/v1/phone-numbers \
  -H "Authorization: Bearer mk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"phone_number": "+14165550100"}'

# Accounts hold a limited number of numbers. GET /v1/accounts/me reports
# phone_number_limit and phone_numbers_used — check them before buying, or
# handle the 403 NUMBER_LIMIT_REACHED that a purchase past the cap returns.

# Ask HonkIO staff to raise the limit. One request may be pending at a time.
curl -X POST https://api.honkio.ca/v1/phone-numbers/allowance-requests \
  -H "Authorization: Bearer mk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"requested_limit": 10, "reason": "Onboarding three new clinics this quarter"}'

# Response 201: { "status": "PENDING", "requested_limit": 10, ... }
# You are emailed if it is approved; the decision also shows in the dashboard.

# Check on it
curl https://api.honkio.ca/v1/phone-numbers/allowance-requests \
  -H "Authorization: Bearer mk_live_YOUR_KEY"

CASL Consent (required before sending)

Under CASL, you must record consent before sending a commercial message to any recipient. The API will block sends to phone numbers without valid consent (HTTP 451).

bash
curl -X POST https://api.honkio.ca/v1/compliance/consents \
  -H "Authorization: Bearer mk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+16135550199",
    "consent_type": "express",
    "source_description": "Website opt-in form",
    "source_ip": "203.0.113.1"
  }'

Implied consent for an existing customer, with the clock running from their last transaction:

bash
curl -X POST https://api.honkio.ca/v1/compliance/consents \
  -H "Authorization: Bearer mk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+16135550199",
    "consent_type": "implied",
    "relationship_type": "purchase",
    "last_transaction_date": "2025-11-04"
  }'

# → { "status": "recorded", "phone_number": "+16135550199", "expires_at": "2027-11-04T00:00:00.000Z" }

Express consent never expires. Implied consent expires after 2 years per CASL §10(9). Pass last_transaction_date so the two-year clock runs from the real relationship rather than from the day you record it, or set expires_at outright when you have already worked out the expiry. The response echoes expires_at so you can verify it.

Sending SMS

Send a message using a provisioned number. The API validates the Canadian destination number, and checks CASL consent before delivery (CRTC DNCL checking coming soon).

bash
curl -X POST https://api.honkio.ca/v1/messages \
  -H "Authorization: Bearer mk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "+14165550100",
    "to":   "+16135550199",
    "body": "Hello from HonkIO! 🇨🇦"
  }'

Phone Number Verification (OTP)

Use the Verify API to confirm ownership of a phone number before sending commercial messages. Your end-user receives a one-time code via SMS; submit it to the check endpoint to confirm.

bash
# Start a verification (sends OTP SMS)
curl -X POST https://api.honkio.ca/v1/verify   -H "Authorization: Bearer mk_live_YOUR_KEY"   -H "Content-Type: application/json"   -d '{
    "from": "+14165550100",
    "to":   "+16135550199",
    "code_length": 6,
    "ttl_minutes": 10,
    "app_name": "Acme"
  }'
# Response: { "id": "clxxx...", "status": "pending", "code_length": 6, ... }

# Check the code submitted by your user
curl -X POST https://api.honkio.ca/v1/verify/clxxx.../check   -H "Authorization: Bearer mk_live_YOUR_KEY"   -H "Content-Type: application/json"   -d '{ "code": "483721" }'
# Response 200: { "status": "verified", ... }
# Response 422: { "code": "VERIFICATION_INVALID_CODE", "attempts_remaining": 4 }

# Fetch status at any time
curl https://api.honkio.ca/v1/verify/clxxx...   -H "Authorization: Bearer mk_live_YOUR_KEY"

In test mode the code is always all zeros for the chosen length (e.g. 000000 for 6-digit). No SMS is sent and nothing is billed. Every verification returns a "mode" field of "LIVE" or "TEST" so you can tell a simulated verification from a real one.

Pricing

Prices are set at runtime and can change without a release, so read them rather than hardcoding them. All amounts are CAD cents. Sending is billed per SMS part: message_cost_cents × the parts the carrier splits the body into. Parts are counted the way the carrier counts them — typographic quotes, dashes and ellipses are smart-encoded to GSM-7 (160 characters, then 153 per part), while emoji and most accented letters force Unicode parts (70, then 67). The charge is settled to the carrier's part count after the send, a message the carrier rejects costs nothing, and a body over 10 parts is refused with 422 MESSAGE_TOO_LONG before any charge. verification_cost_cents covers a typical single-part OTP; a long or non-GSM app_name can add a part. phone_number_activation_fee_cents is charged once, together with the first month, on every number provisioned — local or toll-free — and is not refunded on release. inbound_message_cost_cents is charged per part of every SMS received on a provisioned number, sender and carrier included, except STOP, START and HELP keywords; a received message is debited even if it takes the balance below zero, which pauses sending until the next top-up.

bash
# Current prices, in CAD cents
curl https://api.honkio.ca/v1/pricing \
  -H "Authorization: Bearer mk_live_YOUR_KEY"

# Response 200:
# {
#   "message_cost_cents": 3,
#   "verification_upcharge_cents": 25,
#   "verification_cost_cents": 28,
#   "phone_number_upfront_cost_cents": 250,
#   "phone_number_monthly_cost_cents": 250,
#   "phone_number_activation_fee_cents": 100,
#   "inbound_message_cost_cents": 3
# }

Test-mode requests are priced identically in the response but never charged, so you can see what an integration would cost before spending anything.

Sending limits

HonkIO is built for transactional and relationship messaging, not campaigns, and every customer’s deliverability rides on a shared carrier profile. These limits keep bulk marketing off the platform; a clinic, a contractor or a SaaS sending codes will not notice them. All apply to live mode; test mode is unaffected except for the link-shortener rule.

  • Daily cap: new accounts can send 250 live messages per rolling 24 hours. It does not lift on its own. 30 days after your first live message you can request a higher volume from the dashboard; approval sets 1,000 a day or the figure you asked for. Refused sends return 429 DAILY_LIMIT_REACHED with your limit and count.
  • Identical messages: the same body may reach at most 250 distinct recipients per 24 hours (429 FANOUT_LIMIT_REACHED). Personalized messages are unaffected.
  • Per-number rate: 60 messages per minute per sending number, which is what Canadian carriers grant a long code anyway (429 NUMBER_RATE_LIMITED with Retry-After).
  • Broadcasts: up to 250 recipients per contact-group broadcast and 3 broadcasts per 24 hours (422 BROADCAST_TOO_LARGE, 429 BROADCAST_LIMIT_REACHED).
  • Link shorteners (bit.ly, tinyurl and similar) are refused in both modes because carriers filter them (422 LINK_SHORTENER_BLOCKED). Use the full URL.
  • Automatic pause: if more than 1% of recipients reply STOP, or more than 5% of messages are rejected by carriers, over your recent sends, live sending pauses for 24 hours and you are emailed (403 SENDING_PAUSED with the resume time).
  • Top-ups: the balance cannot exceed $500 and top-ups are limited to $1,000 per 30 days. Raised on request.
bash
curl https://api.honkio.ca/v1/send-limit \
  -H "Authorization: Bearer mk_live_YOUR_KEY"

# → { "daily_limit": 250, "sent_last_24h": 12, "remaining": 238,
#     "probation": { "ends_at": "2026-09-26T14:02:11.000Z", "eligible_to_request": false },
#     "paused_until": null, "requests": [] }

Read your current limits and usage with GET /v1/send-limit, and file a request with POST /v1/send-limit/requests. Every figure above is a platform default that can be raised per account.

Webhooks

Register a webhook endpoint to receive delivery receipts and inbound messages. Every payload is signed with HMAC-SHA256 — verify the X-HonkIO-Signature header. A message.received event carries the sender, your number, the body, keyword_action (STOP/START handling), and the segment_count and cost_cents the message was billed at.

json
{
  "id": "evt_01HXYZ...",
  "type": "message.delivered",
  "created": "2024-01-15T12:00:00Z",
  "account_id": "acc_01HXYZ...",
  "data": {
    "message_id": "msg_01HXYZ...",
    "to": "+16135550199",
    "status": "delivered"
  }
}
// Headers: X-HonkIO-Signature, X-HonkIO-Timestamp, X-HonkIO-Event

AI agents (MCP)

HonkIO ships a Model Context Protocol server, so an AI coding agent can send SMS, run phone verification, buy Canadian numbers and check CASL consent on your behalf — in plain language, with no SDK to wire up. It runs on your machine and talks to this same REST API using your API key.

Add it to Claude Code, Claude Desktop, Cursor or VS Code. Nothing to install — npx fetches it on first use:

json
{
  "mcpServers": {
    "honkio": {
      "command": "npx",
      "args": ["-y", "@honkio/mcp"],
      "env": { "HONKIO_API_KEY": "mk_test_YOUR_KEY" }
    }
  }
}

Use a test key while you explore. Every tool works in test mode: messages come back as delivered, and nothing is sent or charged.

41 tools are available, covering:

  • Sending SMS, listing messages and reading delivery status
  • Phone verification — start a code, check it, list attempts
  • Searching, buying and releasing Canadian numbers
  • Recording and checking CASL consent, and handling opt-outs
  • Managing webhooks, and replaying failed deliveries
  • Account details, usage and API key management
  • Sending limits — check usage against the daily cap, request a higher volume, see the top-up allowance

Then just ask

"Find an available 416 number, tell me what it costs, and don't buy anything yet."

On a live key, buying a number and sending messages spend real money. Agents act on instructions that can be vaguer than you intended — explore with a test key first.
Full tool reference on npm

Error codes

HTTPCodeMeaning
401UNAUTHORIZEDMissing or invalid API key
402INSUFFICIENT_BALANCEAccount balance too low
403NUMBER_LIMIT_REACHEDAccount has reached its phone number limit
404VERIFICATION_NOT_FOUNDVerification ID not found or not owned by this account
409VERIFICATION_ALREADY_VERIFIEDThis number has already been verified
409PURCHASE_IN_PROGRESSAnother number purchase is in flight — retry shortly
409ALLOWANCE_REQUEST_PENDINGAn allowance request is already awaiting review
410VERIFICATION_EXPIREDThe verification code has expired
422VALIDATION_ERRORRequest body or query failed validation — see details
422NON_CANADIAN_NUMBERNot a valid Canadian E.164 number
422MESSAGE_TOO_LONGBody would exceed the carrier limit of 10 SMS parts (≈1,530 GSM-7 or 670 Unicode characters) — nothing is charged
422VERIFICATION_INVALID_CODEIncorrect code — attempts_remaining shows how many tries are left
422ALLOW_LIST_BLOCKEDRecipient is not on the API key's ALLOW list
422DENY_LIST_BLOCKEDRecipient is on the API key's DENY list
422INVALID_ALLOWANCE_REQUESTRequested allowance must exceed your current limit
429RATE_LIMITEDToo many requests — slow down
429VERIFICATION_MAX_ATTEMPTSToo many wrong attempts — this verification is locked
451OPT_OUT_BLOCKEDRecipient has opted out — legally blocked
451NO_CONSENTNo valid CASL consent on file
451CONSENT_EXPIREDImplied consent expired (2-year CASL limit)
451DNCL_BLOCKEDNumber on CRTC DNCL — no exemption applies (coming soon; not currently returned)