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.

bash
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.

javascript
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? }.

nameWhen
an API code such as NON_CANADIAN_NUMBERthe API refused the request; statusCode is its HTTP status
network_errorno response arrived (DNS, a refused connection, a 30 second timeout); statusCode is null
application_errorthe response body was not JSON, such as a proxy error page
invalid_argumentan id was empty, . or .., or a consent call named both or neither subject; nothing was sent
invalid_signaturewebhooks.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).

javascript
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

MethodRouteDocs
honkio.messages.sendPOST /v1/messagesSending messages
honkio.messages.listGET /v1/messagesSending messages
honkio.messages.getGET /v1/messages/{id}Sending messages
honkio.messages.updatePATCH /v1/messages/{id}Sending messages
honkio.messages.cancelDELETE /v1/messages/{id}Sending messages

honkio.phoneNumbers

MethodRouteDocs
honkio.phoneNumbers.areaCodesGET /v1/phone-numbers/area-codesPhone numbers
honkio.phoneNumbers.provisionPOST /v1/phone-numbersPhone numbers
honkio.phoneNumbers.listGET /v1/phone-numbersPhone numbers
honkio.phoneNumbers.getGET /v1/phone-numbers/{id}Phone numbers
honkio.phoneNumbers.releaseDELETE /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

MethodRouteDocs
honkio.tollFreeVerifications.optionsGET /v1/toll-free-verifications/optionsToll-free numbers
honkio.tollFreeVerifications.createPOST /v1/toll-free-verificationsToll-free numbers
honkio.tollFreeVerifications.listGET /v1/toll-free-verificationsToll-free numbers
honkio.tollFreeVerifications.getGET /v1/toll-free-verifications/{id}Toll-free numbers
honkio.tollFreeVerifications.updatePATCH /v1/toll-free-verifications/{id}Toll-free numbers
honkio.tollFreeVerifications.submitPOST /v1/toll-free-verifications/{id}/submitToll-free numbers
honkio.tollFreeVerifications.cancelDELETE /v1/toll-free-verifications/{id}Toll-free numbers

honkio.businessVerification

MethodRouteDocs
honkio.businessVerification.getGET /v1/business-verificationPricing and limits
honkio.businessVerification.savePUT /v1/business-verificationPricing and limits
honkio.businessVerification.submitPOST /v1/business-verification/submitPricing and limits

honkio.autoTopup

MethodRouteDocs
honkio.autoTopup.getGET /v1/auto-topupPricing and limits

honkio.topupCapRequests

MethodRouteDocs
honkio.topupCapRequests.createPOST /v1/account/topup-cap-requestsPricing and limits
honkio.topupCapRequests.listGET /v1/account/topup-cap-requestsPricing and limits

honkio.sendLimit

MethodRouteDocs
honkio.sendLimit.getGET /v1/send-limitPricing and limits

honkio.verify

