SDKs and tools
SDKs
The official HonkIO clients for Node.js, Python, PHP and Laravel: install, quickstart, results and errors, and every method with the page that explains it.
Install and quickstart
Install the package, then create a client with an API key.
npm install @honkio/node@honkio/node runs on Node 18 or newer, ships as ESM and CommonJS, and has no runtime dependencies. @honkio/node on npm
Create a key in the dashboard under API Keys → Create key; the full key is shown once, right after you create it. Start with a test key (mk_test_...): nothing is delivered or charged.
import { Honkio } from '@honkio/node'
const honkio = new Honkio(process.env.HONKIO_API_KEY)
const { data, error } = await honkio.messages.send({
from: '+1416XXXXXXX', // one of your HonkIO numbers
to: '+1613XXXXXXX', // a Canadian number you hold consent for
body: 'Hello from HonkIO!',
})
if (error) {
console.error(error.name, error.message) // e.g. NON_CANADIAN_NUMBER
} else {
console.log(data.id, data.status)
}new Honkio() with no argument reads HONKIO_API_KEY, and throws if neither is set.
Results and errors
data holds the API's response exactly as sent, in snake_case (scheduled_at, segment_count). On failure, error is { name, message, statusCode, details? }.
| name | When |
|---|---|
| an API code such as NON_CANADIAN_NUMBER | the API refused the request; statusCode is its HTTP status |
| network_error | no response arrived (DNS, a refused connection, a 30 second timeout); statusCode is null |
| application_error | the response body was not JSON, such as a proxy error page |
| invalid_argument | an id was empty, . or .., or a consent call named both or neither subject; nothing was sent |
| invalid_signature | webhooks.verify (or verifyWebhook) could not verify a delivery |
details is the API's details when it sent one (validation paths, retry_after, missing template keys). Otherwise it holds whatever the API sent beside the error envelope, such as attempts_remaining on VERIFICATION_INVALID_CODE.
Every error code the API answers with, and what to do about each, is on the Errors page.
Write request fields in camelCase (replyTo, scheduledAt, isCommercial, skipConsentCheck); they are sent as the API's snake_case. Keys inside variables, headers and metadata are yours and are sent exactly as written.
Options and keys
The second argument to new Honkio takes these options: baseUrl, which defaults to https://api.honkio.ca; fetch, which defaults to the global fetch (Node 18 or newer), so pass your own to use a polyfill or to intercept requests; and, from version 0.8.0, maxRetries and maxRetryDelayMs, which set how the client retries (below).
import { Honkio } from '@honkio/node'
const honkio = new Honkio('mk_live_...', {
baseUrl: 'https://api.honkio.ca', // default
// default: the global fetch. Pass your own to log or proxy requests:
fetch: (url, init) => {
console.log(init?.method ?? 'GET', url)
return fetch(url, init)
},
maxRetries: 2, // default; 0 turns retrying off (from version 0.8.0)
maxRetryDelayMs: 60_000, // default: the longest single wait, in milliseconds
})From version 0.8.0, the client retries a refusal that waiting fixes: NUMBER_RATE_LIMITED, CARRIER_THROTTLED and CARRIER_UNAVAILABLE. It waits as long as Retry-After asks, or backs off briefly when the API names no wait. RATE_LIMITED and network errors are retried only on a GET or on a request that carries an Idempotency-Key, and every messages.send, emails.send and batch.send now carries one: yours when you pass idempotencyKey, otherwise one the client makes and keeps for each retry of that call, so a retry cannot send or charge twice. By default it retries twice and never waits more than 60 seconds at a time; a longer wait comes back to you as the error. maxRetries: 0 turns retrying off, and maxRetryDelayMs sets the longest wait, in milliseconds. CARRIER_TIMEOUT is never retried: the message may have gone out.
A key's prefix is its mode: mk_test_ keys simulate everything and never reach the carrier or your balance; mk_live_ keys send for real. Nothing else about calling the SDK changes between them.
Method index
Every method on the client, the REST route it calls, and the page that explains it with examples in Node.js and cURL.
honkio.messages
| Method | Route | Docs |
|---|---|---|
| honkio.messages.send | POST /v1/messages | Sending messages |
| honkio.messages.list | GET /v1/messages | Sending messages |
| honkio.messages.get | GET /v1/messages/{id} | Sending messages |
| honkio.messages.update | PATCH /v1/messages/{id} | Sending messages |
| honkio.messages.cancel | DELETE /v1/messages/{id} | Sending messages |
honkio.phoneNumbers
| Method | Route | Docs |
|---|---|---|
| honkio.phoneNumbers.areaCodes | GET /v1/phone-numbers/area-codes | Phone numbers |
| honkio.phoneNumbers.search | GET /v1/phone-numbers/search | Phone numbers |
| honkio.phoneNumbers.provision | POST /v1/phone-numbers | Phone numbers |
| honkio.phoneNumbers.list | GET /v1/phone-numbers | Phone numbers |
| honkio.phoneNumbers.get | GET /v1/phone-numbers/{id} | Phone numbers |
| honkio.phoneNumbers.release | DELETE /v1/phone-numbers/{id} | Phone numbers |
The API does not read an Idempotency-Key on this route, so provision takes none: purchases on one account run one at a time (PURCHASE_IN_PROGRESS, retry shortly), and a number you already hold answers 409 CONFLICT, so a retry after a timeout cannot buy it twice. After a network_error, call list() before retrying.
honkio.tollFreeVerifications
| Method | Route | Docs |
|---|---|---|
| honkio.tollFreeVerifications.options | GET /v1/toll-free-verifications/options | Toll-free numbers |
| honkio.tollFreeVerifications.create | POST /v1/toll-free-verifications | Toll-free numbers |
| honkio.tollFreeVerifications.list | GET /v1/toll-free-verifications | Toll-free numbers |
| honkio.tollFreeVerifications.get | GET /v1/toll-free-verifications/{id} | Toll-free numbers |
| honkio.tollFreeVerifications.update | PATCH /v1/toll-free-verifications/{id} | Toll-free numbers |
| honkio.tollFreeVerifications.submit | POST /v1/toll-free-verifications/{id}/submit | Toll-free numbers |
| honkio.tollFreeVerifications.cancel | DELETE /v1/toll-free-verifications/{id} | Toll-free numbers |
honkio.businessVerification
| Method | Route | Docs |
|---|---|---|
| honkio.businessVerification.get | GET /v1/business-verification | Pricing and limits |
| honkio.businessVerification.save | PUT /v1/business-verification | Pricing and limits |
| honkio.businessVerification.submit | POST /v1/business-verification/submit | Pricing and limits |
honkio.autoTopup
| Method | Route | Docs |
|---|---|---|
| honkio.autoTopup.get | GET /v1/auto-topup | Pricing and limits |
honkio.topupCapRequests
| Method | Route | Docs |
|---|---|---|
| honkio.topupCapRequests.create | POST /v1/account/topup-cap-requests | Pricing and limits |
| honkio.topupCapRequests.list | GET /v1/account/topup-cap-requests | Pricing and limits |
honkio.sendLimit
| Method | Route | Docs |
|---|---|---|
| honkio.sendLimit.get | GET /v1/send-limit | Pricing and limits |
honkio.verify
| Method | Route | Docs |
|---|---|---|
| honkio.verify.start | POST /v1/verify | Verify (one-time codes) |
| honkio.verify.check | POST /v1/verify/{id}/check | Verify (one-time codes) |
| honkio.verify.get | GET /v1/verify/{id} | Verify (one-time codes) |
| honkio.verify.list | GET /v1/verify | Verify (one-time codes) |
start takes no idempotency key: one start per recipient per 60 seconds, and a retry inside that window answers RATE_LIMITED. After a network_error, list pending verifications and match the phone number before starting a new one.
honkio.consents
| Method | Route | Docs |
|---|---|---|
| honkio.consents.create | POST /v1/compliance/consents | Consent (CASL) |
| honkio.consents.list | GET /v1/compliance/consents | Consent (CASL) |
| honkio.consents.check | GET /v1/compliance/consents/check | Consent (CASL) |
| honkio.consents.revoke | DELETE /v1/compliance/consents/{phone} | Consent (CASL) |
| honkio.consents.revoke | DELETE /v1/compliance/consents/email/{address} | CASL and suppressions |
| honkio.consents.recordBatch | POST /v1/compliance/consents/batch | Consent (CASL) |
honkio.consentBatches
| Method | Route | Docs |
|---|---|---|
| honkio.consentBatches.list | GET /v1/compliance/consent-batches | Consent (CASL) |
| honkio.consentBatches.get | GET /v1/compliance/consent-batches/{id} | Consent (CASL) |
honkio.optInConfirmations
| Method | Route | Docs |
|---|---|---|
| honkio.optInConfirmations.send | POST /v1/compliance/opt-in-confirmations | Toll-free numbers |
| honkio.optInConfirmations.list | GET /v1/compliance/opt-in-confirmations | Toll-free numbers |
honkio.emails
| Method | Route | Docs |
|---|---|---|
| honkio.emails.send | POST /v1/emails | Sending and batch |
| honkio.emails.list | GET /v1/emails | Sending and batch |
| honkio.emails.get | GET /v1/emails/{id} | Sending and batch |
| honkio.emails.update | PATCH /v1/emails/{id} | Sending and batch |
| honkio.emails.cancel | DELETE /v1/emails/{id} | Sending and batch |
isCommercial defaults to false: email is transactional unless you set it. Marketing email needs isCommercial: true, CASL consent on file for the recipient, reaches exactly one recipient, and carries an unsubscribe footer.
honkio.batch
| Method | Route | Docs |
|---|---|---|
| honkio.batch.send | POST /v1/emails/batch | Sending and batch |
honkio.emails.received
| Method | Route | Docs |
|---|---|---|
| honkio.emails.received.list | GET /v1/emails/received | Receiving |
| honkio.emails.received.address | GET /v1/emails/received/address | Receiving |
| honkio.emails.received.setAddressEnabled | PATCH /v1/emails/received/address | Receiving |
| honkio.emails.received.get | GET /v1/emails/received/{id} | Receiving |
| honkio.emails.received.raw | GET /v1/emails/received/{id}/raw | Receiving |
| honkio.emails.received.attachment | GET /v1/emails/received/{id}/attachments/{attachmentId} | Receiving |
| honkio.emails.received.simulate | POST /v1/emails/received/simulate | Receiving |
raw and attachment download bytes, not JSON: both resolve data to a Blob, so read it with arrayBuffer() or pipe it to a file. Both answer ATTACHMENT_EXPIRED once the 40 day retention window has passed.
honkio.domains
| Method | Route | Docs |
|---|---|---|
| honkio.domains.create | POST /v1/email-domains | Domains |
| honkio.domains.list | GET /v1/email-domains | Domains |
| honkio.domains.get | GET /v1/email-domains/{id} | Domains |
| honkio.domains.update | PATCH /v1/email-domains/{id} | Domains |
| honkio.domains.verify | POST /v1/email-domains/{id}/verify | Domains |
| honkio.domains.remove | DELETE /v1/email-domains/{id} | Domains |
honkio.suppressions
| Method | Route | Docs |
|---|---|---|
| honkio.suppressions.create | POST /v1/email-suppressions | CASL and suppressions |
| honkio.suppressions.list | GET /v1/email-suppressions | CASL and suppressions |
| honkio.suppressions.remove | DELETE /v1/email-suppressions/{address} | CASL and suppressions |
honkio.templates
| Method | Route | Docs |
|---|---|---|
| honkio.templates.create | POST /v1/templates | Templates |
| honkio.templates.list | GET /v1/templates | Templates |
| honkio.templates.get | GET /v1/templates/{idOrAlias} | Templates |
| honkio.templates.update | PATCH /v1/templates/{idOrAlias} | Templates |
| honkio.templates.publish | POST /v1/templates/{idOrAlias}/publish | Templates |
| honkio.templates.rollback | POST /v1/templates/{idOrAlias}/rollback | Templates |
| honkio.templates.versions | GET /v1/templates/{idOrAlias}/versions | Templates |
| honkio.templates.remove | DELETE /v1/templates/{idOrAlias} | Templates |
honkio.emailTemplates
| Method | Route | Docs |
|---|---|---|
| honkio.emailTemplates.create | POST /v1/email-templates | Templates |
| honkio.emailTemplates.list | GET /v1/email-templates | Templates |
| honkio.emailTemplates.get | GET /v1/email-templates/{idOrAlias} | Templates |
| honkio.emailTemplates.update | PATCH /v1/email-templates/{idOrAlias} | Templates |
| honkio.emailTemplates.publish | POST /v1/email-templates/{idOrAlias}/publish | Templates |
| honkio.emailTemplates.rollback | POST /v1/email-templates/{idOrAlias}/rollback | Templates |
| honkio.emailTemplates.versions | GET /v1/email-templates/{idOrAlias}/versions | Templates |
| honkio.emailTemplates.remove | DELETE /v1/email-templates/{idOrAlias} | Templates |
Deprecated: use honkio.templates, which manages every template with its email, SMS and push parts in English and French. honkio.emailTemplates shows only the templates that have an email part, in the older email-only shape, and keeps working.
honkio.push
| Method | Route | Docs |
|---|---|---|
| honkio.push.send | POST /v1/push | Sending and fallback |
| honkio.push.list | GET /v1/push | Sending and fallback |
| honkio.push.get | GET /v1/push/{id} | Sending and fallback |
| honkio.push.update | PATCH /v1/push/{id} | Sending and fallback |
| honkio.push.cancel | DELETE /v1/push/{id} | Sending and fallback |
| honkio.push.usage | GET /v1/push/usage | Sending and fallback |
honkio.push.apps
| Method | Route | Docs |
|---|---|---|
| honkio.push.apps.create | POST /v1/push/apps | Credentials |
| honkio.push.apps.list | GET /v1/push/apps | Credentials |
| honkio.push.apps.get | GET /v1/push/apps/{id} | Credentials |
| honkio.push.apps.update | PATCH /v1/push/apps/{id} | Credentials |
| honkio.push.apps.delete | DELETE /v1/push/apps/{id} | Credentials |
honkio.push.web
| Method | Route | Docs |
|---|---|---|
| honkio.push.web.setVapidKeys | POST /v1/push/apps/{id}/web/vapid-keys | Web push (browsers) |
honkio.push.devices
honkio.notify
| Method | Route | Docs |
|---|---|---|
| honkio.notify.send | POST /v1/notify | Send a notification |
honkio.notifications
| Method | Route | Docs |
|---|---|---|
| honkio.notifications.get | GET /v1/notifications/{id} | Send a notification |
| honkio.notifications.list | GET /v1/notifications | Send a notification |
honkio.recipients
| Method | Route | Docs |
|---|---|---|
| honkio.recipients.upsert | PUT /v1/recipients/{user_id} | Recipient profiles |
| honkio.recipients.update | PATCH /v1/recipients/{user_id} | Recipient profiles |
| honkio.recipients.get | GET /v1/recipients/{user_id} | Recipient profiles |
| honkio.recipients.list | GET /v1/recipients | Recipient profiles |
| honkio.recipients.delete | DELETE /v1/recipients/{user_id} | Recipient profiles |
honkio.webhooks
| Method | Route | Docs |
|---|---|---|
| honkio.webhooks.create | POST /v1/webhooks | Overview |
| honkio.webhooks.list | GET /v1/webhooks | Overview |
| honkio.webhooks.get | GET /v1/webhooks/{id} | Overview |
| honkio.webhooks.update | PATCH /v1/webhooks/{id} | Overview |
| honkio.webhooks.remove | DELETE /v1/webhooks/{id} | Overview |
| honkio.webhooks.deliveries | GET /v1/webhooks/{id}/deliveries | Overview |
| honkio.webhooks.deadLetters | GET /v1/webhooks/{id}/dead-letters | Overview |
| honkio.webhooks.replay | POST /v1/webhooks/dead-letters/{dlid}/replay | Overview |
| honkio.webhooks.discard | DELETE /v1/webhooks/dead-letters/{dlid} | Overview |
| honkio.webhooks.reactivate | POST /v1/webhooks/{id}/reactivate | Overview |
| honkio.webhooks.rotateSecret | POST /v1/webhooks/{id}/rotate-secret | Verifying signatures |
honkio.authApps
HonkIO Auth’s management API: your sign-in apps and their users, organizations, security settings and plan. Each row also gives the call in the Python and PHP SDKs, and links to the route in the Auth API reference, with its permission, its fields and a cURL example.
| Method | Route | Docs |
|---|---|---|
| honkio.authApps.createPythonclient.auth_apps.createPHP$honkio->authApps->create | POST /v1/auth-apps | Create an app |
| honkio.authApps.listPythonclient.auth_apps.listPHP$honkio->authApps->list | GET /v1/auth-apps | List apps |
| honkio.authApps.getPythonclient.auth_apps.getPHP$honkio->authApps->get | GET /v1/auth-apps/{id} | Get an app |
| honkio.authApps.updatePythonclient.auth_apps.updatePHP$honkio->authApps->update | PATCH /v1/auth-apps/{id} | Update an app |
| honkio.authApps.removePythonclient.auth_apps.removePHP$honkio->authApps->remove | DELETE /v1/auth-apps/{id} | Delete an app |
| honkio.authApps.rotateSecretPythonclient.auth_apps.rotate_secretPHP$honkio->authApps->rotateSecret | POST /v1/auth-apps/{id}/rotate-secret | Rotate the client secret |
| honkio.authApps.verifyDomainPythonclient.auth_apps.verify_domainPHP$honkio->authApps->verifyDomain | POST /v1/auth-apps/{id}/domain/verify | Verify the domain |
| honkio.authApps.cancelHostnameChangePythonclient.auth_apps.cancel_hostname_changePHP$honkio->authApps->cancelHostnameChange | DELETE /v1/auth-apps/{id}/hostname-change | Cancel a hostname change |
| honkio.authApps.uploadLogoPythonclient.auth_apps.upload_logoPHP$honkio->authApps->uploadLogo | PUT /v1/auth-apps/{id}/logo | Upload a logo |
| honkio.authApps.removeLogoPythonclient.auth_apps.remove_logoPHP$honkio->authApps->removeLogo | DELETE /v1/auth-apps/{id}/logo | Remove the logo |
honkio.authApps.security
| Method | Route | Docs |
|---|---|---|
| honkio.authApps.security.getPythonclient.auth_apps.security.getPHP$honkio->authApps->security->get | GET /v1/auth-apps/{id}/security | Get security settings |
| honkio.authApps.security.updatePythonclient.auth_apps.security.updatePHP$honkio->authApps->security->update | PATCH /v1/auth-apps/{id}/security | Update security settings |
| honkio.authApps.security.summaryPythonclient.auth_apps.security.summaryPHP$honkio->authApps->security->summary | GET /v1/auth-apps/{id}/security/summary | Get the security summary |
honkio.authApps.blocklist
| Method | Route | Docs |
|---|---|---|
| honkio.authApps.blocklist.listPythonclient.auth_apps.blocklist.listPHP$honkio->authApps->security->blocklist | GET /v1/auth-apps/{id}/security/blocklist | List blocklist entries |
| honkio.authApps.blocklist.addPythonclient.auth_apps.blocklist.addPHP$honkio->authApps->security->addToBlocklist | POST /v1/auth-apps/{id}/security/blocklist | Add a blocklist entry |
| honkio.authApps.blocklist.removePythonclient.auth_apps.blocklist.removePHP$honkio->authApps->security->removeFromBlocklist | DELETE /v1/auth-apps/{id}/security/blocklist/{entryId} | Remove a blocklist entry |
honkio.authApps.plan
| Method | Route | Docs |
|---|---|---|
| honkio.authApps.plan.getPythonclient.auth_apps.plan.getPHP$honkio->authApps->plan->get | GET /v1/auth-apps/{id}/plan | Get the plan |
| honkio.authApps.plan.changePythonclient.auth_apps.plan.changePHP$honkio->authApps->plan->change | PUT /v1/auth-apps/{id}/plan | Change the plan |
| honkio.authApps.plan.cancelScheduledPythonclient.auth_apps.plan.cancel_scheduledPHP$honkio->authApps->plan->cancelScheduledChange | DELETE /v1/auth-apps/{id}/plan/scheduled | Cancel a scheduled downgrade |
| honkio.authApps.plan.costsPythonclient.auth_apps.plan.costsPHP$honkio->authApps->plan->costs | GET /v1/auth-apps/{id}/costs | Get sign-in costs |
| honkio.authApps.plan.usagePythonclient.auth_apps.plan.usagePHP$honkio->authApps->plan->usage | GET /v1/auth-apps/{id}/usage | Get usage |
honkio.authApps.signInPolicy
| Method | Route | Docs |
|---|---|---|
| honkio.authApps.signInPolicy.getPythonclient.auth_apps.sign_in_policy.getPHP$honkio->authApps->signInPolicy | GET /v1/auth-apps/{id}/sign-in-policy | Get the sign-in policy |
| honkio.authApps.signInPolicy.updatePythonclient.auth_apps.sign_in_policy.updatePHP$honkio->authApps->updateSignInPolicy | PATCH /v1/auth-apps/{id}/sign-in-policy | Update the sign-in policy |
honkio.authApps.organizations
| Method | Route | Docs |
|---|---|---|
| honkio.authApps.organizations.listPythonclient.auth_apps.organizations.listPHP$honkio->authApps->organizations->list | GET /v1/auth-apps/{id}/organizations | List organizations |
| honkio.authApps.organizations.createPythonclient.auth_apps.organizations.createPHP$honkio->authApps->organizations->create | POST /v1/auth-apps/{id}/organizations | Create an organization |
| honkio.authApps.organizations.getPythonclient.auth_apps.organizations.getPHP$honkio->authApps->organizations->get | GET /v1/auth-apps/{id}/organizations/{orgId} | Get an organization |
| honkio.authApps.organizations.updatePythonclient.auth_apps.organizations.updatePHP$honkio->authApps->organizations->update | PATCH /v1/auth-apps/{id}/organizations/{orgId} | Update an organization |
| honkio.authApps.organizations.removePythonclient.auth_apps.organizations.removePHP$honkio->authApps->organizations->remove | DELETE /v1/auth-apps/{id}/organizations/{orgId} | Delete an organization |
honkio.authApps.memberships
| Method | Route | Docs |
|---|---|---|
| honkio.authApps.memberships.listPythonclient.auth_apps.memberships.listPHP$honkio->authApps->organizations->memberships | GET /v1/auth-apps/{id}/organizations/{orgId}/memberships | List members |
| honkio.authApps.memberships.addPythonclient.auth_apps.memberships.addPHP$honkio->authApps->organizations->addMembership | POST /v1/auth-apps/{id}/organizations/{orgId}/memberships | Add a member |
| honkio.authApps.memberships.getPythonclient.auth_apps.memberships.getPHP$honkio->authApps->organizations->membership | GET /v1/auth-apps/{id}/organizations/{orgId}/memberships/{userId} | Get a membership |
| honkio.authApps.memberships.updatePythonclient.auth_apps.memberships.updatePHP$honkio->authApps->organizations->updateMembership | PATCH /v1/auth-apps/{id}/organizations/{orgId}/memberships/{userId} | Change a member’s role |
| honkio.authApps.memberships.removePythonclient.auth_apps.memberships.removePHP$honkio->authApps->organizations->removeMembership | DELETE /v1/auth-apps/{id}/organizations/{orgId}/memberships/{userId} | Remove a member |
honkio.authApps.roles
| Method | Route | Docs |
|---|---|---|
| honkio.authApps.roles.listPythonclient.auth_apps.roles.listPHP$honkio->authApps->organizations->roles | GET /v1/auth-apps/{id}/organizations/{orgId}/roles | List roles |
| honkio.authApps.roles.putPythonclient.auth_apps.roles.putPHP$honkio->authApps->organizations->putRole | PUT /v1/auth-apps/{id}/organizations/{orgId}/roles/{name} | Create or update a role |
| honkio.authApps.roles.removePythonclient.auth_apps.roles.removePHP$honkio->authApps->organizations->removeRole | DELETE /v1/auth-apps/{id}/organizations/{orgId}/roles/{name} | Delete a role |
roles.create(appId, orgId, { name }) and roles.update(appId, orgId, name, body) also exist: both are put(), which creates the role or changes it, under another name. Python has the same pair, and PHP has createRole and updateRole.
honkio.authApps.invitations
| Method | Route | Docs |
|---|---|---|
| honkio.authApps.invitations.listPythonclient.auth_apps.invitations.listPHP$honkio->authApps->invitations->list | GET /v1/auth-apps/{id}/invitations | List invitations |
| honkio.authApps.invitations.createPythonclient.auth_apps.invitations.createPHP$honkio->authApps->invitations->create | POST /v1/auth-apps/{id}/invitations | Create an invitation |
| honkio.authApps.invitations.getPythonclient.auth_apps.invitations.getPHP$honkio->authApps->invitations->get | GET /v1/auth-apps/{id}/invitations/{invitationId} | Get an invitation |
| honkio.authApps.invitations.revokePythonclient.auth_apps.invitations.revokePHP$honkio->authApps->invitations->revoke | DELETE /v1/auth-apps/{id}/invitations/{invitationId} | Revoke an invitation |
honkio.authApps.users
| Method | Route | Docs |
|---|---|---|
| honkio.authApps.users.listPythonclient.auth_apps.users.listPHP$honkio->authApps->users->list | GET /v1/auth-apps/{id}/users | List users |
| honkio.authApps.users.getPythonclient.auth_apps.users.getPHP$honkio->authApps->users->get | GET /v1/auth-apps/{id}/users/{userId} | Get a user |
| honkio.authApps.users.updatePythonclient.auth_apps.users.updatePHP$honkio->authApps->users->update | PATCH /v1/auth-apps/{id}/users/{userId} | Update a user |
| honkio.authApps.users.blockPythonclient.auth_apps.users.blockPHP$honkio->authApps->users->block | POST /v1/auth-apps/{id}/users/{userId}/block | Block a user |
| honkio.authApps.users.unblockPythonclient.auth_apps.users.unblockPHP$honkio->authApps->users->unblock | POST /v1/auth-apps/{id}/users/{userId}/unblock | Unblock a user |
| honkio.authApps.users.removePythonclient.auth_apps.users.removePHP$honkio->authApps->users->remove | DELETE /v1/auth-apps/{id}/users/{userId} | Delete a user |
| honkio.authApps.users.removeLikelyBotsPythonclient.auth_apps.users.remove_likely_botsPHP$honkio->authApps->users->removeLikelyBots | POST /v1/auth-apps/{id}/users/bulk-delete | Delete likely bots |
| honkio.authApps.users.sessionsPythonclient.auth_apps.users.sessionsPHP$honkio->authApps->users->sessions | GET /v1/auth-apps/{id}/users/{userId}/sessions | List a user’s sessions |
| honkio.authApps.users.signInsPythonclient.auth_apps.users.sign_insPHP$honkio->authApps->users->signIns | GET /v1/auth-apps/{id}/users/{userId}/sign-ins | List a user’s sign-ins |
| honkio.authApps.users.passkeysPythonclient.auth_apps.users.passkeysPHP$honkio->authApps->users->passkeys | GET /v1/auth-apps/{id}/users/{userId}/passkeys | List a user’s passkeys |
| honkio.authApps.users.removePasskeyPythonclient.auth_apps.users.remove_passkeyPHP$honkio->authApps->users->removePasskey | DELETE /v1/auth-apps/{id}/users/{userId}/passkeys/{passkeyId} | Remove a user’s passkey |
honkio.authApps.sessions
| Method | Route | Docs |
|---|---|---|
| honkio.authApps.sessions.revokePythonclient.auth_apps.sessions.revokePHP$honkio->authApps->users->revokeSession | DELETE /v1/auth-apps/{id}/sessions/{sessionId} | Revoke a session |
honkio.authApps.signIns
| Method | Route | Docs |
|---|---|---|
| honkio.authApps.signIns.listPythonclient.auth_apps.sign_ins.listPHP$honkio->authApps->signIns | GET /v1/auth-apps/{id}/sign-ins | List sign-in history |
honkio.authApps.exports
| Method | Route | Docs |
|---|---|---|
| honkio.authApps.exports.createPythonclient.auth_apps.exports.createPHP$honkio->authApps->exports->create | POST /v1/auth-apps/{id}/exports | Start an export |
| honkio.authApps.exports.latestPythonclient.auth_apps.exports.latestPHP$honkio->authApps->exports->latest | GET /v1/auth-apps/{id}/exports/latest | Get the latest export |
| honkio.authApps.exports.getPythonclient.auth_apps.exports.getPHP$honkio->authApps->exports->get | GET /v1/auth-apps/{id}/exports/{exportId} | Get an export |
| honkio.authApps.exports.downloadPythonclient.auth_apps.exports.downloadPHP$honkio->authApps->exports->download | GET /v1/auth-exports/{token} | Download an export |
download takes the token or the whole download_url and resolves data to a Blob holding the JSON file. The link works once, for 15 minutes after the export is ready. It is its own credential, so the route needs no API key and the public OpenAPI document leaves it out.
Webhooks
Pass the raw body, not parsed JSON, to webhooks.verify (or the standalone verifyWebhook export). It never throws: a parsed body, a missing secret or missing headers answer invalid_argument, and a bad or stale signature answers invalid_signature.
const webhookSecret = process.env.HONKIO_WEBHOOK_SECRET
if (!webhookSecret) throw new Error('Set HONKIO_WEBHOOK_SECRET')
app.post('/webhooks/honkio', express.raw({ type: 'application/json' }), (req, res) => {
const { data: event, error } = honkio.webhooks.verify(req.body, req.headers, webhookSecret)
if (error) return res.status(400).end()
if (!event.livemode) console.log('test event')
res.status(200).end()
})The signature is HMAC-SHA256, hex, over the timestamp and the raw body, sent in X-HonkIO-Signature with X-HonkIO-Timestamp and X-HonkIO-Event. Deliveries older than 300 seconds are refused by default; change it with toleranceSeconds.
A verified event is the envelope: id, type, created, account_id, livemode, data. Deduplicate on id: a failed delivery is retried for about 24 hours, each attempt signed afresh with the same event id.
deliveries(id) lists recent attempts. Events that failed every attempt are dead letters: deadLetters(id) lists them, replay(deadLetterId) sends one again, and discard(deadLetterId) drops it. reactivate(id) re-enables an endpoint the platform disabled.
Pagination
List calls follow one of three shapes. Page based (page, limit, and a meta object with page, limit, total and pages): messages.list and consents.list. Offset based (limit, offset): verify.list. Cursor based (limit, cursor, answering with data, has_more and next_cursor): emails.list, emails.received.list, suppressions.list and templates.list. Everything else, such as phoneNumbers.list and webhooks.list, returns every row in one call.
Python
The honkio package needs Python 3.10 or newer and depends only on httpx. It is typed, and has a synchronous client (Honkio) and an asynchronous one (AsyncHonkio) with the same resources and arguments.
pip install honkioCreate a key as above. Honkio() with no argument reads HONKIO_API_KEY and raises InvalidArgumentError if neither is set. Arguments after the id are keyword-only and named like the API's fields; from is a Python keyword, so it is from_. Leave an argument out to not send it; pass None to send null.
from honkio import ApiError, Honkio
client = Honkio() # reads HONKIO_API_KEY
try:
message = client.messages.send(
from_="+1416XXXXXXX", # one of your HonkIO numbers
to="+1613XXXXXXX", # a Canadian number you hold consent for
body="Hello from HonkIO!",
idempotency_key="order-1042",
)
print(message["id"], message["status"])
except ApiError as e:
print(e.code, e.message) # e.g. NON_CANADIAN_NUMBERSend an email the same way:
import os
from honkio import Honkio
client = Honkio(os.environ["HONKIO_API_KEY"])
sent = client.emails.send(
from_="Acme <onboarding@test.honkio.ca>",
to="delivered@test.honkio.ca",
subject="Hello from HonkIO",
html="<p>It works.</p>",
)
print(sent["id"])The same calls, asynchronously:
import asyncio
from honkio import AsyncHonkio
async def main() -> None:
async with AsyncHonkio() as client:
message = await client.messages.send(from_="+1416XXXXXXX", to="+1613XXXXXXX", body="Hi")
print(message["id"])
asyncio.run(main())Unlike the Node.js SDK, errors are exceptions. Every API error is a subclass of ApiError, with code, message, status_code and details. No response raises APIConnectionError or APITimeoutError, and a bad argument raises InvalidArgumentError. From version 0.2.0, some refusals are retried before an exception is raised (below).
from honkio import APIConnectionError, ApiError, UnprocessableEntityError
try:
client.verify.check("VERIFICATION_ID", code="123456")
except UnprocessableEntityError as e:
print(e.code, e.details) # e.g. VERIFICATION_INVALID_CODE {"attempts_remaining": 2}
except ApiError as e:
print(e.status_code, e.code, e.message)
except APIConnectionError:
... # no response arrived (timeouts too): safe to try the check againFrom version 0.2.0, Honkio and AsyncHonkio retry NUMBER_RATE_LIMITED, CARRIER_THROTTLED and CARRIER_UNAVAILABLE, waiting as long as Retry-After asks. RATE_LIMITED and connection errors are retried only on a GET or on a request with an idempotency key, and messages.send, emails.send and batch.send always send one: your idempotency_key, or one the client makes and keeps for each retry of that call. The defaults are 2 retries and no single wait over 60 seconds; when retrying does not help, the last exception is raised. max_retries=0 turns retrying off, and max_retry_delay sets the longest wait, in seconds.
from honkio import Honkio
# The defaults (from version 0.2.0): 2 retries, no single wait over 60 seconds.
client = Honkio(max_retries=2, max_retry_delay=60)
# Retrying off: every refusal is raised at once.
no_retries = Honkio(max_retries=0)Every error code the API answers with, and what to do about each, is on the Errors page.
verify_webhook takes the raw body (str or bytes), the headers and your signing secret. It returns the parsed event, and raises InvalidSignatureError when the signature, the timestamp (more than 300 seconds old) or the body is wrong. Pass the body before any JSON parsing.
import os
from honkio import InvalidSignatureError, verify_webhook
WEBHOOK_SECRET = os.environ["HONKIO_WEBHOOK_SECRET"]
def handle(request): # a Flask-style request
try:
event = verify_webhook(request.get_data(), request.headers, WEBHOOK_SECRET)
except InvalidSignatureError:
return "bad signature", 400
if not event["livemode"]:
print("test event")
return "", 204PHP
honkio/honkio-php needs PHP 8.2 or newer and Guzzle 7. Requests are arrays with the API's own snake_case keys, and responses are decoded arrays. Omit a key to not send it; null sends null.
composer require honkio/honkio-phpCreate a key as above. new Honkio() with no argument reads HONKIO_API_KEY and throws InvalidArgumentException if neither is set.
<?php
use Honkio\Exception\ApiException;
use Honkio\Honkio;
$honkio = new Honkio(); // reads HONKIO_API_KEY
try {
$message = $honkio->messages->send([
'from' => '+1416XXXXXXX', // one of your HonkIO numbers
'to' => '+1613XXXXXXX', // a Canadian number you hold consent for
'body' => 'Hello from HonkIO!',
], idempotencyKey: 'order-1042');
echo $message['id'], ' ', $message['status'], "\n";
} catch (ApiException $e) {
echo $e->errorCode, ': ', $e->getMessage(), "\n"; // e.g. NON_CANADIAN_NUMBER
}Send an email the same way:
<?php
use Honkio\Honkio;
$honkio = new Honkio(getenv('HONKIO_API_KEY'));
$email = $honkio->emails->send([
'from' => 'Acme <onboarding@test.honkio.ca>',
'to' => 'delivered@test.honkio.ca',
'subject' => 'Hello from HonkIO',
'html' => '<p>It works.</p>',
]);
echo $email['id'], "\n";Errors are exceptions, and each one implements Honkio\Exception\HonkioException. An API error is an ApiException (or the subclass for its status) with errorCode, statusCode, getMessage() and details. No response throws ConnectionException, and the send may or may not have happened. Pass an idempotency key as the last argument of messages->send, emails->send and batch->send, and reuse it when you retry.
<?php
use Honkio\Exception\ApiException;
use Honkio\Exception\ConnectionException;
try {
$honkio->verify->check('VERIFICATION_ID', ['code' => '123456']);
} catch (ApiException $e) {
if ($e->errorCode === 'VERIFICATION_INVALID_CODE') {
echo $e->details['attempts_remaining'];
}
} catch (ConnectionException $e) {
// No response arrived: safe to try the check again.
}From version 0.2.0, the client retries NUMBER_RATE_LIMITED, CARRIER_THROTTLED and CARRIER_UNAVAILABLE, waiting as long as Retry-After asks. RATE_LIMITED and connection errors are retried only on a GET or on a request with an idempotency key, and messages->send, emails->send and batch->send always send one: yours, or one the client makes and keeps for each retry of that call. The defaults are 2 retries and no single wait over 60 seconds; when retrying does not help, the last exception is thrown. In the options array, max_retries => 0 turns retrying off, and max_retry_delay sets the longest wait, in seconds.
<?php
use Honkio\Honkio;
// The defaults (from version 0.2.0): 2 retries, no single wait over 60 seconds.
$honkio = new Honkio(null, ['max_retries' => 2, 'max_retry_delay' => 60]);
// Retrying off: every refusal is thrown at once.
$noRetries = new Honkio(null, ['max_retries' => 0]);Every error code the API answers with, and what to do about each, is on the Errors page.
Webhook::verify takes the raw body, the headers and your signing secret. It returns the event as an array, and throws SignatureVerificationException when it cannot verify a delivery. Pass the body before any JSON parsing.
<?php
use Honkio\Exception\SignatureVerificationException;
use Honkio\Webhook;
$rawBody = file_get_contents('php://input');
try {
$event = Webhook::verify($rawBody, getallheaders(), getenv('HONKIO_WEBHOOK_SECRET'));
} catch (SignatureVerificationException $e) {
http_response_code(400);
exit;
}
if (! $event['livemode']) {
error_log('test event');
}
http_response_code(204);Laravel
honkio/laravel adds a mail transport and an SMS notification channel on top of the PHP SDK. It needs PHP 8.2 or newer and Laravel 12 or 13, and Laravel discovers it automatically.
composer require honkio/laravelSet your key, and the number notifications send from (HONKIO_BASE_URL is only for pointing at another API host):
HONKIO_API_KEY=mk_test_...
HONKIO_SMS_FROM=+14165550100
# MAIL_MAILER=honkioFrom version 0.2.0, HONKIO_MAX_RETRIES (default 2; 0 turns retrying off) and HONKIO_MAX_RETRY_DELAY (seconds, default 60) set the client’s retries, as for the PHP SDK.
# The defaults, from version 0.2.0
HONKIO_MAX_RETRIES=2
HONKIO_MAX_RETRY_DELAY=60Add a mailer to config/mail.php, then set MAIL_MAILER=honkio or call Mail::mailer('honkio'). The from address must belong to a verified sending domain on your account.
<?php
// config/mail.php
return [
'mailers' => [
'honkio' => ['transport' => 'honkio'],
],
];Email is transactional by default. Mark a Mailable commercial with the X-HonkIO-Commercial header: the transport removes it and sends is_commercial as true, and the API then requires CASL consent on file and one recipient per message. A refusal throws the SDK exception from Mail::send(). Retries of queued mail are not deduplicated, so a retried job can send again.
<?php
use Illuminate\Mail\Mailable;
use Symfony\Component\Mime\Email;
class SpringSale extends Mailable
{
public function build(): static
{
return $this
->subject('Spring sale')
->view('mail.spring-sale')
->withSymfonyMessage(fn (Email $message) => $message->getHeaders()->addTextHeader('X-HonkIO-Commercial', 'true'));
}
}An SMS notification uses the honkio channel and returns a HonkioMessage from toHonkio. The recipient's number (E.164) comes from routeNotificationForHonkio on the notifiable, and a notifiable with no number is skipped without calling the API. Every recipient needs consent on file under CASL. Notifications carry an idempotency key, so a retried queued job cannot send or charge twice.
<?php
use Honkio\Laravel\Notifications\HonkioMessage;
use Illuminate\Notifications\Notification;
class ShipmentSent extends Notification
{
public function via($notifiable): array
{
return ['honkio'];
}
public function toHonkio($notifiable): HonkioMessage
{
return HonkioMessage::create('Your order has shipped.')
->from('+14165550100'); // optional, defaults to HONKIO_SMS_FROM
}
}<?php
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use Notifiable;
// The recipient's number, in E.164. A user with none is skipped without calling the API.
public function routeNotificationForHonkio($notification): ?string
{
return $this->phone_number;
}
}The client is also available as the Honkio facade (Honkio::messages()->send([...])) and, as a singleton, by type-hinting Honkio\Honkio.
HonkIO