MethodRouteDocs
honkio.verify.startPOST /v1/verifyVerify (one-time codes)
honkio.verify.checkPOST /v1/verify/{id}/checkVerify (one-time codes)
honkio.verify.getGET /v1/verify/{id}Verify (one-time codes)
honkio.verify.listGET /v1/verifyVerify (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

MethodRouteDocs
honkio.consents.createPOST /v1/compliance/consentsConsent (CASL)
honkio.consents.listGET /v1/compliance/consentsConsent (CASL)
honkio.consents.checkGET /v1/compliance/consents/checkConsent (CASL)
honkio.consents.revokeDELETE /v1/compliance/consents/{phone}Consent (CASL)
honkio.consents.revokeDELETE /v1/compliance/consents/email/{address}CASL and suppressions
honkio.consents.recordBatchPOST /v1/compliance/consents/batchConsent (CASL)
MethodRouteDocs

honkio.optInConfirmations

MethodRouteDocs
honkio.optInConfirmations.sendPOST /v1/compliance/opt-in-confirmationsToll-free numbers
honkio.optInConfirmations.listGET /v1/compliance/opt-in-confirmationsToll-free numbers

honkio.emails

MethodRouteDocs
honkio.emails.sendPOST /v1/emailsSending and batch
honkio.emails.listGET /v1/emailsSending and batch
honkio.emails.getGET /v1/emails/{id}Sending and batch
honkio.emails.updatePATCH /v1/emails/{id}Sending and batch
honkio.emails.cancelDELETE /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

MethodRouteDocs
honkio.batch.sendPOST /v1/emails/batchSending and batch

honkio.emails.received

MethodRouteDocs
honkio.emails.received.listGET /v1/emails/receivedReceiving
honkio.emails.received.addressGET /v1/emails/received/addressReceiving
honkio.emails.received.setAddressEnabledPATCH /v1/emails/received/addressReceiving
honkio.emails.received.getGET /v1/emails/received/{id}Receiving
honkio.emails.received.rawGET /v1/emails/received/{id}/rawReceiving
honkio.emails.received.attachmentGET /v1/emails/received/{id}/attachments/{attachmentId}Receiving
honkio.emails.received.simulatePOST /v1/emails/received/simulateReceiving

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

MethodRouteDocs
honkio.domains.createPOST /v1/email-domainsDomains
honkio.domains.listGET /v1/email-domainsDomains
honkio.domains.getGET /v1/email-domains/{id}Domains
honkio.domains.updatePATCH /v1/email-domains/{id}Domains
honkio.domains.verifyPOST /v1/email-domains/{id}/verifyDomains
honkio.domains.removeDELETE /v1/email-domains/{id}Domains

honkio.suppressions

MethodRouteDocs
honkio.suppressions.createPOST /v1/email-suppressionsCASL and suppressions
honkio.suppressions.listGET /v1/email-suppressionsCASL and suppressions
honkio.suppressions.removeDELETE /v1/email-suppressions/{address}CASL and suppressions

honkio.templates

MethodRouteDocs
honkio.templates.createPOST /v1/templatesTemplates
honkio.templates.listGET /v1/templatesTemplates
honkio.templates.getGET /v1/templates/{idOrAlias}Templates
honkio.templates.updatePATCH /v1/templates/{idOrAlias}Templates
honkio.templates.publishPOST /v1/templates/{idOrAlias}/publishTemplates
honkio.templates.rollbackPOST /v1/templates/{idOrAlias}/rollbackTemplates
honkio.templates.versionsGET /v1/templates/{idOrAlias}/versionsTemplates
honkio.templates.removeDELETE /v1/templates/{idOrAlias}Templates

honkio.emailTemplates

MethodRouteDocs
honkio.emailTemplates.createPOST /v1/email-templatesTemplates
honkio.emailTemplates.listGET /v1/email-templatesTemplates
honkio.emailTemplates.getGET /v1/email-templates/{idOrAlias}Templates
honkio.emailTemplates.updatePATCH /v1/email-templates/{idOrAlias}Templates
honkio.emailTemplates.publishPOST /v1/email-templates/{idOrAlias}/publishTemplates
honkio.emailTemplates.rollbackPOST /v1/email-templates/{idOrAlias}/rollbackTemplates
honkio.emailTemplates.versionsGET /v1/email-templates/{idOrAlias}/versionsTemplates
honkio.emailTemplates.removeDELETE /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

MethodRouteDocs
honkio.push.sendPOST /v1/pushSending and fallback
honkio.push.listGET /v1/pushSending and fallback
honkio.push.getGET /v1/push/{id}Sending and fallback
honkio.push.updatePATCH /v1/push/{id}Sending and fallback
honkio.push.cancelDELETE /v1/push/{id}Sending and fallback
honkio.push.usageGET /v1/push/usageSending and fallback

honkio.push.apps

MethodRouteDocs
honkio.push.apps.createPOST /v1/push/appsCredentials
honkio.push.apps.listGET /v1/push/appsCredentials
honkio.push.apps.getGET /v1/push/apps/{id}Credentials
honkio.push.apps.updatePATCH /v1/push/apps/{id}Credentials
honkio.push.apps.deleteDELETE /v1/push/apps/{id}Credentials

honkio.push.web

MethodRouteDocs
honkio.push.web.setVapidKeysPOST /v1/push/apps/{id}/web/vapid-keysWeb push (browsers)

honkio.push.devices

MethodRouteDocs
honkio.push.devices.registerPUT /v1/push/devicesDevices
honkio.push.devices.listGET /v1/push/devicesDevices
honkio.push.devices.deleteDELETE /v1/push/devices/{id}Devices

honkio.notify

MethodRouteDocs
honkio.notify.sendPOST /v1/notifySend a notification

honkio.notifications

MethodRouteDocs
honkio.notifications.getGET /v1/notifications/{id}Send a notification
honkio.notifications.listGET /v1/notificationsSend a notification

honkio.recipients

MethodRouteDocs
honkio.recipients.upsertPUT /v1/recipients/{user_id}Recipient profiles
honkio.recipients.updatePATCH /v1/recipients/{user_id}Recipient profiles
honkio.recipients.getGET /v1/recipients/{user_id}Recipient profiles
honkio.recipients.listGET /v1/recipientsRecipient profiles
honkio.recipients.deleteDELETE /v1/recipients/{user_id}Recipient profiles

honkio.webhooks

MethodRouteDocs
honkio.webhooks.createPOST /v1/webhooksOverview
honkio.webhooks.listGET /v1/webhooksOverview
honkio.webhooks.getGET /v1/webhooks/{id}Overview
honkio.webhooks.updatePATCH /v1/webhooks/{id}Overview
honkio.webhooks.removeDELETE /v1/webhooks/{id}Overview
honkio.webhooks.deliveriesGET /v1/webhooks/{id}/deliveriesOverview
honkio.webhooks.deadLettersGET /v1/webhooks/{id}/dead-lettersOverview
honkio.webhooks.replayPOST /v1/webhooks/dead-letters/{dlid}/replayOverview
honkio.webhooks.discardDELETE /v1/webhooks/dead-letters/{dlid}Overview
honkio.webhooks.reactivatePOST /v1/webhooks/{id}/reactivateOverview
honkio.webhooks.rotateSecretPOST /v1/webhooks/{id}/rotate-secretVerifying 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.

MethodRouteDocs
honkio.authApps.createPythonclient.auth_apps.createPHP$honkio->authApps->createPOST /v1/auth-appsCreate an app
honkio.authApps.listPythonclient.auth_apps.listPHP$honkio->authApps->listGET /v1/auth-appsList apps
honkio.authApps.getPythonclient.auth_apps.getPHP$honkio->authApps->getGET /v1/auth-apps/{id}Get an app
honkio.authApps.updatePythonclient.auth_apps.updatePHP$honkio->authApps->updatePATCH /v1/auth-apps/{id}Update an app
honkio.authApps.removePythonclient.auth_apps.removePHP$honkio->authApps->removeDELETE /v1/auth-apps/{id}Delete an app
honkio.authApps.rotateSecretPythonclient.auth_apps.rotate_secretPHP$honkio->authApps->rotateSecretPOST /v1/auth-apps/{id}/rotate-secretRotate the client secret
honkio.authApps.verifyDomainPythonclient.auth_apps.verify_domainPHP$honkio->authApps->verifyDomainPOST /v1/auth-apps/{id}/domain/verifyVerify the domain
honkio.authApps.cancelHostnameChangePythonclient.auth_apps.cancel_hostname_changePHP$honkio->authApps->cancelHostnameChangeDELETE /v1/auth-apps/{id}/hostname-changeCancel a hostname change

honkio.authApps.security

MethodRouteDocs
honkio.authApps.security.getPythonclient.auth_apps.security.getPHP$honkio->authApps->security->getGET /v1/auth-apps/{id}/securityGet security settings
honkio.authApps.security.updatePythonclient.auth_apps.security.updatePHP$honkio->authApps->security->updatePATCH /v1/auth-apps/{id}/securityUpdate security settings
honkio.authApps.security.summaryPythonclient.auth_apps.security.summaryPHP$honkio->authApps->security->summaryGET /v1/auth-apps/{id}/security/summaryGet the security summary

honkio.authApps.blocklist

MethodRouteDocs
honkio.authApps.blocklist.listPythonclient.auth_apps.blocklist.listPHP$honkio->authApps->security->blocklistGET /v1/auth-apps/{id}/security/blocklistList blocklist entries
honkio.authApps.blocklist.addPythonclient.auth_apps.blocklist.addPHP$honkio->authApps->security->addToBlocklistPOST /v1/auth-apps/{id}/security/blocklistAdd a blocklist entry
honkio.authApps.blocklist.removePythonclient.auth_apps.blocklist.removePHP$honkio->authApps->security->removeFromBlocklistDELETE /v1/auth-apps/{id}/security/blocklist/{entryId}Remove a blocklist entry

honkio.authApps.plan

MethodRouteDocs
honkio.authApps.plan.getPythonclient.auth_apps.plan.getPHP$honkio->authApps->plan->getGET /v1/auth-apps/{id}/planGet the plan
honkio.authApps.plan.changePythonclient.auth_apps.plan.changePHP$honkio->authApps->plan->changePUT /v1/auth-apps/{id}/planChange the plan
honkio.authApps.plan.cancelScheduledPythonclient.auth_apps.plan.cancel_scheduledPHP$honkio->authApps->plan->cancelScheduledChangeDELETE /v1/auth-apps/{id}/plan/scheduledCancel a scheduled downgrade
honkio.authApps.plan.costsPythonclient.auth_apps.plan.costsPHP$honkio->authApps->plan->costsGET /v1/auth-apps/{id}/costsGet sign-in costs
honkio.authApps.plan.usagePythonclient.auth_apps.plan.usagePHP$honkio->authApps->plan->usageGET /v1/auth-apps/{id}/usageGet usage

honkio.authApps.signInPolicy

MethodRouteDocs
honkio.authApps.signInPolicy.getPythonclient.auth_apps.sign_in_policy.getPHP$honkio->authApps->signInPolicyGET /v1/auth-apps/{id}/sign-in-policyGet the sign-in policy
honkio.authApps.signInPolicy.updatePythonclient.auth_apps.sign_in_policy.updatePHP$honkio->authApps->updateSignInPolicyPATCH /v1/auth-apps/{id}/sign-in-policyUpdate the sign-in policy

honkio.authApps.organizations

MethodRouteDocs
honkio.authApps.organizations.listPythonclient.auth_apps.organizations.listPHP$honkio->authApps->organizations->listGET /v1/auth-apps/{id}/organizationsList organizations
honkio.authApps.organizations.createPythonclient.auth_apps.organizations.createPHP$honkio->authApps->organizations->createPOST /v1/auth-apps/{id}/organizationsCreate an organization
honkio.authApps.organizations.getPythonclient.auth_apps.organizations.getPHP$honkio->authApps->organizations->getGET /v1/auth-apps/{id}/organizations/{orgId}Get an organization
honkio.authApps.organizations.updatePythonclient.auth_apps.organizations.updatePHP$honkio->authApps->organizations->updatePATCH /v1/auth-apps/{id}/organizations/{orgId}Update an organization
honkio.authApps.organizations.removePythonclient.auth_apps.organizations.removePHP$honkio->authApps->organizations->removeDELETE /v1/auth-apps/{id}/organizations/{orgId}Delete an organization

honkio.authApps.memberships

MethodRouteDocs
honkio.authApps.memberships.listPythonclient.auth_apps.memberships.listPHP$honkio->authApps->organizations->membershipsGET /v1/auth-apps/{id}/organizations/{orgId}/membershipsList members
honkio.authApps.memberships.addPythonclient.auth_apps.memberships.addPHP$honkio->authApps->organizations->addMembershipPOST /v1/auth-apps/{id}/organizations/{orgId}/membershipsAdd a member
honkio.authApps.memberships.getPythonclient.auth_apps.memberships.getPHP$honkio->authApps->organizations->membershipGET /v1/auth-apps/{id}/organizations/{orgId}/memberships/{userId}Get a membership
honkio.authApps.memberships.updatePythonclient.auth_apps.memberships.updatePHP$honkio->authApps->organizations->updateMembershipPATCH /v1/auth-apps/{id}/organizations/{orgId}/memberships/{userId}Change a member’s role
honkio.authApps.memberships.removePythonclient.auth_apps.memberships.removePHP$honkio->authApps->organizations->removeMembershipDELETE /v1/auth-apps/{id}/organizations/{orgId}/memberships/{userId}Remove a member

honkio.authApps.roles

MethodRouteDocs
honkio.authApps.roles.listPythonclient.auth_apps.roles.listPHP$honkio->authApps->organizations->rolesGET /v1/auth-apps/{id}/organizations/{orgId}/rolesList roles
honkio.authApps.roles.putPythonclient.auth_apps.roles.putPHP$honkio->authApps->organizations->putRolePUT /v1/auth-apps/{id}/organizations/{orgId}/roles/{name}Create or update a role
honkio.authApps.roles.removePythonclient.auth_apps.roles.removePHP$honkio->authApps->organizations->removeRoleDELETE /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

MethodRouteDocs
honkio.authApps.invitations.listPythonclient.auth_apps.invitations.listPHP$honkio->authApps->invitations->listGET /v1/auth-apps/{id}/invitationsList invitations
honkio.authApps.invitations.createPythonclient.auth_apps.invitations.createPHP$honkio->authApps->invitations->createPOST /v1/auth-apps/{id}/invitationsCreate an invitation
honkio.authApps.invitations.getPythonclient.auth_apps.invitations.getPHP$honkio->authApps->invitations->getGET /v1/auth-apps/{id}/invitations/{invitationId}Get an invitation
honkio.authApps.invitations.revokePythonclient.auth_apps.invitations.revokePHP$honkio->authApps->invitations->revokeDELETE /v1/auth-apps/{id}/invitations/{invitationId}Revoke an invitation

honkio.authApps.users

MethodRouteDocs
honkio.authApps.users.listPythonclient.auth_apps.users.listPHP$honkio->authApps->users->listGET /v1/auth-apps/{id}/usersList users
honkio.authApps.users.getPythonclient.auth_apps.users.getPHP$honkio->authApps->users->getGET /v1/auth-apps/{id}/users/{userId}Get a user
honkio.authApps.users.updatePythonclient.auth_apps.users.updatePHP$honkio->authApps->users->updatePATCH /v1/auth-apps/{id}/users/{userId}Update a user
honkio.authApps.users.blockPythonclient.auth_apps.users.blockPHP$honkio->authApps->users->blockPOST /v1/auth-apps/{id}/users/{userId}/blockBlock a user
honkio.authApps.users.unblockPythonclient.auth_apps.users.unblockPHP$honkio->authApps->users->unblockPOST /v1/auth-apps/{id}/users/{userId}/unblockUnblock a user
honkio.authApps.users.removePythonclient.auth_apps.users.removePHP$honkio->authApps->users->removeDELETE /v1/auth-apps/{id}/users/{userId}Delete a user
honkio.authApps.users.removeLikelyBotsPythonclient.auth_apps.users.remove_likely_botsPHP$honkio->authApps->users->removeLikelyBotsPOST /v1/auth-apps/{id}/users/bulk-deleteDelete likely bots
honkio.authApps.users.sessionsPythonclient.auth_apps.users.sessionsPHP$honkio->authApps->users->sessionsGET /v1/auth-apps/{id}/users/{userId}/sessionsList a user’s sessions
honkio.authApps.users.signInsPythonclient.auth_apps.users.sign_insPHP$honkio->authApps->users->signInsGET /v1/auth-apps/{id}/users/{userId}/sign-insList a user’s sign-ins
honkio.authApps.users.passkeysPythonclient.auth_apps.users.passkeysPHP$honkio->authApps->users->passkeysGET /v1/auth-apps/{id}/users/{userId}/passkeysList a user’s passkeys
honkio.authApps.users.removePasskeyPythonclient.auth_apps.users.remove_passkeyPHP$honkio->authApps->users->removePasskeyDELETE /v1/auth-apps/{id}/users/{userId}/passkeys/{passkeyId}Remove a user’s passkey

honkio.authApps.sessions

MethodRouteDocs
honkio.authApps.sessions.revokePythonclient.auth_apps.sessions.revokePHP$honkio->authApps->users->revokeSessionDELETE /v1/auth-apps/{id}/sessions/{sessionId}Revoke a session

honkio.authApps.signIns

MethodRouteDocs
honkio.authApps.signIns.listPythonclient.auth_apps.sign_ins.listPHP$honkio->authApps->signInsGET /v1/auth-apps/{id}/sign-insList sign-in history

honkio.authApps.exports

MethodRouteDocs
honkio.authApps.exports.createPythonclient.auth_apps.exports.createPHP$honkio->authApps->exports->createPOST /v1/auth-apps/{id}/exportsStart an export
honkio.authApps.exports.latestPythonclient.auth_apps.exports.latestPHP$honkio->authApps->exports->latestGET /v1/auth-apps/{id}/exports/latestGet the latest export
honkio.authApps.exports.getPythonclient.auth_apps.exports.getPHP$honkio->authApps->exports->getGET /v1/auth-apps/{id}/exports/{exportId}Get an export
honkio.authApps.exports.downloadPythonclient.auth_apps.exports.downloadPHP$honkio->authApps->exports->downloadGET /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.

javascript
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.

bash
pip install honkio

Create 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.

python
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_NUMBER

Send an email the same way:

python
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:

python
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).

python
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 again

From 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.

python
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.

python
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 "", 204

PHP

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.

bash
composer require honkio/honkio-php

Create a key as above. new Honkio() with no argument reads HONKIO_API_KEY and throws InvalidArgumentException if neither is set.

php
<?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
<?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
<?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
<?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
<?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.

bash
composer require honkio/laravel

Set your key, and the number notifications send from (HONKIO_BASE_URL is only for pointing at another API host):

dotenv
HONKIO_API_KEY=mk_test_...
HONKIO_SMS_FROM=+14165550100
# MAIL_MAILER=honkio

From 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.

dotenv
# The defaults, from version 0.2.0
HONKIO_MAX_RETRIES=2
HONKIO_MAX_RETRY_DELAY=60

Add 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
<?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
<?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
<?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
<?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.