{"openapi":"3.1.0","info":{"title":"HonkIO API","description":"Canadian-focused SMS gateway API.\n\n## Canadian Compliance\n- **CASL**: All commercial messages require consent. Use `/v1/compliance/consents` to record.\n- **DNCL**: CRTC Do Not Call List checking is coming soon (not yet enforced).\n- **PIPEDA / Quebec Law 25**: Data is stored in Canada (ca-central-1). Requests are processed in the United States in transit. Right-to-erasure available.\n\n## Authentication\nProvide your API key via `Authorization: Bearer mk_live_...` or `X-API-Key: mk_live_...`.\nTest keys never send, never charge and can't change live data; they work before your first top-up.\n\n## Rate limits\n100 requests per second per account. Every authenticated response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`.\n\n## Pagination\nShape follows the route family, not one convention across the API: documented here rather than changed. Messages listings return `{ data, meta: { page, limit, total, pages } }`. Contacts, Contact Groups, Lists and Verify listings return `{ data, total, limit, offset }`. Compliance consent and opt-out listings accept `page`/`limit` and return `{ data }`, with no total. Account transactions use a cursor: pass `before`, get back `has_more`.","version":"1.0.0","contact":{"name":"HonkIO Support","email":"support@honkio.ca"}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"API Key","description":"Use `mk_live_...` for production or `mk_test_...` for testing."},"apiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Alternative to the Authorization header: send `mk_live_...` or `mk_test_...` as X-API-Key directly."}},"schemas":{"Error":{"type":"object","description":"Standard error body. The one visible exception is the OAuth 2.1 surface (/oauth/token, /oauth/register, /oauth/revoke), which follows RFC 6749/7591/7009 and answers { error, error_description } instead.","required":["code","message","statusCode"],"properties":{"code":{"type":"string","description":"Machine-readable error code, e.g. VALIDATION_ERROR."},"message":{"type":"string","description":"Human-readable message in the request's locale (Accept-Language)."},"messageEn":{"type":"string","description":"English message, always present alongside message."},"messageFr":{"type":"string","description":"French message, always present alongside message."},"statusCode":{"type":"integer","description":"The HTTP status, repeated in the body."},"details":{"description":"Present on some errors: an AJV validation array, or error-specific structured detail."}}},"ApiKeyPermissions":{"type":"object","description":"Per-resource permissions. Each key is a resource name; value is a string of allowed operation letters: r=read, w=write/create, m=modify/update, d=delete. A missing key denies all access to that resource.","additionalProperties":{"type":"string"},"example":{"messages":"rw","contacts":"r","phone_numbers":"r"}},"Message":{"type":"object","additionalProperties":true,"required":["id","direction","from","to","body","body_purged","status","segment_count","mode","is_verification","auto_reply_keyword","cost_cents","error_code","error_message","sent_at","created_at","casl_consent_type","dncl_exemption","delivered_at"],"properties":{"id":{"type":"string"},"direction":{"type":"string","enum":["OUTBOUND","INBOUND"]},"from":{"type":"string"},"to":{"type":"string"},"body":{"type":["string","null"],"description":"Null once the body is purged (see body_purged) and on a verification OTP, whose code is never stored."},"body_purged":{"type":"boolean"},"status":{"type":"string","enum":["QUEUED","SENDING","SENT","DELIVERED","FAILED","UNDELIVERED","RECEIVED"]},"segment_count":{"type":["integer","null"],"description":"Number of SMS parts the carrier split the body into (inbound: as received). Billing is per part."},"mode":{"type":"string","enum":["LIVE","TEST"],"description":"TEST messages are simulated: never sent to a carrier and never billed."},"is_verification":{"type":"boolean","description":"The OTP SMS behind a verification. Its body is never stored."},"auto_reply_keyword":{"type":["string","null"],"enum":["STOP","START","HELP",null],"description":"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."},"cost_cents":{"type":"integer","description":"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_code":{"type":["string","null"],"description":"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). CARRIER_THROTTLED when the carrier asked us to slow down, or dropped the message from the sending number's full queue before trying the recipient: refunded, and safe to send again."},"error_message":{"type":["string","null"],"description":"Human-readable reason for a FAILED or UNDELIVERED message, from the carrier when it gave one."},"sent_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"},"casl_consent_type":{"type":["string","null"],"enum":["EXPRESS","IMPLIED",null],"description":"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)."},"dncl_exemption":{"type":["string","null"],"description":"The CRTC Do Not Call List exemption the send relied on, when one applied."},"delivered_at":{"type":["string","null"],"format":"date-time","description":"When the carrier reported the message delivered to the handset."}},"description":"An SMS message, outbound or inbound. The same shape from the send, the lookup and the list."},"SentMessage":{"type":"object","additionalProperties":true,"required":["id","direction","from","to","body","body_purged","status","segment_count","mode","is_verification","auto_reply_keyword","cost_cents","error_code","error_message","sent_at","created_at","casl_consent_type","dncl_exemption","delivered_at","estimated_send_at"],"properties":{"id":{"type":"string"},"direction":{"type":"string","enum":["OUTBOUND","INBOUND"]},"from":{"type":"string"},"to":{"type":"string"},"body":{"type":["string","null"],"description":"Null once the body is purged (see body_purged) and on a verification OTP, whose code is never stored."},"body_purged":{"type":"boolean"},"status":{"type":"string","enum":["QUEUED","SENDING","SENT","DELIVERED","FAILED","UNDELIVERED","RECEIVED"]},"segment_count":{"type":["integer","null"],"description":"Number of SMS parts the carrier split the body into (inbound: as received). Billing is per part."},"mode":{"type":"string","enum":["LIVE","TEST"],"description":"TEST messages are simulated: never sent to a carrier and never billed."},"is_verification":{"type":"boolean","description":"The OTP SMS behind a verification. Its body is never stored."},"auto_reply_keyword":{"type":["string","null"],"enum":["STOP","START","HELP",null],"description":"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."},"cost_cents":{"type":"integer","description":"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_code":{"type":["string","null"],"description":"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). CARRIER_THROTTLED when the carrier asked us to slow down, or dropped the message from the sending number's full queue before trying the recipient: refunded, and safe to send again."},"error_message":{"type":["string","null"],"description":"Human-readable reason for a FAILED or UNDELIVERED message, from the carrier when it gave one."},"sent_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"},"casl_consent_type":{"type":["string","null"],"enum":["EXPRESS","IMPLIED",null],"description":"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)."},"dncl_exemption":{"type":["string","null"],"description":"The CRTC Do Not Call List exemption the send relied on, when one applied."},"delivered_at":{"type":["string","null"],"format":"date-time","description":"When the carrier reported the message delivered to the handset."},"estimated_send_at":{"type":["string","null"],"format":"date-time","description":"An estimate of when the carrier will start sending this message, from the parts already waiting on the sending number (a local number sends 10 parts a minute). Close to now when nothing is waiting. Null in test mode and on a retry that returned an earlier send."}},"description":"The message a send created, with an estimate of when the carrier will send it."},"Verification":{"type":"object","additionalProperties":true,"required":["id","phone_number","from_number","status","attempts","code_length","app_name","mode","cost_cents","expires_at","verified_at","created_at"],"properties":{"id":{"type":"string"},"phone_number":{"type":"string"},"from_number":{"type":"string"},"status":{"type":"string","enum":["pending","verified","expired","max_attempts"]},"attempts":{"type":"integer"},"code_length":{"type":"integer"},"app_name":{"type":["string","null"]},"mode":{"type":"string","enum":["LIVE","TEST"],"description":"TEST verifications are simulated: no SMS is sent, nothing is billed, and the code is always 000000 padded to code_length."},"cost_cents":{"type":"integer","description":"Cost in cents CAD. On a TEST verification this is 0: nothing was charged."},"expires_at":{"type":"string","format":"date-time"},"verified_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}},"description":"A phone-number verification (SMS OTP). status is lowercase, unlike message statuses."},"PhoneNumber":{"type":"object","additionalProperties":true,"required":["id","phone_number","area_code","region","capabilities","status","monthly_cost_cents","provisioned_at","released_at","number_type","toll_free_verification"],"properties":{"id":{"type":"string"},"phone_number":{"type":"string","description":"E.164"},"area_code":{"type":"string"},"region":{"type":["string","null"],"description":"Province or territory, when the area code maps to one."},"capabilities":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["ACTIVE","SUSPENDED","RELEASING","RELEASED"]},"monthly_cost_cents":{"type":"integer","description":"Recurring rent in CAD cents."},"provisioned_at":{"type":"string","format":"date-time"},"released_at":{"type":["string","null"],"format":"date-time"},"number_type":{"type":"string","enum":["local","toll_free"],"description":"Toll-free numbers (833, 844, 855, 866, 877, 888) need an approved toll-free verification before they can send SMS."},"toll_free_verification":{"type":["object","null"],"additionalProperties":true,"required":["id","status"],"properties":{"id":{"type":"string","description":"tfv_…"},"status":{"type":"string","enum":["DRAFT","AWAITING_PAYMENT","IN_REVIEW","CHANGES_REQUESTED","SUBMITTED","APPROVED","REJECTED","DECLINED"],"description":"The number sends SMS once this is APPROVED."}},"description":"The newest toll-free verification application for this number, cancelled ones aside. Null for a local number or a toll-free number never applied for."}},"description":"A phone number provisioned on the account."},"PhoneNumberSearchResult":{"type":"object","additionalProperties":true,"required":["phone_number","country_code","area_code","region","capabilities","monthly_cost_cents","upfront_cost_cents","activation_fee_cents","currency"],"properties":{"phone_number":{"type":"string","description":"E.164"},"country_code":{"type":"string"},"area_code":{"type":["string","null"]},"region":{"type":["string","null"],"description":"Province or territory, when the area code maps to one."},"capabilities":{"type":"array","items":{"type":"string"}},"monthly_cost_cents":{"type":"integer","description":"Recurring rent in CAD cents, at the tier (local or toll-free) this number would be sold at."},"upfront_cost_cents":{"type":"integer","description":"The first month's rent, charged when the number is provisioned."},"activation_fee_cents":{"type":"integer","description":"One-time activation fee charged with the first month. Not refunded on release."},"currency":{"type":"string","enum":["CAD"]}},"description":"A number available to provision, priced in CAD cents."},"ApiKey":{"type":"object","additionalProperties":true,"required":["id","keyPrefix","mode","label","createdAt","lastUsedAt"],"properties":{"id":{"type":"string"},"keyPrefix":{"type":"string","description":"Non-secret prefix for display and identification."},"mode":{"type":"string","enum":["LIVE","TEST"]},"label":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"lastUsedAt":{"type":["string","null"],"format":"date-time"},"defaultDeny":{"type":"boolean","description":"Present on GET /v1/accounts/me and /v1/accounts/{id}; absent from the login response."},"permissions":{"$ref":"#/components/schemas/ApiKeyPermissions"},"email_domain_ids":{"type":["array","null"],"items":{"type":"string"},"description":"The email domains this key may send from (ids from /v1/email-domains): null when unrestricted, [] when restricted to none (its domains were deleted). Present on GET /v1/accounts/me and /v1/accounts/{id}; absent from the login response."},"oauth_grant":{"type":"boolean","description":"True when an OAuth connection holds this key: it can be revoked but not rotated (409 CONFLICT, details.reason \"oauth_grant\"). Present on GET /v1/accounts/me and /v1/accounts/{id}; absent from the login response."}},"description":"An active (unrevoked) API key, never the key itself. Field names are camelCase here, unlike the rest of the API."},"Account":{"type":"object","additionalProperties":true,"required":["id","name","email","stripe_customer_id","email_verified_at","status","balance_cents","verified_phone","phone_verified_at","email_undeliverable","email_undeliverable_at","email_undeliverable_reason","terms_accepted_at","terms_version","created_at"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"},"stripe_customer_id":{"type":["string","null"]},"email_verified_at":{"type":["string","null"],"format":"date-time"},"status":{"type":"string","enum":["PENDING_PAYMENT","ACTIVE","SUSPENDED","CLOSED"]},"balance_cents":{"type":"integer","description":"Credit balance in cents CAD."},"verified_phone":{"type":["string","null"]},"phone_verified_at":{"type":["string","null"],"format":"date-time"},"email_undeliverable":{"type":"boolean","description":"True when platform email to this address is bouncing or being marked as spam."},"email_undeliverable_at":{"type":["string","null"],"format":"date-time","description":"The bounce or complaint that made the address unreachable."},"email_undeliverable_reason":{"type":["string","null"],"description":"BOUNCE or COMPLAINT."},"terms_accepted_at":{"type":["string","null"],"format":"date-time","description":"When the owner accepted the Terms of Service and Privacy Policy."},"terms_version":{"type":["string","null"],"description":"The Terms version (last-updated date) that was accepted."},"created_at":{"type":"string","format":"date-time"}},"description":"An account, as signup and PATCH /v1/accounts/{id} return it."},"AccountDocument":{"type":"object","additionalProperties":true,"required":["account","api_keys","features"],"properties":{"account":{"allOf":[{"$ref":"#/components/schemas/Account"},{"type":"object","additionalProperties":true,"required":["monthly_rent_cents","low_balance","phone_number_limit","phone_numbers_used","inbound_sms_daily_cap","inbound_sms_received_24h"],"properties":{"monthly_rent_cents":{"type":"integer","description":"Monthly rent owed on every number not yet released (ACTIVE and SUSPENDED)."},"low_balance":{"type":"boolean","description":"True when the credit balance would not cover next month's phone-number rent."},"phone_number_limit":{"type":"integer","description":"Maximum phone numbers this account may hold."},"phone_numbers_used":{"type":"integer","description":"Numbers currently held (ACTIVE and SUSPENDED)."},"email_domain_limit":{"type":"integer","description":"Maximum live sending domains this account may hold. Present only while email is available."},"email_domains_used":{"type":"integer","description":"Live sending domains held (every status but FAILED). Present only while email is available."},"inbound_email_daily_cap":{"type":"integer","description":"Received emails this account may accept per rolling 24 h before further mail is stored rejected and uncharged (0 = unlimited); the account's own override, or the platform default. Present only while email is available."},"inbound_email_received_24h":{"type":"integer","description":"Received emails counted against the cap above, in the current rolling 24 h window. Present only while email is available."},"inbound_email_daily_mb_cap":{"type":"integer","description":"Billable MB of received email (each message's started MB above the first, the part priced per MB) this account may accept per rolling 24 h; a message that would go past it is stored rejected (daily_mb_cap) and uncharged (0 = unlimited). The account's own override, or the platform default. Present only while email is available."},"inbound_email_mb_received_24h":{"type":"integer","description":"Billable MB counted against the MB cap above, in the current rolling 24 h window. Present only while email is available."},"inbound_sms_daily_cap":{"type":"integer","description":"Received texts this account may accept per rolling 24 h before further texts are stored uncharged and not delivered as message.received (0 = unlimited); the account's own override, or the platform default."},"inbound_sms_received_24h":{"type":"integer","description":"Received texts counted against the cap above, in the current rolling 24 h window."}}}]},"api_keys":{"type":"array","description":"The account's active USER keys (dashboard session keys are not listed).","items":{"allOf":[{"$ref":"#/components/schemas/ApiKey"},{"required":["defaultDeny","permissions","email_domain_ids","oauth_grant"]}]}},"features":{"type":"object","additionalProperties":true,"required":["email"],"description":"Platform capabilities currently available to this account.","properties":{"email":{"type":"boolean","description":"True when the email product is live; false while it is still gated."}}}},"description":"The account with its rent position, number allowance and active API keys, from GET /v1/accounts/{id}. GET /v1/accounts/me returns it with caller added."},"AccountMeDocument":{"type":"object","additionalProperties":true,"required":["account","api_keys","features","caller"],"properties":{"account":{"allOf":[{"$ref":"#/components/schemas/Account"},{"type":"object","additionalProperties":true,"required":["monthly_rent_cents","low_balance","phone_number_limit","phone_numbers_used","inbound_sms_daily_cap","inbound_sms_received_24h"],"properties":{"monthly_rent_cents":{"type":"integer","description":"Monthly rent owed on every number not yet released (ACTIVE and SUSPENDED)."},"low_balance":{"type":"boolean","description":"True when the credit balance would not cover next month's phone-number rent."},"phone_number_limit":{"type":"integer","description":"Maximum phone numbers this account may hold."},"phone_numbers_used":{"type":"integer","description":"Numbers currently held (ACTIVE and SUSPENDED)."},"email_domain_limit":{"type":"integer","description":"Maximum live sending domains this account may hold. Present only while email is available."},"email_domains_used":{"type":"integer","description":"Live sending domains held (every status but FAILED). Present only while email is available."},"inbound_email_daily_cap":{"type":"integer","description":"Received emails this account may accept per rolling 24 h before further mail is stored rejected and uncharged (0 = unlimited); the account's own override, or the platform default. Present only while email is available."},"inbound_email_received_24h":{"type":"integer","description":"Received emails counted against the cap above, in the current rolling 24 h window. Present only while email is available."},"inbound_email_daily_mb_cap":{"type":"integer","description":"Billable MB of received email (each message's started MB above the first, the part priced per MB) this account may accept per rolling 24 h; a message that would go past it is stored rejected (daily_mb_cap) and uncharged (0 = unlimited). The account's own override, or the platform default. Present only while email is available."},"inbound_email_mb_received_24h":{"type":"integer","description":"Billable MB counted against the MB cap above, in the current rolling 24 h window. Present only while email is available."},"inbound_sms_daily_cap":{"type":"integer","description":"Received texts this account may accept per rolling 24 h before further texts are stored uncharged and not delivered as message.received (0 = unlimited); the account's own override, or the platform default."},"inbound_sms_received_24h":{"type":"integer","description":"Received texts counted against the cap above, in the current rolling 24 h window."}}}]},"api_keys":{"type":"array","description":"The account's active USER keys (dashboard session keys are not listed).","items":{"allOf":[{"$ref":"#/components/schemas/ApiKey"},{"required":["defaultDeny","permissions","email_domain_ids","oauth_grant"]}]}},"features":{"type":"object","additionalProperties":true,"required":["email"],"description":"Platform capabilities currently available to this account.","properties":{"email":{"type":"boolean","description":"True when the email product is live; false while it is still gated."}}},"caller":{"$ref":"#/components/schemas/AccountCaller"}},"description":"The account document from GET /v1/accounts/{id}, plus who is calling."},"AccountCaller":{"type":"object","additionalProperties":true,"required":["mode","oauth_client"],"properties":{"mode":{"type":"string","enum":["LIVE","TEST"],"description":"The mode of the key that made this request. A TEST key is a sandbox: nothing it sends reaches a carrier or is charged."},"oauth_client":{"type":["object","null"],"additionalProperties":true,"required":["id","name"],"description":"The OAuth client this request's access token was granted to; null when the request used an API key.","properties":{"id":{"type":"string","description":"The client id: its metadata document URL, or a dcr_ id for a client that registered itself."},"name":{"type":"string","description":"The client name shown on the consent page when the connection was approved."}}}},"description":"Who is calling: the key's mode and, for an OAuth connection, its client."},"Contact":{"type":"object","additionalProperties":true,"required":["id","phone_number","email","name","notes","created_at","updated_at"],"properties":{"id":{"type":"string"},"phone_number":{"type":["string","null"],"description":"E.164; null for an email-only contact. Only contacts with a phone can join groups and lists."},"email":{"type":["string","null"],"description":"Lower-cased; null for a phone-only contact. At least one of phone_number and email is set."},"name":{"type":["string","null"]},"notes":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"description":"A contact: a phone number, an email, or both."},"ContactGroup":{"type":"object","additionalProperties":true,"required":["id","name","description","member_count","members","created_at","updated_at"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"member_count":{"type":"integer"},"members":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["id","phone_number","name"],"properties":{"id":{"type":"string","description":"The contact id."},"phone_number":{"type":["string","null"]},"name":{"type":["string","null"]}}}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"description":"A contact group with every member embedded."},"ContactList":{"type":"object","additionalProperties":true,"required":["id","name","description","entry_count","entries","created_at","updated_at"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"entry_count":{"type":"integer"},"entries":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["id","phone_number","contact_id","contact_group_id","contact_name","contact_phone","group_name","added_at"],"properties":{"id":{"type":"string"},"phone_number":{"type":["string","null"],"description":"Set on a direct number entry."},"contact_id":{"type":["string","null"],"description":"Set on a contact entry."},"contact_group_id":{"type":["string","null"],"description":"Set on a group entry, which expands to the group's members at send time."},"contact_name":{"type":["string","null"]},"contact_phone":{"type":["string","null"]},"group_name":{"type":["string","null"]},"added_at":{"type":"string","format":"date-time"}}}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"description":"An allow/deny contact list with every entry embedded. Assign it to an API key to restrict who that key may message."},"Consent":{"type":"object","additionalProperties":true,"required":["id","accountId","channel","phoneNumber","emailAddress","consentType","status","sourceDescription","sourceIp","sourceUrl","relationshipType","expiresAt","grantedAt","revokedAt","revokedReason","doubleOptInConfirmedAt","doubleOptInConfirmationId","consentBatchId"],"properties":{"id":{"type":"string"},"accountId":{"type":"string"},"channel":{"type":"string","enum":["SMS","EMAIL"]},"phoneNumber":{"type":["string","null"]},"emailAddress":{"type":["string","null"]},"consentType":{"type":"string","enum":["EXPRESS","IMPLIED"]},"status":{"type":"string","enum":["ACTIVE","EXPIRED","REVOKED"]},"sourceDescription":{"type":["string","null"]},"sourceIp":{"type":["string","null"]},"sourceUrl":{"type":["string","null"]},"relationshipType":{"type":["string","null"]},"expiresAt":{"type":["string","null"],"format":"date-time"},"grantedAt":{"type":"string","format":"date-time"},"revokedAt":{"type":["string","null"],"format":"date-time"},"revokedReason":{"type":["string","null"]},"doubleOptInConfirmedAt":{"type":["string","null"],"format":"date-time","description":"When the subscriber last confirmed double opt-in by replying YES to an opt-in confirmation from one of your toll-free numbers. A toll-free send needs a confirmation from its own sending number: see GET /v1/compliance/opt-in-confirmations."},"doubleOptInConfirmationId":{"type":["string","null"],"description":"The opt-in confirmation (oic_…) they answered."},"consentBatchId":{"type":["string","null"],"description":"The bulk upload (cb_…) that recorded this consent; null for one recorded on its own."}},"description":"A consent record as stored: field names are camelCase, unlike the rest of the API."},"ConsentBatch":{"type":"object","additionalProperties":true,"required":["id","source","file_name","attested","counts","revoked_at","created_at"],"properties":{"id":{"type":"string","description":"cb_…"},"source":{"type":"string","enum":["api","dashboard"],"description":"Sent through the API, or uploaded on the dashboard."},"file_name":{"type":["string","null"],"description":"The file name the uploader gave, if any."},"attested":{"type":"boolean","description":"Whether the uploader confirmed these people gave this consent or have this relationship."},"counts":{"type":"object","additionalProperties":true,"required":["rows","recorded","express","implied","already_recorded","opted_out","revoked_by_review","duplicate_in_file","invalid"],"properties":{"rows":{"type":"integer"},"recorded":{"type":"integer"},"express":{"type":"integer"},"implied":{"type":"integer"},"already_recorded":{"type":"integer"},"opted_out":{"type":"integer"},"revoked_by_review":{"type":"integer"},"duplicate_in_file":{"type":"integer"},"invalid":{"type":"integer"}}},"revoked_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"},"defaults":{"type":"object","additionalProperties":true,"required":["consent_type"],"properties":{"consent_type":{"type":"string","enum":["express","implied"]},"source_description":{"type":"string"},"source_ip":{"type":"string"},"source_url":{"type":"string"},"relationship_type":{"type":"string"},"last_transaction_date":{"type":"string"},"expires_at":{"type":["string","null"]}},"description":"The defaults the upload was sent with, as sent. Only on GET /v1/compliance/consent-batches/{id}."}}},"OptOut":{"type":"object","additionalProperties":true,"required":["id","accountId","channel","phoneNumber","fromNumber","fromAddress","emailAddress","keywordUsed","optedOutAt","reinstatedAt","reinstateKeyword"],"properties":{"id":{"type":"string"},"accountId":{"type":"string"},"channel":{"type":"string","enum":["SMS","EMAIL"]},"phoneNumber":{"type":["string","null"]},"fromNumber":{"type":["string","null"],"description":"The sending number the subscriber opted out from (SMS)."},"fromAddress":{"type":["string","null"]},"emailAddress":{"type":["string","null"]},"keywordUsed":{"type":["string","null"],"description":"STOP, UNSUBSCRIBE and so on; null for an opt-out recorded through the API."},"optedOutAt":{"type":"string","format":"date-time"},"reinstatedAt":{"type":["string","null"],"format":"date-time"},"reinstateKeyword":{"type":["string","null"]}},"description":"An opt-out record as stored: field names are camelCase, unlike the rest of the API."},"OptInConfirmation":{"type":"object","additionalProperties":true,"required":["id","status","from","to","brand_name","language","message_id","expires_at","confirmed_at","reply_message_id","created_at"],"properties":{"id":{"type":"string","description":"oic_…"},"status":{"type":"string","enum":["PENDING","CONFIRMED","EXPIRED","DECLINED"],"description":"PENDING until the recipient replies YES (CONFIRMED), it lapses after 7 days or is replaced by a newer one (EXPIRED), or the recipient texts STOP (DECLINED)."},"from":{"type":["string","null"],"description":"The toll-free number it was sent from."},"to":{"type":"string","description":"The recipient."},"brand_name":{"type":"string"},"language":{"type":"string","enum":["en","fr"]},"message_id":{"type":["string","null"],"description":"The confirmation text, as a message (GET /v1/messages/{id})."},"expires_at":{"type":"string","format":"date-time"},"confirmed_at":{"type":["string","null"],"format":"date-time"},"reply_message_id":{"type":["string","null"],"description":"The recipient's YES, as an inbound message."},"created_at":{"type":"string","format":"date-time"}},"description":"A double opt-in confirmation sent from a toll-free number."},"Webhook":{"type":"object","additionalProperties":true,"required":["id","url","events","active","deactivated_reason","deactivated_at","failing_since","failed_events","last_success_at","last_failure_at","last_failure_reason","secret_rotated_at","created_at"],"properties":{"id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string","description":"An event name, e.g. message.delivered or phone_number.suspended."}},"active":{"type":"boolean"},"deactivated_reason":{"type":["string","null"],"description":"Why delivery disabled the endpoint, when it did: e.g. \"HTTP_401 for 26h across 7 events\" after 24 hours of failure across at least 5 events, or SSRF_BLOCKED."},"deactivated_at":{"type":["string","null"],"format":"date-time"},"failing_since":{"type":["string","null"],"format":"date-time","description":"The first failed attempt since the last successful delivery; null while the endpoint is healthy. Any delivered attempt clears it."},"failed_events":{"type":"integer","description":"Distinct events that have failed since failing_since. The endpoint is disabled once every attempt has failed for 24 hours and this reaches 5."},"last_success_at":{"type":["string","null"],"format":"date-time","description":"The last delivered attempt, to within a minute."},"last_failure_at":{"type":["string","null"],"format":"date-time"},"last_failure_reason":{"type":["string","null"],"description":"The last failed attempt's result: HTTP_<status>, HTTP_<status>_REDIRECT_NOT_FOLLOWED, TIMEOUT or NETWORK_ERROR."},"secret_rotated_at":{"type":["string","null"],"format":"date-time","description":"When the signing secret was last rotated (POST /v1/webhooks/{id}/rotate-secret); null while the endpoint still signs with the secret it was created with."},"created_at":{"type":"string","format":"date-time"}},"description":"A registered webhook endpoint. The signing secret is shown once, at creation or rotation."},"WebhookDelivery":{"type":"object","additionalProperties":true,"required":["id","event_id","event_type","attempt","success","http_status","error_reason","duration_ms","occurred_at"],"properties":{"id":{"type":"string"},"event_id":{"type":"string"},"event_type":{"type":"string"},"attempt":{"type":"integer"},"success":{"type":"boolean"},"http_status":{"type":["integer","null"]},"error_reason":{"type":["string","null"]},"duration_ms":{"type":["integer","null"]},"occurred_at":{"type":"string","format":"date-time"}},"description":"One delivery attempt of one event."},"WebhookDeadLetter":{"type":"object","additionalProperties":true,"required":["id","event_id","event_type","failed_reason","created_at","replayed_at","payload"],"properties":{"id":{"type":"string"},"event_id":{"type":"string"},"event_type":{"type":"string"},"failed_reason":{"type":"string","description":"Why the event stopped being retried: the last attempt's failure with the attempt count after about 24 hours of retries (e.g. \"HTTP_500 after 8 attempts\"), the same with \"; endpoint disabled\" when the endpoint was disabled while it was owed, ENDPOINT_DISABLED when you had switched the endpoint off, SSRF_BLOCKED, or queue_full."},"created_at":{"type":"string","format":"date-time"},"replayed_at":{"type":["string","null"],"format":"date-time"},"payload":{"description":"The event body that failed to deliver: { id, type, created, account_id, data }."}},"description":"An event that failed every delivery attempt."},"NumberAllowanceRequest":{"type":"object","additionalProperties":true,"required":["id","requested_limit","limit_at_request","held_at_request","reason","status","granted_limit","staff_note","decided_at","created_at"],"properties":{"id":{"type":"string"},"requested_limit":{"type":"integer","description":"Total numbers asked for, not an increment."},"limit_at_request":{"type":"integer"},"held_at_request":{"type":"integer"},"reason":{"type":"string"},"status":{"type":"string","enum":["PENDING","APPROVED","DENIED"]},"granted_limit":{"type":["integer","null"]},"staff_note":{"type":["string","null"]},"decided_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}},"description":"A request for a higher phone number allowance."},"SendLimit":{"type":"object","additionalProperties":true,"required":["daily_limit","sent_last_24h","remaining","window_hours","approved_daily_limit","probation_days","recipient_rate_per_hour","recipient_rate_per_day","probation","paused_until","pause_reason","toll_free","number_queues","requests"],"properties":{"daily_limit":{"type":"integer","description":"Live sends from local numbers allowed in a rolling 24 hours."},"sent_last_24h":{"type":"integer"},"remaining":{"type":"integer"},"window_hours":{"type":"integer"},"approved_daily_limit":{"type":"integer","description":"The standard volume a request is granted by default."},"probation_days":{"type":"integer"},"recipient_rate_per_hour":{"type":"integer","description":"Messages one recipient may receive from the account in an hour."},"recipient_rate_per_day":{"type":"integer","description":"Messages one recipient may receive from the account in a day."},"probation":{"type":"object","additionalProperties":true,"required":["started_at","ends_at","eligible_to_request"],"properties":{"started_at":{"type":["string","null"],"format":"date-time","description":"The first live send; null before it."},"ends_at":{"type":["string","null"],"format":"date-time"},"eligible_to_request":{"type":"boolean"}}},"paused_until":{"type":["string","null"],"format":"date-time","description":"Set while sending is paused."},"pause_reason":{"type":["string","null"],"description":"Why sending is paused: OPT_OUT_RATE, FAILURE_RATE or MANUAL."},"toll_free":{"type":"object","additionalProperties":true,"required":["daily_limit","sent_last_24h","remaining","fanout_max_recipients","broadcast_max_recipients","broadcasts_per_day"],"properties":{"daily_limit":{"type":"integer","description":"Live sends from toll-free numbers allowed in a rolling 24 hours. There is no probation."},"sent_last_24h":{"type":"integer","description":"Live sends from toll-free numbers in the last 24 hours."},"remaining":{"type":"integer"},"fanout_max_recipients":{"type":"integer","description":"Distinct recipients one identical body may reach from toll-free numbers in 24 hours."},"broadcast_max_recipients":{"type":"integer","description":"Members a contact-group broadcast from a toll-free number may reach."},"broadcasts_per_day":{"type":"integer","description":"Contact-group broadcasts from toll-free numbers per rolling 24 hours."}},"description":"The separate allowance for sends from toll-free numbers. Sends from local numbers count toward the top-level figures only."},"number_queues":{"type":"array","description":"Each of your active numbers' carrier queue: the carrier sends a number's message parts at a fixed pace, so a list takes time. An estimate, from the parts already handed over.","items":{"type":"object","additionalProperties":true,"required":["phone_number","parts_per_minute","queued_parts","clears_at"],"properties":{"phone_number":{"type":"string"},"parts_per_minute":{"type":"integer","description":"Message parts the carrier sends from this number a minute (10 for a local number)."},"queued_parts":{"type":"integer","description":"Parts waiting now. A single send is refused with NUMBER_RATE_LIMITED while a minute's worth are waiting."},"clears_at":{"type":["string","null"],"format":"date-time","description":"When the parts waiting now should all have gone out; null when none are waiting."}}}},"requests":{"type":"array","items":{"$ref":"#/components/schemas/SendLimitRequest"}}},"description":"The account's sending limits, current usage and past volume requests."},"SendLimitRequest":{"type":"object","additionalProperties":true,"required":["id","requested_limit","limit_at_request","sent_last_30d_at_request","reason","status","granted_limit","staff_note","decided_at","created_at"],"properties":{"id":{"type":"string"},"requested_limit":{"type":["integer","null"],"description":"Messages per day asked for; null asks for the standard approved volume."},"limit_at_request":{"type":"integer"},"sent_last_30d_at_request":{"type":"integer"},"reason":{"type":"string"},"status":{"type":"string","enum":["PENDING","APPROVED","DENIED"]},"granted_limit":{"type":["integer","null"]},"staff_note":{"type":["string","null"]},"decided_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}},"description":"A request for a higher daily sending volume."},"TollFreeVerification":{"type":"object","additionalProperties":true,"required":["id","status","phone_numbers","application","fee_cents","payment_url","paid_at","refunded_at","reviewer_note","carrier_status","carrier_rejection_reason","submitted_at","decided_at","created_at","updated_at"],"properties":{"id":{"type":"string"},"status":{"type":"string","enum":["DRAFT","AWAITING_PAYMENT","IN_REVIEW","CHANGES_REQUESTED","SUBMITTED","APPROVED","REJECTED","DECLINED","CANCELLED"],"description":"DRAFT, then AWAITING_PAYMENT, IN_REVIEW (HonkIO staff), SUBMITTED (with the carrier) and APPROVED. CHANGES_REQUESTED and REJECTED (by the carrier) can be edited and submitted again, and a REJECTED one can be withdrawn (CANCELLED); DECLINED (by HonkIO) and CANCELLED are final."},"phone_numbers":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["id","phone_number"],"properties":{"id":{"type":"string"},"phone_number":{"type":"string"}}}},"application":{"type":"object","additionalProperties":true,"required":["business_name","doing_business_as","entity_type","business_registration_number","business_address","contact","website","use_case","use_case_summary","sample_messages","opt_in_workflow","opt_in_image_urls","opt_in_confirmation_message","help_message","privacy_policy_url","terms_url","monthly_volume","age_gated","additional_information"],"properties":{"business_name":{"type":"string"},"doing_business_as":{"type":["string","null"]},"entity_type":{"type":"string","enum":["SOLE_PROPRIETOR","PRIVATE_PROFIT","PUBLIC_PROFIT","NON_PROFIT","GOVERNMENT"]},"business_registration_number":{"type":"string"},"business_address":{"type":"object","additionalProperties":true,"required":["line1","city","province","postal_code"],"properties":{"line1":{"type":"string"},"city":{"type":"string"},"province":{"type":"string"},"postal_code":{"type":"string"}}},"contact":{"type":"object","additionalProperties":true,"required":["first_name","last_name","email","phone"],"properties":{"first_name":{"type":"string"},"last_name":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"}}},"website":{"type":"string"},"use_case":{"type":"string","enum":["2FA","App Notifications","Appointments","Auctions","Auto Repair Services","Bank Transfers","Billing","Booking Confirmations","Business Updates","COVID-19 Alerts","Career Training","Chatbot","Conversational / Alerts","Courier Services & Deliveries","Emergency Alerts","Events & Planning","Financial Services","Fraud Alerts","Fundraising","General Marketing","General School Updates","HR / Staffing","Healthcare Alerts","Housing Community Updates","Insurance Services","Job Dispatch","Legal Services","Mixed","Motivational Reminders","Notary Notifications","Order Notifications","Political","Public Works","Real Estate Services","Religious Services","Repair and Diagnostics Alerts","Rewards Program","Surveys","System Alerts","Voting Reminders","Waitlist Alerts","Webinar Reminders","Workshop Alerts"]},"use_case_summary":{"type":"string"},"sample_messages":{"type":"array","items":{"type":"string"}},"opt_in_workflow":{"type":"string"},"opt_in_image_urls":{"type":"array","items":{"type":"string"}},"opt_in_confirmation_message":{"type":"string"},"help_message":{"type":"string"},"privacy_policy_url":{"type":"string"},"terms_url":{"type":["string","null"]},"monthly_volume":{"type":"string","enum":["10","100","1,000","10,000","100,000","250,000","500,000","750,000","1,000,000","5,000,000","10,000,000+"]},"age_gated":{"type":"boolean"},"additional_information":{"type":["string","null"]}},"description":"The application as stored: every field, with the business number, postal code and phone normalized."},"fee_cents":{"type":"integer","description":"The verification fee for this application, in cents CAD."},"payment_url":{"type":["string","null"],"description":"While AWAITING_PAYMENT: the dashboard page where the fee is paid. Null otherwise."},"paid_at":{"type":["string","null"],"format":"date-time"},"refunded_at":{"type":["string","null"],"format":"date-time"},"reviewer_note":{"type":["string","null"],"description":"What HonkIO staff asked to change, or why they declined the application."},"carrier_status":{"type":["string","null"],"enum":["pending","action_needed","approved","rejected",null],"description":"Where the carrier's review stands once it has the application: pending, action_needed (the carrier asked HonkIO a question; nothing for you to do yet), approved or rejected. Null before it is submitted."},"carrier_rejection_reason":{"type":["string","null"],"description":"Why the carrier rejected the application, when it did."},"submitted_at":{"type":["string","null"],"format":"date-time"},"decided_at":{"type":["string","null"],"format":"date-time","description":"When the carrier approved or rejected it."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"description":"A toll-free verification application: the numbers it covers, the business behind them and where it stands."},"TollFreeVerificationOptions":{"type":"object","additionalProperties":true,"required":["use_cases","monthly_volumes","entity_types","provinces","fee_cents"],"properties":{"use_cases":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["value","label_en","label_fr"],"properties":{"value":{"type":"string"},"label_en":{"type":"string"},"label_fr":{"type":"string"}}}},"monthly_volumes":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["value","label_en","label_fr"],"properties":{"value":{"type":"string"},"label_en":{"type":"string"},"label_fr":{"type":"string"}}}},"entity_types":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["value","label_en","label_fr"],"properties":{"value":{"type":"string"},"label_en":{"type":"string"},"label_fr":{"type":"string"}}}},"provinces":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["value","label_en","label_fr"],"properties":{"value":{"type":"string"},"label_en":{"type":"string"},"label_fr":{"type":"string"}}}},"fee_cents":{"type":"integer","description":"The verification fee, in cents CAD, charged once per application."}},"description":"The values a toll-free verification application takes, each with an English and French label, and the fee."},"BusinessVerification":{"type":"object","additionalProperties":true,"required":["id","status","verified","verified_at","verified_details","business_name","doing_business_as","entity_type","business_registration_number","business_address","website","business_description","contact","reviewer_note","revoked_reason","submitted_at","reviewed_at","revoked_at","created_at","updated_at"],"properties":{"id":{"type":"string"},"status":{"type":"string","enum":["DRAFT","IN_REVIEW","VERIFIED","CHANGES_REQUESTED","DECLINED","REVOKED"],"description":"DRAFT, then IN_REVIEW (HonkIO staff), then VERIFIED. CHANGES_REQUESTED and DECLINED can be edited and submitted again; REVOKED can only be restored by HonkIO staff. Editing a VERIFIED profile moves it back to DRAFT for another review."},"verified":{"type":"boolean","description":"Whether the business counts as verified. It stays true while the changed details of a verified business are reviewed again (status DRAFT, IN_REVIEW or CHANGES_REQUESTED), and turns false when HonkIO staff decline the changes or revoke the verification."},"verified_at":{"type":["string","null"],"format":"date-time","description":"When HonkIO staff last verified the business; null when it is not verified."},"verified_details":{"oneOf":[{"type":"object","additionalProperties":true,"required":["business_name","doing_business_as","entity_type","business_registration_number"],"properties":{"business_name":{"type":"string"},"doing_business_as":{"type":["string","null"]},"entity_type":{"type":"string","enum":["SOLE_PROPRIETOR","PRIVATE_PROFIT","PUBLIC_PROFIT","NON_PROFIT","GOVERNMENT"]},"business_registration_number":{"type":["string","null"]}}},{"type":"null"}],"description":"The business as HonkIO staff last verified it. While changed details are reviewed again, the fields below show the changes and this keeps what was verified: toll-free verification reads its business number. Null when the business is not verified."},"business_name":{"type":"string"},"doing_business_as":{"type":["string","null"]},"entity_type":{"type":"string","enum":["SOLE_PROPRIETOR","PRIVATE_PROFIT","PUBLIC_PROFIT","NON_PROFIT","GOVERNMENT"]},"business_registration_number":{"type":["string","null"]},"business_address":{"oneOf":[{"type":"object","additionalProperties":true,"required":["line1","line2","city","province","postal_code"],"properties":{"line1":{"type":"string"},"line2":{"type":["string","null"]},"city":{"type":"string"},"province":{"type":"string"},"postal_code":{"type":"string"}}},{"type":"null"}]},"website":{"type":["string","null"]},"business_description":{"type":["string","null"]},"contact":{"type":"object","additionalProperties":true,"required":["first_name","last_name","email","phone"],"properties":{"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]},"email":{"type":["string","null"]},"phone":{"type":["string","null"]}}},"reviewer_note":{"type":["string","null"],"description":"What HonkIO staff asked to change, or why they declined the profile."},"revoked_reason":{"type":["string","null"],"description":"Why HonkIO staff revoked the verification."},"submitted_at":{"type":["string","null"],"format":"date-time"},"reviewed_at":{"type":["string","null"],"format":"date-time"},"revoked_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"description":"Your business verification: the profile staff review, where the review stands and, when they asked for changes, declined or revoked it, why."},"TopupCapRequest":{"type":"object","additionalProperties":true,"required":["id","requested_max_balance_cents","requested_max_30d_cents","reason","status","granted_max_balance_cents","granted_max_30d_cents","decision_note","decided_at","created_at"],"properties":{"id":{"type":"string"},"requested_max_balance_cents":{"type":["integer","null"],"description":"The balance cap asked for, in CAD cents; null when not asked for."},"requested_max_30d_cents":{"type":["integer","null"],"description":"The rolling 30-day cap asked for, in CAD cents; null when not asked for."},"reason":{"type":"string"},"status":{"type":"string","enum":["PENDING","APPROVED","DECLINED"]},"granted_max_balance_cents":{"type":["integer","null"]},"granted_max_30d_cents":{"type":["integer","null"]},"decision_note":{"type":["string","null"]},"decided_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}},"description":"A request for higher top-up limits."},"AutoTopup":{"type":"object","additionalProperties":true,"required":["available","enabled","threshold_cents","amount_cents","card","paused_at","paused_reason","cap_blocked","setup_pending","monthly_rent_cents","attempts"],"properties":{"available":{"type":"boolean","description":"False when auto top-up cannot be used on this platform right now. The other fields still describe your settings."},"enabled":{"type":"boolean","description":"Whether auto top-up is on. It can be on and paused (paused_at)."},"threshold_cents":{"type":["integer","null"],"description":"When your balance falls below this, in CAD cents, your card is charged. Null before auto top-up was first turned on."},"amount_cents":{"type":["integer","null"],"description":"The credit each auto top-up adds, in CAD cents, before tax. Always at least threshold_cents, so one top-up brings the balance back up to it. Null before auto top-up was first turned on."},"card":{"oneOf":[{"type":"object","additionalProperties":true,"required":["brand","last4","exp_month","exp_year"],"properties":{"brand":{"type":"string"},"last4":{"type":"string"},"exp_month":{"type":"integer"},"exp_year":{"type":"integer"}}},{"type":"null"}],"description":"The saved card auto top-up charges. Null when none is saved."},"paused_at":{"type":["string","null"],"format":"date-time","description":"When auto top-up was paused. Nothing is charged while it is paused. You resume it with \"Top up to resume\" in the Auto top-up section of the dashboard's Billing page, which is a new top-up that saves your card again. A pause for ACCOUNT_REVIEW cannot be resumed that way (see paused_reason)."},"paused_reason":{"type":["string","null"],"enum":["DECLINED","AUTHENTICATION_REQUIRED","EXPIRED_CARD","CARD_REMOVED","BILLING_ADDRESS","ACCOUNT_REVIEW","ADMIN","REFUND","CHARGE_LIMIT",null],"description":"Why it was paused: the card was declined, needed authentication, expired or was removed; the billing address could not be used for tax; a payment was refunded and taken off the balance (REFUND); it reached its limit of 3 charges in 24 hours (CHARGE_LIMIT); the account is under review; or HonkIO staff paused it. You resume from every reason except ACCOUNT_REVIEW with \"Top up to resume\" on the dashboard. You cannot resume from ACCOUNT_REVIEW yourself. The account.auto_topup_paused webhook event carries the same value as reason."},"cap_blocked":{"type":"boolean","description":"True while auto top-up is skipping charges because one would go over your balance or 30-day top-up limit."},"setup_pending":{"type":"boolean","description":"True when a setup was started and not finished: settings were chosen on the dashboard and the top-up that saves the card has not completed. They take effect only when it does. False once it completes, and after the settings are edited or auto top-up is turned off."},"monthly_rent_cents":{"type":"integer","description":"Monthly rent of the numbers you hold, in CAD cents. Auto top-up also tops up ahead of rent due within a day."},"attempts":{"type":"array","items":{"$ref":"#/components/schemas/AutoTopupAttempt"},"description":"The 10 most recent attempts, newest first."}},"description":"Your auto top-up: the settings, the saved card, whether it is paused and why, and recent attempts."},"AutoTopupAttempt":{"type":"object","additionalProperties":true,"required":["id","trigger","status","amount_cents","tax_cents","total_cents","failure_code","created_at","settled_at"],"properties":{"id":{"type":"string","description":"ata_…"},"trigger":{"type":"string","enum":["threshold","rent"],"description":"threshold: the balance fell below threshold_cents. rent: the balance was below the number rent due within 24 hours."},"status":{"type":"string","enum":["pending","succeeded","failed","skipped_cap"],"description":"skipped_cap: not charged, because the top-up would have gone over your balance or 30-day top-up limit."},"amount_cents":{"type":"integer","description":"The credit added to your balance, in CAD cents."},"tax_cents":{"type":["integer","null"],"description":"Sales tax charged on top of amount_cents, in CAD cents. Null when no tax was calculated."},"total_cents":{"type":["integer","null"],"description":"What the card was charged, in CAD cents: amount_cents plus tax_cents. Null before the charge is made."},"failure_code":{"type":["string","null"],"description":"Why a failed attempt failed: a card code such as card_declined or expired_card, or platform_error when the failure was on HonkIO's side (auto top-up is not paused and tries again). Attempts that charged nothing and paused nothing (abandoned, not_needed) are never shown."},"created_at":{"type":"string","format":"date-time"},"settled_at":{"type":["string","null"],"format":"date-time"}}},"PaginationMeta":{"type":"object","additionalProperties":true,"required":["page","limit","total","pages"],"properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"pages":{"type":"integer"}},"description":"Page-number pagination, returned as `meta` by the messages listing."},"PaginationOffset":{"type":"object","additionalProperties":true,"required":["total","limit","offset"],"properties":{"total":{"type":"integer","description":"Total matching records."},"limit":{"type":"integer"},"offset":{"type":"integer"}},"description":"Offset pagination, returned alongside `data` by the verify, contacts, contact groups and lists listings."},"OAuthError":{"type":"object","additionalProperties":true,"required":["error"],"properties":{"error":{"type":"string"},"error_description":{"type":"string"}},"description":"OAuth error body (RFC 6749 section 5.2), not the API's standard Error shape."},"def-0":{"type":"object","description":"Per-resource permissions.","additionalProperties":{"type":"string"},"title":"ApiKeyPermissions"}}},"paths":{"/v1/auth/login":{"post":{"summary":"Authenticate and retrieve API keys","tags":["Auth"],"description":"Validates email and password hash. Returns account info and API key prefix list for the dashboard session.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":1}}}}}},"security":[],"responses":{"200":{"description":"Signed in. The two session keys are shown once; the dashboard uses them for its own calls.","content":{"application/json":{"schema":{"description":"Signed in. The two session keys are shown once; the dashboard uses them for its own calls.","type":"object","additionalProperties":true,"required":["account","api_keys","session_key","session_key_id","session_test_key","session_test_key_id"],"properties":{"account":{"type":"object","additionalProperties":true,"required":["id","name","email","status","balance_cents","created_at"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"},"status":{"type":"string","enum":["PENDING_PAYMENT","ACTIVE"]},"balance_cents":{"type":"integer"},"created_at":{"type":"string","format":"date-time"}}},"api_keys":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"},"description":"The account's active USER keys, without permissions or defaultDeny."},"session_key":{"type":"string","description":"A LIVE dashboard session key, valid for 7 days or until the next login or password change."},"session_key_id":{"type":"string"},"session_test_key":{"type":"string","description":"The matching TEST dashboard session key."},"session_test_key_id":{"type":"string"}}}}}},"401":{"description":"INVALID_CREDENTIALS: unknown email or wrong password.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"ACCOUNT_SUSPENDED: the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"RATE_LIMITED: 10 attempts per 15 minutes from one address, or 20 failures per 15 minutes for one email.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/auth/set-password":{"post":{"summary":"Change account password (requires current password)","tags":["Auth"],"description":"Also signs out every dashboard session and disconnects every app connected through OAuth: each connection's API key and tokens are revoked.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","new_password","current_password"],"properties":{"email":{"type":"string","format":"email"},"new_password":{"type":"string","minLength":10},"current_password":{"type":"string","minLength":1}}}}}},"security":[],"responses":{"200":{"description":"Password changed. Every dashboard session is signed out.","content":{"application/json":{"schema":{"description":"Password changed. Every dashboard session is signed out.","type":"object","additionalProperties":true,"required":["status"],"properties":{"status":{"type":"string","enum":["password_set"]}}}}}},"401":{"description":"UNAUTHORIZED or INVALID_CREDENTIALS: unknown account or wrong current password.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"RATE_LIMITED: 10 attempts per hour from one address.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/auth/forgot-password":{"post":{"summary":"Request a password reset email","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"security":[],"responses":{"200":{"description":"Always answered, whether or not the email has an account, so the response reveals nothing.","content":{"application/json":{"schema":{"description":"Always answered, whether or not the email has an account, so the response reveals nothing.","type":"object","additionalProperties":true,"required":["status"],"properties":{"status":{"type":"string","enum":["reset_email_sent"]}}}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"RATE_LIMITED: 5 requests per 15 minutes from one address.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/auth/reset-password":{"post":{"summary":"Reset password using emailed token","tags":["Auth"],"description":"Also signs out every dashboard session and disconnects every app connected through OAuth: each connection's API key and tokens are revoked.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token","new_password"],"properties":{"token":{"type":"string","minLength":64,"maxLength":64,"description":"Reset token from email link"},"new_password":{"type":"string","minLength":10}}}}}},"security":[],"responses":{"200":{"description":"Password reset. Every dashboard session is signed out.","content":{"application/json":{"schema":{"description":"Password reset. Every dashboard session is signed out.","type":"object","additionalProperties":true,"required":["status"],"properties":{"status":{"type":"string","enum":["password_reset"]}}}}}},"422":{"description":"VALIDATION_ERROR: details.token when the token is unknown, used or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"RATE_LIMITED: 10 attempts per 15 minutes from one address.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/auth/step-up":{"post":{"summary":"Issue a short-lived elevated-permission token after password re-entry","tags":["Auth"],"description":"Send the returned token as X-Step-Up-Token on the one request it is for: it is honoured once, expires after 5 minutes, and only works with the API key that requested it. `compliance:d` allows a right-to-erasure call. `api_keys:elevate` allows creating or rotating a key with permissions beyond the calling key's.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["password","scope"],"properties":{"password":{"type":"string","minLength":1},"scope":{"type":"string","enum":["compliance:d","api_keys:elevate"]}}}}}},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"responses":{"200":{"description":"A single-use token for the X-Step-Up-Token header, valid for 5 minutes with the key that requested it.","content":{"application/json":{"schema":{"description":"A single-use token for the X-Step-Up-Token header, valid for 5 minutes with the key that requested it.","type":"object","additionalProperties":true,"required":["token","scope","expires_at"],"properties":{"token":{"type":"string"},"scope":{"type":"string","enum":["compliance:d","api_keys:elevate"]},"expires_at":{"type":"string","format":"date-time"}}}}}},"401":{"description":"UNAUTHORIZED: missing or invalid API key, or the password is wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"RATE_LIMITED: 10 attempts per 15 minutes from one address, 20 wrong passwords per 15 minutes for one account, or the account-wide 100 requests per second.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/auth/verify-email":{"post":{"summary":"Confirm an account email address using the link emailed at signup","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","minLength":64,"maxLength":64,"description":"Email verification token"}}}}}},"security":[],"responses":{"200":{"description":"Email address confirmed.","content":{"application/json":{"schema":{"description":"Email address confirmed.","type":"object","additionalProperties":true,"required":["status"],"properties":{"status":{"type":"string","enum":["email_verified"]}}}}}},"422":{"description":"VALIDATION_ERROR: details.token says whether the token is invalid, expired or already used.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"RATE_LIMITED: 20 attempts per hour from one address.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/auth/resend-verification":{"post":{"summary":"Send a fresh email-verification link to an account email","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"security":[],"responses":{"200":{"description":"Always answered, whether or not the email has an unverified account, so the response reveals nothing.","content":{"application/json":{"schema":{"description":"Always answered, whether or not the email has an unverified account, so the response reveals nothing.","type":"object","additionalProperties":true,"required":["status"],"properties":{"status":{"type":"string","enum":["verification_email_sent"]}}}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"RATE_LIMITED: 5 requests per hour from one address.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/accounts":{"post":{"summary":"Create a new account","tags":["Accounts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email","password","terms_accepted","terms_version"],"properties":{"name":{"type":"string"},"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":10,"description":"Account password (required, min 10 chars)"},"terms_accepted":{"description":"Must be true: affirmative acceptance of the Terms of Service and Privacy Policy (required).","enum":[true]},"terms_version":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"The Terms' \"last updated\" date the signer saw, YYYY-MM-DD (required)."}}}}}},"security":[],"responses":{"201":{"description":"Account created in PENDING_PAYMENT, with no API keys. Sign in to the dashboard and create one on API Keys, where the raw key is shown once.","content":{"application/json":{"schema":{"description":"Account created in PENDING_PAYMENT, with no API keys. Sign in to the dashboard and create one on API Keys, where the raw key is shown once.","type":"object","additionalProperties":true,"required":["account"],"properties":{"account":{"$ref":"#/components/schemas/Account"}}}}}},"409":{"description":"CONFLICT: an account already uses this email.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"RATE_LIMITED: 5 signups per hour from one address.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"SERVICE_UNAVAILABLE: new registrations are paused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/accounts/{id}":{"patch":{"summary":"Update account name or settings","tags":["Accounts"],"description":"name is required: an omitted or empty value is refused with 422 VALIDATION_ERROR.\n\nRequires `account:m`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"Required."}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"account:m","responses":{"200":{"description":"The updated account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Account"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN: the id is not the calling key's account, or the key lacks the permission this operation requires.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"Get account details","tags":["Accounts"],"description":"Requires `account:r`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"account:r","responses":{"200":{"description":"Account with its active API keys.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountDocument"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN: the id is not the calling key's account, or the key lacks the permission this operation requires.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/accounts/me":{"get":{"summary":"Get the account the API key belongs to","tags":["Accounts"],"description":"Returns the same document as GET /v1/accounts/{id}, resolving the account from the API key so no id is needed, plus `caller`: the calling key's mode and, for an OAuth connection, the client it was granted to.\n\nRequires `account:r`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"account:r","responses":{"200":{"description":"The account the API key belongs to, and who is calling.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountMeDocument"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/accounts/{id}/topup-allowance":{"get":{"summary":"Top-up allowance (balance cap and rolling 30-day cap)","tags":["Accounts"],"description":"Requires `account:r`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"account:r","responses":{"200":{"description":"How much may be added now. Amounts are CAD cents.","content":{"application/json":{"schema":{"description":"How much may be added now. Amounts are CAD cents.","type":"object","additionalProperties":true,"required":["max_topup_now_cents","headroom_balance_cents","headroom_30d_cents","max_balance_cents","max_30d_cents","balance_cents","topped_up_30d_cents"],"properties":{"max_topup_now_cents":{"type":"integer","description":"The largest top-up accepted right now: the smaller of the two headrooms."},"headroom_balance_cents":{"type":"integer","description":"Room left under the balance cap."},"headroom_30d_cents":{"type":"integer","description":"Room left under the rolling 30-day cap."},"max_balance_cents":{"type":"integer"},"max_30d_cents":{"type":"integer"},"balance_cents":{"type":"integer"},"topped_up_30d_cents":{"type":"integer"}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN: the id is not the calling key's account, or the key lacks the permission this operation requires.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/accounts/{id}/usage":{"get":{"summary":"Get usage summary for a billing period","tags":["Accounts"],"description":"Message counts, billed cost and segments for the period (defaults to the current calendar month), plus live delivery health: \"delivery\" { liveOutbound, delivered, failed, undelivered, pending, failureRatePct } over live outbound messages (auto-replies excluded) and \"byNumber\", the same figures per sending number, busiest first. A rising failureRatePct usually means wrong numbers, landlines, or a script retrying one recipient; above 10 % over your last 50 sends you are emailed and account.delivery_warning fires.\n\nRequires `account:r`.","parameters":[{"schema":{"type":"string","format":"date"},"in":"query","name":"from","required":false,"description":"Start date (YYYY-MM-DD)"},{"schema":{"type":"string","format":"date"},"in":"query","name":"to","required":false,"description":"End date (YYYY-MM-DD)"},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"account:r","responses":{"200":{"description":"Usage for the period. Field names are camelCase, unlike the rest of the API.","content":{"application/json":{"schema":{"description":"Usage for the period. Field names are camelCase, unlike the rest of the API.","type":"object","additionalProperties":true,"required":["totalMessages","testMessages","outboundMessages","inboundMessages","totalCostCents","segmentCount","delivery","byNumber","period"],"properties":{"totalMessages":{"type":"integer","description":"Every message in the period, inbound and test-mode included."},"testMessages":{"type":"integer","description":"How many of totalMessages were test-mode simulations, billed nothing."},"outboundMessages":{"type":"integer"},"inboundMessages":{"type":"integer"},"totalCostCents":{"type":"integer","description":"What LIVE messages cost, in CAD cents."},"segmentCount":{"type":"integer","description":"LIVE SMS parts billed."},"delivery":{"type":"object","additionalProperties":true,"required":["liveOutbound","delivered","failed","undelivered","pending","failureRatePct"],"properties":{"liveOutbound":{"type":"integer"},"delivered":{"type":"integer"},"failed":{"type":"integer"},"undelivered":{"type":"integer"},"pending":{"type":"integer","description":"Accepted, no verdict from the carrier yet."},"failureRatePct":{"type":"number","description":"(failed + undelivered) / liveOutbound x 100, one decimal; 0 with no traffic."}}},"byNumber":{"type":"array","description":"The same delivery figures per sending number, busiest first.","items":{"type":"object","additionalProperties":true,"required":["from","liveOutbound","delivered","failed","undelivered","failureRatePct"],"properties":{"from":{"type":"string"},"liveOutbound":{"type":"integer"},"delivered":{"type":"integer"},"failed":{"type":"integer"},"undelivered":{"type":"integer"},"failureRatePct":{"type":"number"}}}},"period":{"type":"object","additionalProperties":true,"required":["from","to"],"properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time","description":"Exclusive end of the period."}}}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN: the id is not the calling key's account, or the key lacks the permission this operation requires.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: a malformed date, or to before from.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/accounts/{id}/transactions":{"get":{"summary":"List balance transactions (ledger), newest first","tags":["Accounts"],"description":"Every non-message balance movement: Stripe top-ups/refunds/disputes, phone rent, provisioning fees, and verification upcharges, paginated newest first. Per-message SMS/email costs are NOT included here; see the messages/emails list for those.\n\nRequires `account:r`.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"string"},"in":"query","name":"before","required":false,"description":"Id of the oldest transaction already seen. Returns the next (older) page."},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"account:r","responses":{"200":{"description":"Paginated balance transaction ledger","content":{"application/json":{"schema":{"description":"Paginated balance transaction ledger","type":"object","properties":{"transactions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"amount_cents":{"type":"integer","description":"Signed: positive = credit, negative = debit"},"balance_after_cents":{"type":"integer"},"reason":{"type":"string","description":"Why the balance moved: topup, refund, dispute, phone_rent, provisioning, activation, verification, and their *_refund reversals; admin_adjustment for staff changes. Per-message SMS charges do not appear here: they are on the messages themselves. One-off corrections carry their own reasons (inbound_backfill, overcharge_refund, sweep_reversal)."},"tax_cents":{"type":"integer","description":"Top-ups only, when sales tax was charged: the GST/HST charged on top of amount_cents. amount_cents is always the credit, before tax. Absent otherwise."},"charged_cents":{"type":"integer","description":"Top-ups only, when sales tax was charged: what the card was charged, amount_cents plus tax_cents. Absent otherwise."},"created_at":{"type":"string","format":"date-time"}}}},"has_more":{"type":"boolean"}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: also when the id is not the calling key's account (404 rather than 403, so the ledger's existence is not confirmed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/accounts/{id}/api-keys":{"post":{"summary":"Issue a new API key","tags":["Accounts"],"description":"Requires `api_keys:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mode":{"type":"string","enum":["live","test"],"description":"Defaults to the calling key's mode. A test key can only create test keys."},"label":{"type":"string","maxLength":100},"permissions":{"type":"object","description":"Per-resource permissions. Each key is a resource name; value is a string of allowed operations: r=read, w=write, m=modify, d=delete. Omit a resource to deny all access to it. Example: { \"messages\": \"rw\", \"contacts\": \"r\" }. When omitted, the new key gets the calling key's permissions. A key can only grant permissions it holds itself, unless the request carries an `api_keys:elevate` step-up token (X-Step-Up-Token). A test key can only create test keys. A dashboard session key (the one login mints) always needs that step-up to create a key, even one no broader than itself.","additionalProperties":{"type":"string"}},"email_domain_ids":{"type":"array","items":{"type":"string","minLength":1},"maxItems":100,"description":"Restrict the key to sending email from these domains (ids from /v1/email-domains). Omitted: the key inherits the calling key's restriction, if any. A restricted key can only create keys restricted to a subset of its own domains, unless the request carries an api_keys:elevate step-up token."}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"header","name":"x-step-up-token","required":false,"description":"A token from POST /v1/auth/step-up with the api_keys:elevate scope. Only needed to request permissions broader than the calling key's own, or when the calling key is a dashboard session key."}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"api_keys:w","responses":{"201":{"description":"API key issued. The raw key is shown only once: store it securely.","content":{"application/json":{"schema":{"description":"API key issued. The raw key is shown only once: store it securely.","type":"object","properties":{"key":{"type":"string","description":"Full API key (shown once only)"},"prefix":{"type":"string","description":"Non-secret prefix for display/identification"},"mode":{"type":"string","enum":["live","test"]},"permissions":{"$ref":"#/components/schemas/def-0"},"email_domain_ids":{"type":["null","array"],"items":{"type":"string"}},"notice":{"type":"string"}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN (not your account), LIVE_KEY_REQUIRED (a test key asked for a live key), KEY_FENCED (the calling key has an allow/deny fence), or PERMISSION_ESCALATION (broader permissions or email domains than the calling key, or a dashboard session key, without an api_keys:elevate step-up).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/accounts/{id}/api-keys/{keyId}/rotate":{"post":{"summary":"Rotate an API key (issue replacement and revoke the old one atomically)","tags":["Accounts"],"description":"Session keys (the ones minted by dashboard login) can't be rotated: they always expire on their own; rotating one returns 409 CONFLICT with details.reason \"session_key\". Rotate a USER key instead. A key an OAuth connection holds (oauth_grant on the keys list) can't be rotated either: the replacement would not belong to the connection, so the app would stop working. That returns 409 CONFLICT with details.reason \"oauth_grant\"; revoke the connection and reconnect instead.\n\nRequires `api_keys:w`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"keyId","required":true},{"schema":{"type":"string"},"in":"header","name":"x-step-up-token","required":false,"description":"A token from POST /v1/auth/step-up with the api_keys:elevate scope. Only needed to rotate a key into one holding more than the calling key's own permissions, or when the calling key is a dashboard session key."}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"api_keys:w","responses":{"201":{"description":"The replacement key, shown once. The old key is revoked in the same step; the new one keeps its mode, label, permissions, default-deny setting, lists and email-domain restriction.","content":{"application/json":{"schema":{"description":"The replacement key, shown once. The old key is revoked in the same step; the new one keeps its mode, label, permissions, default-deny setting, lists and email-domain restriction.","type":"object","additionalProperties":true,"required":["key","prefix","mode","revoked_id","notice"],"properties":{"key":{"type":"string","description":"The full new API key (shown once only)."},"prefix":{"type":"string"},"mode":{"type":"string","enum":["live","test"]},"revoked_id":{"type":"string","description":"The id of the key this replaced."},"notice":{"type":"string"}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN (not your account), LIVE_KEY_REQUIRED (a test key rotating a live key), KEY_FENCED (a key with an allow/deny fence rotating any key but itself), or PERMISSION_ESCALATION (the target holds more than the calling key, or the caller is a dashboard session key, without an api_keys:elevate step-up).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"CONFLICT: the key is already revoked, is being rotated by another request, is a dashboard session key (details.reason is session_key), or is held by an OAuth connection (details.reason is oauth_grant).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/accounts/{id}/api-keys/{keyId}":{"delete":{"summary":"Revoke an API key","tags":["Accounts"],"description":"Requires `api_keys:d`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"keyId","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"api_keys:d","responses":{"204":{"description":"Revoked."},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN (not your account) or LIVE_KEY_REQUIRED (a test key revoking a live key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/accounts/{id}/api-keys/{keyId}/default-deny":{"patch":{"summary":"Toggle default-deny mode for an API key","tags":["Accounts"],"description":"When default_deny is true, messages via this key are blocked unless the recipient is in an assigned ALLOW list.\n\nRequires `api_keys:m`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["default_deny"],"properties":{"default_deny":{"type":"boolean"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"keyId","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"api_keys:m","responses":{"200":{"description":"The key's new default-deny setting.","content":{"application/json":{"schema":{"description":"The key's new default-deny setting.","type":"object","additionalProperties":true,"required":["api_key_id","default_deny"],"properties":{"api_key_id":{"type":"string"},"default_deny":{"type":"boolean"}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN (not your account), LIVE_KEY_REQUIRED (a test key changing a live key), or KEY_FENCED (a fenced key changing its own or any other key's default-deny).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/accounts/{id}/api-keys/{keyId}/lists":{"get":{"summary":"Get allow/deny list assignments for an API key","tags":["Accounts"],"description":"Requires `api_keys:r`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"keyId","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"api_keys:r","responses":{"200":{"description":"The key's allow and deny lists, null when none is assigned.","content":{"application/json":{"schema":{"description":"The key's allow and deny lists, null when none is assigned.","type":"object","additionalProperties":true,"required":["allow_list","deny_list"],"properties":{"allow_list":{"oneOf":[{"type":"object","additionalProperties":true,"required":["id","name","description"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]}}},{"type":"null"}]},"deny_list":{"oneOf":[{"type":"object","additionalProperties":true,"required":["id","name","description"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]}}},{"type":"null"}]}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN: the id is not the calling key's account, or the key lacks the permission this operation requires.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/accounts/{id}/api-keys/{keyId}/lists/{mode}":{"put":{"summary":"Assign an allow or deny list to an API key","tags":["Accounts"],"description":"Requires `api_keys:m`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["list_id"],"properties":{"list_id":{"type":"string"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"keyId","required":true},{"schema":{"type":"string","enum":["allow","deny"]},"in":"path","name":"mode","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"api_keys:m","responses":{"200":{"description":"The list now assigned.","content":{"application/json":{"schema":{"description":"The list now assigned.","type":"object","additionalProperties":true,"required":["api_key_id","mode","list_id"],"properties":{"api_key_id":{"type":"string"},"mode":{"type":"string","enum":["ALLOW","DENY"]},"list_id":{"type":"string"}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN (not your account), LIVE_KEY_REQUIRED (a test key changing a live key), or KEY_FENCED (a fenced key changing its own or any other key's lists).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND (no such key) or CONTACT_LIST_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Remove an allow or deny list from an API key","tags":["Accounts"],"description":"Requires `api_keys:m`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"keyId","required":true},{"schema":{"type":"string","enum":["allow","deny"]},"in":"path","name":"mode","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"api_keys:m","responses":{"204":{"description":"Unassigned (also when nothing was assigned)."},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN (not your account), LIVE_KEY_REQUIRED (a test key changing a live key), or KEY_FENCED (a fenced key changing its own or any other key's lists).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/accounts/{id}/phone-verification":{"post":{"summary":"Start account owner phone verification","tags":["Accounts"],"description":"Requires `account:m`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phone"],"properties":{"phone":{"type":"string","pattern":"^\\+1\\d{10}$","description":"Canadian or US NANP number in E.164, e.g. +14165551234"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"account:m","responses":{"200":{"description":"The code was sent by SMS.","content":{"application/json":{"schema":{"description":"The code was sent by SMS.","type":"object","additionalProperties":true,"required":["sent","expires_in_seconds"],"properties":{"sent":{"type":"boolean","enum":[true]},"expires_in_seconds":{"type":"integer"}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN (not your account) or LIVE_KEY_REQUIRED (owner verification needs a live key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"RATE_LIMITED: 3 codes per hour per account.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"CARRIER_ERROR: the carrier did not accept the SMS.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"SERVICE_UNAVAILABLE: owner verification is not configured. CARRIER_THROTTLED: the carrier asked us to slow down and did not take the SMS; ask again after Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/accounts/{id}/phone-verification/confirm":{"post":{"summary":"Confirm OTP to complete phone verification","tags":["Accounts"],"description":"Requires `account:m`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","pattern":"^\\d{6}$","description":"The 6-digit code sent by SMS"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"account:m","responses":{"200":{"description":"Verified: live sending is unlocked.","content":{"application/json":{"schema":{"description":"Verified: live sending is unlocked.","type":"object","additionalProperties":true,"required":["verified","verified_phone","verified_at"],"properties":{"verified":{"type":"boolean","enum":[true]},"verified_phone":{"type":"string"},"verified_at":{"type":"string","format":"date-time"}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN (not your account) or LIVE_KEY_REQUIRED (owner verification needs a live key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR, VERIFICATION_EXPIRED (no code pending; body statusCode 422) or VERIFICATION_INVALID_CODE.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"VERIFICATION_MAX_ATTEMPTS: five wrong codes; request a new one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/auto-topup":{"get":{"summary":"Your auto top-up settings","tags":["Accounts"],"description":"Whether auto top-up is on, the balance that triggers it and the amount it adds, a summary of the saved card, whether it is paused and why, and the 10 most recent attempts. available is false when auto top-up cannot be used on this platform right now. It is turned on with a top-up on the dashboard, which saves the card; it cannot be changed with an API key.\n\nRequires `account:r`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"account:r","responses":{"200":{"description":"Auto top-up settings, saved card summary, pause state and recent attempts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoTopup"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/messages":{"post":{"summary":"Send an SMS message","tags":["Messages"],"description":"Live sends are gated before any charge: 403 SENDING_PAUSED, 429 DAILY_LIMIT_REACHED / NUMBER_RATE_LIMITED / RECIPIENT_RATE_LIMITED (NUMBER_RATE_LIMITED: the sending number already has a minute of message parts waiting for the carrier, which sends 10 parts a minute from a local number; RECIPIENT_RATE_LIMITED: 30 messages an hour and 100 a day to one recipient; Retry-After is set on both) / 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. From a toll-free number: 403 TOLL_FREE_NOT_VERIFIED until the number's toll-free verification is approved (test keys too), and 451 DOUBLE_OPT_IN_REQUIRED until the recipient has replied YES to an opt-in confirmation from that number (POST /v1/compliance/opt-in-confirmations). A refused send costs nothing. 503 CARRIER_UNAVAILABLE means the carrier could not be reached; nothing was sent or charged, retry shortly. 503 CARRIER_THROTTLED means the carrier asked us to slow down and did not take the message: nothing was charged; retry after Retry-After with the same Idempotency-Key, which sends it. 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. A live send answers estimated_send_at, an estimate of when the carrier will send it.\n\nRequires `messages:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["from","to","body"],"properties":{"from":{"type":"string","description":"Sending number (E.164, must be owned by account)"},"to":{"type":"string","description":"Recipient number (E.164, Canadian numbers only)"},"body":{"type":"string","maxLength":1600,"description":"Up to 1600 characters and 10 SMS parts (about 1,530 GSM-7 or 670 Unicode characters). Longer bodies are rejected with 422 MESSAGE_TOO_LONG before any charge. Billed per part as the carrier splits it."},"skip_consent_check":{"type":"boolean","description":"Skip the CASL consent gate. Test-mode keys only: a live key gets 403 FORBIDDEN. Use only when you have consent recorded outside HonkIO."},"dncl_exemptions":{"type":"array","items":{"type":"string","enum":["existing_business_relationship","registered_charity","political","survey","newspaper_subscription","personal"]},"description":"DNCL exemption reasons. CRTC DNCL checking is coming soon and not yet enforced, so this field is currently accepted but not acted upon."}}}}}},"parameters":[{"schema":{"type":"string","maxLength":255},"in":"header","name":"idempotency-key","required":false}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"messages:w","responses":{"201":{"description":"Message sent (LIVE) or simulated (TEST). With an Idempotency-Key already used for the same from, to and body, the original message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SentMessage"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"INSUFFICIENT_BALANCE: the balance cannot cover the send. Also PAYMENT_REQUIRED when a LIVE key is used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN (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, SENDING_PAUSED, or TOLL_FREE_NOT_VERIFIED (a toll-free from number without an approved toll-free verification; test keys too).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"PHONE_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).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"DAILY_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. NUMBER_RATE_LIMITED: the sending number already has a minute of message parts waiting for the carrier (a local number sends 10 parts a minute); wait Retry-After.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"451":{"description":"The CASL gate refused the recipient: NO_CONSENT, OPT_OUT_BLOCKED or CONSENT_EXPIRED. From a toll-free number, also DOUBLE_OPT_IN_REQUIRED: the recipient has not yet replied YES to an opt-in confirmation from that number (POST /v1/compliance/opt-in-confirmations).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"CARRIER_ERROR: the carrier refused the send. Any charge is refunded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"CARRIER_UNAVAILABLE: the carrier could not be reached. Nothing was sent or charged; retry shortly. CARRIER_THROTTLED: the carrier asked us to slow down and did not take the message. Nothing was charged; retry after Retry-After with the same Idempotency-Key, which sends it. 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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List messages","tags":["Messages"],"description":"Requires `messages:r`.","parameters":[{"schema":{"type":"string"},"in":"query","name":"q","required":false,"description":"Search by phone number (partial match on to/from)"},{"schema":{"type":"string"},"in":"query","name":"from","required":false},{"schema":{"type":"string"},"in":"query","name":"to","required":false},{"schema":{"type":"string","enum":["QUEUED","SENDING","SENT","DELIVERED","FAILED","UNDELIVERED","RECEIVED"]},"in":"query","name":"status","required":false},{"schema":{"type":"string","enum":["OUTBOUND","INBOUND"]},"in":"query","name":"direction","required":false},{"schema":{"type":"integer","minimum":1,"default":1},"in":"query","name":"page","required":false},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"in":"query","name":"limit","required":false},{"schema":{"type":"string","format":"date"},"in":"query","name":"date_from","required":false},{"schema":{"type":"string","format":"date"},"in":"query","name":"date_to","required":false}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"messages:r","responses":{"200":{"description":"A page of messages, newest first.","content":{"application/json":{"schema":{"description":"A page of messages, newest first.","type":"object","additionalProperties":true,"required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Message"}},"meta":{"$ref":"#/components/schemas/PaginationMeta"}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/messages/{id}":{"get":{"summary":"Retrieve a message by ID","tags":["Messages"],"description":"Requires `messages:r`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"messages:r","responses":{"200":{"description":"The message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such message on this account. A test key finds TEST messages only.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/verify":{"get":{"summary":"List verifications","tags":["Verify"],"description":"Returns a paginated list of verifications for the authenticated account, newest first.\n\nRequires `verify:r`.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"integer","minimum":0,"default":0},"in":"query","name":"offset","required":false},{"schema":{"type":"string","enum":["pending","verified","expired","max_attempts"]},"in":"query","name":"status","required":false,"description":"Filter by status"}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"verify:r","responses":{"200":{"description":"A page of verifications, newest first.","content":{"application/json":{"schema":{"description":"A page of verifications, newest first.","allOf":[{"$ref":"#/components/schemas/PaginationOffset"},{"type":"object","additionalProperties":true,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Verification"}}}}]}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Start a phone number verification","tags":["Verify"],"description":"Sends an OTP to the specified Canadian phone number.\n\nBilled at your standard per-part message rate (the OTP is one part unless `app_name` is long or uses non-GSM characters; the charge is settled to the carrier's part count) plus a per-verification upcharge: see `verification_upcharge_cents` from `GET /v1/pricing`. A verification the carrier rejects is refunded in full.\n\n**Options:**\n- `code_length`: digits in the OTP, `4`, `6` (default), or `8`\n- `ttl_minutes`: how long the code is valid (1 to 60 min, default 10)\n- `app_name`: brand name shown in the SMS, e.g. `\"Acme\"` → *\"Your Acme verification code is: …\"*\n\nIn test mode (`mk_test_...` key) no SMS is sent; the code is always all zeros for the chosen length (e.g. `000000` for 6-digit, `0000` for 4-digit). `from` may be any valid NANP number in test mode, so an account with no number yet can try it; a toll-free `from` must still be yours and verified.\n\nRequires `verify:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["from","to"],"properties":{"from":{"type":"string","description":"Your HonkIO number (E.164, must be active on your account). A test key may use any valid NANP number, except a toll-free one, which must be yours and verified in both modes."},"to":{"type":"string","description":"The phone number to verify (E.164, Canadian numbers only)"},"code_length":{"type":"integer","enum":[4,6,8],"description":"OTP digit length (default: 6)"},"ttl_minutes":{"type":"integer","minimum":1,"maximum":60,"description":"Minutes until the code expires (default: 10)"},"app_name":{"type":"string","maxLength":64,"description":"Brand name shown in the SMS body (default: HonkIO)"}}}}}},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"verify:w","responses":{"201":{"description":"Verification started: the code was sent (LIVE) or simulated (TEST, code all zeros).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Verification"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"INSUFFICIENT_BALANCE: the balance cannot cover the send. Also PAYMENT_REQUIRED when a LIVE key is used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"ACCOUNT_NOT_VERIFIED, PHONE_NUMBER_NOT_OWNED, ALLOW_LIST_BLOCKED, DENY_LIST_BLOCKED, SENDING_PAUSED, or TOLL_FREE_NOT_VERIFIED (a toll-free from number without an approved toll-free verification; test keys too).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR, NON_CANADIAN_NUMBER, RESERVED_DESTINATION, UNDELIVERABLE_NUMBER, NOT_A_MOBILE_NUMBER or LINK_SHORTENER_BLOCKED. A refused start costs nothing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"RATE_LIMITED (one start per recipient per 60 seconds, Retry-After 60), DAILY_LIMIT_REACHED, FANOUT_LIMIT_REACHED, NUMBER_RATE_LIMITED or RECIPIENT_RATE_LIMITED.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"451":{"description":"OPT_OUT_BLOCKED: the recipient opted out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"CARRIER_ERROR: the carrier refused the SMS. The charge is refunded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"CARRIER_UNAVAILABLE: the carrier could not be reached. Nothing was sent or charged; retry shortly. CARRIER_THROTTLED: the carrier asked us to slow down and did not take the code. Nothing was charged; start again after Retry-After. CARRIER_TIMEOUT: the carrier did not answer in time, so the code may or may not have reached the recipient. The verification is closed, so that code can never be checked, and the charge is refunded: start a new one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/verify/{id}":{"get":{"summary":"Get a verification","tags":["Verify"],"description":"Returns a single verification record by ID.\n\nRequires `verify:r`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"verify:r","responses":{"200":{"description":"The verification.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Verification"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"VERIFICATION_NOT_FOUND. A test key finds TEST verifications only.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/verify/{id}/check":{"post":{"summary":"Check a verification code","tags":["Verify"],"description":"Submit the OTP code the end-user received. Max 5 attempts. Accepts 4, 6, or 8 digit codes depending on how the verification was started.\n\nRequires `verify:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"4 to 8 digit OTP code"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"verify:w","responses":{"200":{"description":"Code accepted: the verification is now verified.","content":{"application/json":{"schema":{"description":"Code accepted: the verification is now verified.","allOf":[{"$ref":"#/components/schemas/Verification"},{"type":"object","additionalProperties":true,"required":["attempts_remaining"],"properties":{"attempts_remaining":{"type":"integer","description":"Always 0 on success."}}}]}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"VERIFICATION_NOT_FOUND. A test key finds TEST verifications only.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"VERIFICATION_ALREADY_VERIFIED.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"VERIFICATION_EXPIRED: start a new verification.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR (not 4 to 8 digits) or VERIFICATION_INVALID_CODE, which carries attempts_remaining.","content":{"application/json":{"schema":{"description":"VALIDATION_ERROR (not 4 to 8 digits) or VERIFICATION_INVALID_CODE, which carries attempts_remaining.","allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","additionalProperties":true,"properties":{"attempts_remaining":{"type":"integer","description":"Wrong-code attempts left. Present only on VERIFICATION_INVALID_CODE."}}}]}}}},"429":{"description":"VERIFICATION_MAX_ATTEMPTS: five wrong codes; start a new verification. Also RATE_LIMITED at 100 requests per second.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"INTERNAL_ERROR: unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/pricing":{"get":{"summary":"Current per-send prices in CAD cents","tags":["Pricing"],"security":[],"responses":{"200":{"description":"Current prices. All amounts are CAD cents.","content":{"application/json":{"schema":{"description":"Current prices. All amounts are CAD cents.","type":"object","properties":{"message_cost_cents":{"type":"integer","description":"Charged per SMS part, counted the way the carrier splits the body: typographic quotes, dashes and ellipses are smart-encoded to GSM-7 (160 characters, then 153 per part); emoji and most accented letters force Unicode parts (70, then 67). The charge is settled to the carrier's part count after the send. Bodies over 10 parts are refused with 422 MESSAGE_TOO_LONG before any charge."},"verification_upcharge_cents":{"type":"integer","description":"Added once to a verification, on top of the OTP's own segment cost."},"verification_cost_cents":{"type":"integer","description":"What a typical single-part verification costs: message_cost_cents + verification_upcharge_cents. A long or non-GSM app_name can push the OTP to two parts and raise this; the charge is settled to the carrier's part count once the OTP is accepted."},"phone_number_upfront_cost_cents":{"type":"integer","description":"The first month's rent for a local number, charged when it is provisioned."},"phone_number_monthly_cost_cents":{"type":"integer","description":"Recurring monthly rent for a local number."},"phone_number_activation_fee_cents":{"type":"integer","description":"One-time activation fee charged with the first month on every number (local and toll-free). Not refunded on release. A local number's first charge is phone_number_upfront_cost_cents + phone_number_activation_fee_cents."},"inbound_message_cost_cents":{"type":"integer","description":"Charged per segment of each inbound SMS received on a provisioned number, using the carrier's part count. Keyword traffic the carrier requires (STOP, START, HELP) is not charged."},"email_price_millicents":{"type":"integer","description":"Charged per accepted email recipient (to, cc and bcc each count), in CAD millicents: thousandths of a cent, so 250 is $0.0025, or $2.50 per 1,000. The first email_attachment_included_bytes of attachments per recipient are included. Test-mode sends are free. Absent while email sending is not available."},"email_attachment_included_bytes":{"type":"integer","description":"Attachment bytes included in email_price_millicents, per recipient."},"email_attachment_price_millicents_per_mb":{"type":"integer","description":"Charged per recipient for every started MB (1,048,576 bytes) of attachments above the included bytes, in CAD millicents. A 10 MiB attachment to one recipient at the defaults is 250 + 8 × 200 = 1,850 millicents ($0.0185)."},"email_attachment_max_bytes":{"type":"integer","description":"Maximum attachment bytes per email, in total and per file (413 EMAIL_ATTACHMENT_TOO_LARGE above it). At most 10 files per email."},"email_inbound_price_millicents":{"type":"integer","description":"Charged per received email, in CAD millicents: thousandths of a cent, so 250 is $0.0025. Covers the first email_inbound_included_bytes of the message. Charged on receipt whatever the balance; mail rejected as a virus or over the daily cap is free. Absent while email sending is not available."},"email_inbound_included_bytes":{"type":"integer","description":"Bytes of a received message (the raw message, headers and encoded attachments included) covered by email_inbound_price_millicents."},"email_inbound_price_millicents_per_mb":{"type":"integer","description":"Charged for every started MB (1,048,576 bytes) of a received message above email_inbound_included_bytes, in CAD millicents. A 10 MiB message at the defaults is 250 + 9 × 200 = 2,050 millicents ($0.0205). 0 means received mail is charged the base price only."}}}}}}}}},"/v1/phone-numbers/area-codes":{"get":{"summary":"List provinces and their active area codes","tags":["Phone Numbers"],"description":"Requires `phone_numbers:r`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"phone_numbers:r","responses":{"200":{"description":"Provinces with their active area codes.","content":{"application/json":{"schema":{"description":"Provinces with their active area codes.","type":"object","additionalProperties":true,"required":["data"],"properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["province","country","area_codes"],"properties":{"province":{"type":"string"},"country":{"type":"string"},"area_codes":{"type":"array","items":{"type":"string"}}}}}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/phone-numbers/search":{"get":{"summary":"Search available Canadian phone numbers to provision","tags":["Phone Numbers"],"description":"Optionally filtered by `area_codes`: up to 25 distinct codes (repeats count once), each an active Canadian area code (GET /v1/phone-numbers/area-codes) or a toll-free prefix (833, 844, 855, 866, 877, 888). Toll-free prefixes are accepted although the area-code list does not include them: they are sold at the toll-free tier, and searching by prefix is how to find one. 1-800 numbers are not offered yet, so 800 is refused like an unknown code. More than 25 codes returns 422 TOO_MANY_AREA_CODES; any other code returns 422 VALIDATION_ERROR naming it in `details.area_codes`. Each account may search 30 times a minute; beyond that, 429 RATE_LIMITED with Retry-After.\n\nRequires `phone_numbers:r`.","parameters":[{"schema":{"type":"string"},"in":"query","name":"area_codes","required":false,"description":"Comma-separated area codes to filter by (e.g. 416,647): at most 25, each an active Canadian area code or a toll-free prefix (833, 844, 855, 866, 877, 888)"},{"schema":{"type":"integer","minimum":1,"maximum":50,"default":10},"in":"query","name":"limit","required":false}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"phone_numbers:r","responses":{"200":{"description":"Available numbers. A bare array, not wrapped in data.","content":{"application/json":{"schema":{"description":"Available numbers. A bare array, not wrapped in data.","type":"array","items":{"$ref":"#/components/schemas/PhoneNumberSearchResult"}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR (details.area_codes names any code not sold) or TOO_MANY_AREA_CODES.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"RATE_LIMITED: 30 searches a minute per account, or 100 requests per second.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"NUMBER_SEARCH_FAILED: the carrier search failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/phone-numbers":{"post":{"summary":"Provision (purchase) a phone number","tags":["Phone Numbers"],"description":"Provisions a number and debits its first month's rent plus a one-time activation fee, as one purchase (see GET /v1/pricing for both amounts; the activation fee is the same on every tier and is not refunded on release). A balance that cannot cover the total returns 402 INSUFFICIENT_BALANCE before anything is charged or ordered. Accounts are capped on how many phone numbers they may hold at once (counting ACTIVE and SUSPENDED), a per-account limit that falls back to a platform-wide default staff can change at runtime; exceeding it returns 403 NUMBER_LIMIT_REACHED with the limit and current usage in `details`. Request a higher allowance from the dashboard. May also return 409 PURCHASE_IN_PROGRESS when another purchase for this account is already in flight: this is transient, unlike the terminal 409 CONFLICT returned when the number is already owned; retry PURCHASE_IN_PROGRESS after a short delay rather than treating it as a failed purchase.\n\nRequires `phone_numbers:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phone_number"],"properties":{"phone_number":{"type":"string","description":"E.164 number to provision (from available search)"}}}}}},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"phone_numbers:w","responses":{"201":{"description":"The provisioned number.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumber"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"INSUFFICIENT_BALANCE: the balance cannot cover the first month and the activation fee. Nothing was charged or ordered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"NUMBER_LIMIT_REACHED (details carries limit and used), LIVE_KEY_REQUIRED (test keys cannot provision), or ACCOUNT_NOT_VERIFIED (the account has not verified an owner mobile number).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"CONFLICT (the number is already owned) or PURCHASE_IN_PROGRESS (another purchase for this account is in flight; retry shortly).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"NON_CANADIAN_NUMBER or VALIDATION_ERROR.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"INTERNAL_ERROR: the charge failed for a reason other than balance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"TOLLFREE_800_COMING_SOON: 1-800 numbers are not offered yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"PROVISIONING_FAILED: the carrier did not provision the number. The charge is refunded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List account's provisioned phone numbers","tags":["Phone Numbers"],"description":"Requires `phone_numbers:r`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"phone_numbers:r","responses":{"200":{"description":"The account's numbers, excluding released ones, newest first. Not paginated.","content":{"application/json":{"schema":{"description":"The account's numbers, excluding released ones, newest first. Not paginated.","type":"object","additionalProperties":true,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PhoneNumber"}}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/phone-numbers/{id}":{"get":{"summary":"Get a phone number by ID","tags":["Phone Numbers"],"description":"Requires `phone_numbers:r`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"phone_numbers:r","responses":{"200":{"description":"The number, released ones included.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumber"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Release (cancel) a phone number","tags":["Phone Numbers"],"description":"Releases the number at the carrier and answers 204, also when the carrier had already let it go. If the carrier does not accept the release, answers 502 RELEASE_FAILED and the number stays yours, unchanged; try again.\n\nRequires `phone_numbers:d`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"phone_numbers:d","responses":{"204":{"description":"Released, or already released at the carrier."},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED: test keys cannot reach live resources here. Also FORBIDDEN when the key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such number on this account, or it is already released.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"RELEASE_FAILED: the carrier did not accept the release. The number stays yours, unchanged; try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/phone-numbers/allowance-requests":{"post":{"summary":"Request a higher phone number allowance","tags":["Phone Numbers"],"description":"Needs a verified business (BUSINESS_VERIFICATION_REQUIRED until then; see /v1/business-verification). Files a request for HonkIO staff to review. Only one request may be pending at a time. Approval raises the account limit; you are emailed when that happens.\n\nRequires `phone_numbers:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["requested_limit","reason"],"properties":{"requested_limit":{"type":"integer","minimum":2,"maximum":100,"description":"Total numbers you want to be able to hold (not an increment)"},"reason":{"type":"string","minLength":10,"maxLength":1000,"description":"What you need them for. Staff decide on this."}}}}}},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"phone_numbers:w","responses":{"201":{"description":"The request, pending review.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NumberAllowanceRequest"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"BUSINESS_VERIFICATION_REQUIRED: verify your business first (PUT /v1/business-verification, then submit it). Also LIVE_KEY_REQUIRED for a test key, or FORBIDDEN when the key lacks the permission this operation requires.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"ALLOWANCE_REQUEST_PENDING: a request is already pending.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR, or INVALID_ALLOWANCE_REQUEST when requested_limit is not above the current limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"RATE_LIMITED: 3 requests per day per account.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List your phone number allowance requests","tags":["Phone Numbers"],"description":"Requires `phone_numbers:r`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"phone_numbers:r","responses":{"200":{"description":"The account's allowance requests, newest first.","content":{"application/json":{"schema":{"description":"The account's allowance requests, newest first.","type":"object","additionalProperties":true,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/NumberAllowanceRequest"}}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/toll-free-verifications/options":{"get":{"summary":"List toll-free verification options","tags":["Phone Numbers"],"description":"The values an application's use_case, monthly_volume, entity_type and business_address.province take, each with an English and French label, and the verification fee.\n\nRequires `phone_numbers:r`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"phone_numbers:r","responses":{"200":{"description":"The option lists and the fee.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TollFreeVerificationOptions"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/toll-free-verifications":{"post":{"summary":"Create a toll-free verification application","tags":["Phone Numbers"],"description":"Toll-free numbers can send SMS only once verified. This creates a DRAFT application for up to 5 of your toll-free numbers; submit it to pay the verification fee and send it for review. Live keys only.\n\nRequires `phone_numbers:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone_number_ids":{"minItems":1,"maxItems":5,"type":"array","items":{"type":"string","minLength":1,"maxLength":64},"description":"The ids of 1 to 5 of your toll-free numbers (GET /v1/phone-numbers). A number can be on only one open application."},"application":{"type":"object","properties":{"business_name":{"type":"string","minLength":1,"maxLength":255,"description":"The legal name of the business, as registered with the Canada Revenue Agency."},"doing_business_as":{"type":["string","null"],"maxLength":255,"description":"The name the business trades under, when it differs from the legal name."},"entity_type":{"type":"string","enum":["SOLE_PROPRIETOR","PRIVATE_PROFIT","PUBLIC_PROFIT","NON_PROFIT","GOVERNMENT"],"description":"The kind of organization. GET /v1/toll-free-verifications/options lists the values with labels."},"business_registration_number":{"type":"string","maxLength":40,"description":"The CRA business number: 9 digits, optionally followed by a program account such as RC0001. Spaces and hyphens are ignored."},"business_registration_type":{"type":"string","description":"Optional. Always CRA: only Canadian businesses can apply.","enum":["CRA"]},"business_registration_country":{"type":"string","description":"Optional. Always CA.","enum":["CA"]},"business_address":{"type":"object","properties":{"line1":{"type":"string","minLength":1,"maxLength":255},"city":{"type":"string","minLength":1,"maxLength":100},"province":{"type":"string","enum":["AB","BC","MB","NB","NL","NS","NT","NU","ON","PE","QC","SK","YT"],"description":"Two-letter province or territory code."},"postal_code":{"type":"string","maxLength":10,"description":"Canadian postal code, e.g. K1A 0B1."}},"required":["line1","city","province","postal_code"],"additionalProperties":false,"description":"The registered address of the business, in Canada."},"contact":{"type":"object","properties":{"first_name":{"type":"string","minLength":1,"maxLength":100},"last_name":{"type":"string","minLength":1,"maxLength":100},"email":{"type":"string","maxLength":254,"format":"email"},"phone":{"type":"string","maxLength":32,"description":"A North American phone number, e.g. +14165550100."}},"required":["first_name","last_name","email","phone"],"additionalProperties":false,"description":"The person the carrier and HonkIO staff can contact about this application."},"website":{"type":"string","maxLength":2048,"description":"The business website (https). Reviewers check that it matches the business."},"use_case":{"type":"string","enum":["2FA","App Notifications","Appointments","Auctions","Auto Repair Services","Bank Transfers","Billing","Booking Confirmations","Business Updates","COVID-19 Alerts","Career Training","Chatbot","Conversational / Alerts","Courier Services & Deliveries","Emergency Alerts","Events & Planning","Financial Services","Fraud Alerts","Fundraising","General Marketing","General School Updates","HR / Staffing","Healthcare Alerts","Housing Community Updates","Insurance Services","Job Dispatch","Legal Services","Mixed","Motivational Reminders","Notary Notifications","Order Notifications","Political","Public Works","Real Estate Services","Religious Services","Repair and Diagnostics Alerts","Rewards Program","Surveys","System Alerts","Voting Reminders","Waitlist Alerts","Webinar Reminders","Workshop Alerts"],"description":"What the messages are for. GET /v1/toll-free-verifications/options lists the values with labels."},"use_case_summary":{"type":"string","minLength":10,"maxLength":2000,"description":"A few sentences on what you send, to whom, and why they expect it."},"sample_messages":{"minItems":1,"maxItems":5,"type":"array","items":{"type":"string","minLength":1,"maxLength":1000},"description":"1 to 5 messages exactly as you will send them, at most 1000 characters together. At least one must tell the recipient how to opt out (STOP, or ARRET in French)."},"opt_in_workflow":{"type":"string","minLength":10,"maxLength":2000,"description":"How recipients agree to receive your messages: where, how, and what they are told."},"opt_in_image_urls":{"minItems":1,"maxItems":5,"type":"array","items":{"type":"string","maxLength":2048},"description":"1 to 5 https links to screenshots or photos of the opt-in (a web form, a paper form, a checkout box). The carrier requires at least one."},"opt_in_confirmation_message":{"type":"string","minLength":1,"maxLength":1000,"description":"The message a recipient gets right after opting in."},"help_message":{"type":"string","minLength":1,"maxLength":1000,"description":"The reply to HELP: who you are and how to reach you."},"privacy_policy_url":{"type":"string","maxLength":2048,"description":"Your privacy policy (https)."},"terms_url":{"type":["string","null"],"maxLength":2048,"description":"Your terms of service (https), if you have them."},"monthly_volume":{"type":"string","enum":["10","100","1,000","10,000","100,000","250,000","500,000","750,000","1,000,000","5,000,000","10,000,000+"],"description":"The number of messages you expect to send a month. GET /v1/toll-free-verifications/options lists the values with labels."},"age_gated":{"type":"boolean","description":"True when the messages are about age-restricted products such as alcohol or cannabis."},"additional_information":{"type":["string","null"],"maxLength":2000,"description":"Anything else the reviewers should know."}},"required":["business_name","entity_type","business_registration_number","business_address","contact","website","use_case","use_case_summary","sample_messages","opt_in_workflow","opt_in_image_urls","opt_in_confirmation_message","help_message","privacy_policy_url","monthly_volume"],"additionalProperties":false,"description":"The application. Every field is checked when you create it."}},"required":["phone_number_ids","application"],"additionalProperties":false,"description":"The numbers to verify and the application for them."}}},"description":"The numbers to verify and the application for them."},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"phone_numbers:w","responses":{"201":{"description":"The application, in DRAFT.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TollFreeVerification"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"NUMBER_IN_OPEN_VERIFICATION: a number is already on another open application (details.phone_number_id).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR (details names the fields), NOT_A_TOLL_FREE_NUMBER (a number is not one of your toll-free numbers; details.phone_number_id), or LIVE_MODE_REQUIRED for a test key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"RATE_LIMITED: 10 new applications per day per account.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List toll-free verification applications","tags":["Phone Numbers"],"description":"The account's applications, newest first, cancelled ones included. Not paginated.\n\nRequires `phone_numbers:r`.","parameters":[{"schema":{"type":"string","enum":["DRAFT","AWAITING_PAYMENT","IN_REVIEW","CHANGES_REQUESTED","SUBMITTED","APPROVED","REJECTED","DECLINED","CANCELLED"]},"in":"query","name":"status","required":false,"description":"Only applications in this status."}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"phone_numbers:r","responses":{"200":{"description":"The account's applications, newest first.","content":{"application/json":{"schema":{"description":"The account's applications, newest first.","type":"object","additionalProperties":true,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TollFreeVerification"}}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/toll-free-verifications/{id}":{"get":{"summary":"Get a toll-free verification application","tags":["Phone Numbers"],"description":"The application, its numbers, its status and, when staff asked for changes or declined it, their note.\n\nRequires `phone_numbers:r`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"phone_numbers:r","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"The application.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TollFreeVerification"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a toll-free verification application","tags":["Phone Numbers"],"description":"Edits an application in DRAFT, CHANGES_REQUESTED or REJECTED. Its status does not change: submit it again when you are done. Live keys only.\n\nRequires `phone_numbers:m`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone_number_ids":{"minItems":1,"maxItems":5,"type":"array","items":{"type":"string","minLength":1,"maxLength":64},"description":"Replaces the numbers on the application: 1 to 5 of your toll-free numbers."},"application":{"type":"object","properties":{"business_name":{"type":"string","minLength":1,"maxLength":255},"doing_business_as":{"type":["string","null"],"maxLength":255},"entity_type":{"type":"string","enum":["SOLE_PROPRIETOR","PRIVATE_PROFIT","PUBLIC_PROFIT","NON_PROFIT","GOVERNMENT"]},"business_registration_number":{"type":"string","maxLength":40},"business_registration_type":{"type":"string","enum":["CRA"]},"business_registration_country":{"type":"string","enum":["CA"]},"business_address":{"type":"object","properties":{"line1":{"type":"string","minLength":1,"maxLength":255},"city":{"type":"string","minLength":1,"maxLength":100},"province":{"type":"string","enum":["AB","BC","MB","NB","NL","NS","NT","NU","ON","PE","QC","SK","YT"]},"postal_code":{"type":"string","maxLength":10}},"required":["line1","city","province","postal_code"],"additionalProperties":false},"contact":{"type":"object","properties":{"first_name":{"type":"string","minLength":1,"maxLength":100},"last_name":{"type":"string","minLength":1,"maxLength":100},"email":{"type":"string","maxLength":254,"format":"email"},"phone":{"type":"string","maxLength":32}},"required":["first_name","last_name","email","phone"],"additionalProperties":false},"website":{"type":"string","maxLength":2048},"use_case":{"type":"string","enum":["2FA","App Notifications","Appointments","Auctions","Auto Repair Services","Bank Transfers","Billing","Booking Confirmations","Business Updates","COVID-19 Alerts","Career Training","Chatbot","Conversational / Alerts","Courier Services & Deliveries","Emergency Alerts","Events & Planning","Financial Services","Fraud Alerts","Fundraising","General Marketing","General School Updates","HR / Staffing","Healthcare Alerts","Housing Community Updates","Insurance Services","Job Dispatch","Legal Services","Mixed","Motivational Reminders","Notary Notifications","Order Notifications","Political","Public Works","Real Estate Services","Religious Services","Repair and Diagnostics Alerts","Rewards Program","Surveys","System Alerts","Voting Reminders","Waitlist Alerts","Webinar Reminders","Workshop Alerts"]},"use_case_summary":{"type":"string","minLength":10,"maxLength":2000},"sample_messages":{"minItems":1,"maxItems":5,"type":"array","items":{"type":"string","minLength":1,"maxLength":1000}},"opt_in_workflow":{"type":"string","minLength":10,"maxLength":2000},"opt_in_image_urls":{"minItems":1,"maxItems":5,"type":"array","items":{"type":"string","maxLength":2048}},"opt_in_confirmation_message":{"type":"string","minLength":1,"maxLength":1000},"help_message":{"type":"string","minLength":1,"maxLength":1000},"privacy_policy_url":{"type":"string","maxLength":2048},"terms_url":{"type":["string","null"],"maxLength":2048},"monthly_volume":{"type":"string","enum":["10","100","1,000","10,000","100,000","250,000","500,000","750,000","1,000,000","5,000,000","10,000,000+"]},"age_gated":{"type":"boolean"},"additional_information":{"type":["string","null"],"maxLength":2000}},"additionalProperties":false,"description":"Any of the application's fields."}},"additionalProperties":false,"description":"The fields to change. Application fields you send replace the stored ones (an address or contact is replaced whole); the result must still be a complete, valid application."}}},"description":"The fields to change. Application fields you send replace the stored ones (an address or contact is replaced whole); the result must still be a complete, valid application."},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"phone_numbers:m","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"The edited application.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TollFreeVerification"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"VERIFICATION_NOT_EDITABLE (not in DRAFT, CHANGES_REQUESTED or REJECTED) or NUMBER_IN_OPEN_VERIFICATION.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR, NOT_A_TOLL_FREE_NUMBER, or LIVE_MODE_REQUIRED for a test key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Cancel or withdraw a toll-free verification application","tags":["Phone Numbers"],"description":"Cancels an application in DRAFT or AWAITING_PAYMENT, or withdraws one the carrier REJECTED, and frees its numbers for another application. A withdrawn application's fee is not refunded; an application in review or with the carrier cannot be cancelled. Once the payment page has been opened, cancel from the dashboard instead (VERIFICATION_NOT_EDITABLE, reason checkout_started), so the open payment can be closed first. Live keys only.\n\nRequires `phone_numbers:d`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"phone_numbers:d","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"204":{"description":"Cancelled."},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"VERIFICATION_NOT_EDITABLE: only DRAFT and AWAITING_PAYMENT applications can be cancelled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"LIVE_MODE_REQUIRED for a test key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/toll-free-verifications/{id}/submit":{"post":{"summary":"Submit a toll-free verification application","tags":["Phone Numbers"],"description":"From DRAFT: moves the application to AWAITING_PAYMENT and returns payment_url, the dashboard page where the verification fee is paid; once paid it goes to HonkIO staff for review. From CHANGES_REQUESTED or REJECTED: sends the edited application back for review, at no charge. Live keys only (LIVE_MODE_REQUIRED). Needs a verified business with a CRA business number on its profile (BUSINESS_VERIFICATION_REQUIRED, or BUSINESS_NUMBER_REQUIRED when the business as HonkIO verified it has none; see verified_details on /v1/business-verification).\n\nRequires `phone_numbers:w`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"phone_numbers:w","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"The application, AWAITING_PAYMENT with payment_url, or back IN_REVIEW.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TollFreeVerification"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"BUSINESS_VERIFICATION_REQUIRED (verify your business first) or BUSINESS_NUMBER_REQUIRED (your verified business profile has no CRA business number, which toll-free verification needs).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"VERIFICATION_NOT_EDITABLE: only DRAFT, CHANGES_REQUESTED and REJECTED applications can be submitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"LIVE_MODE_REQUIRED for a test key, or NOT_A_TOLL_FREE_NUMBER when a number was released since.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/send-limit":{"get":{"summary":"Your sending limits and current usage","tags":["Accounts"],"description":"The daily cap (a rolling 24 hours), how much of it is used, the per-recipient cap, the probation status that gates \"request a higher volume\", any active pause, and your past requests. The top-level cap and usage are for sends from local numbers; toll_free holds the separate allowance for sends from toll-free numbers, which has no probation. number_queues shows each of your numbers' carrier queue: the parts a minute it sends, the parts waiting now, and when they should have gone out (an estimate).\n\nRequires `account:r`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"account:r","responses":{"200":{"description":"Sending limits, usage and past volume requests.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendLimit"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/send-limit/requests":{"get":{"summary":"List your volume requests","tags":["Accounts"],"description":"Requires `account:r`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"account:r","responses":{"200":{"description":"The account's volume requests, newest first.","content":{"application/json":{"schema":{"description":"The account's volume requests, newest first.","type":"object","additionalProperties":true,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SendLimitRequest"}}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Request a higher daily sending volume","tags":["Accounts"],"description":"Needs a verified business (BUSINESS_VERIFICATION_REQUIRED until then; see /v1/business-verification), and opens once the account has completed its probation period (counted from its first live send). Omit requested_limit to ask for the standard approved volume, or name a target. One request may be pending at a time; you are emailed when it is decided.\n\nRequires `account:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"requested_limit":{"type":"integer","minimum":1,"maximum":1000000,"description":"Messages per day you want (not an increment). Omit for the standard approved volume."},"reason":{"type":"string","description":"What you send and to whom. Staff decide on this. 10 to 1000 characters."}}}}}},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"account:w","responses":{"201":{"description":"The request, pending review.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendLimitRequest"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"BUSINESS_VERIFICATION_REQUIRED (verify your business first), SEND_LIMIT_REQUEST_TOO_EARLY (probation not over; details.eligible_at) or LIVE_KEY_REQUIRED.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"SEND_LIMIT_REQUEST_PENDING: a request is already pending.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"RATE_LIMITED: 3 requests per day per account.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/business-verification":{"get":{"summary":"Get your business verification","tags":["Accounts"],"description":"Your business profile and where its review stands, or null before one is saved. A verified business can request more phone numbers, a higher sending volume, higher top-up limits and toll-free verification.\n\nRequires `account:r`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"account:r","responses":{"200":{"description":"Your business verification profile, or null before one is saved.","content":{"application/json":{"schema":{"description":"Your business verification profile, or null before one is saved.","oneOf":[{"$ref":"#/components/schemas/BusinessVerification"},{"type":"null"}]}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"summary":"Save your business verification profile","tags":["Accounts"],"description":"Creates the profile, or replaces it while it is DRAFT, VERIFIED, CHANGES_REQUESTED or DECLINED (BUSINESS_VERIFICATION_NOT_EDITABLE otherwise). Every field is checked. The status does not change, except that a VERIFIED profile goes back to DRAFT: submit it when it is ready. Changes to a verified business go back to HonkIO for review, and the account stays verified (verified is true) in the meantime, as it was verified: verified_details, not the changed fields, is what counts until HonkIO verifies the changes. A test key can save a profile that was never verified, but changing a verified business needs a live key (LIVE_KEY_REQUIRED).\n\nRequires `account:m`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"business_name":{"type":"string","minLength":1,"maxLength":255,"description":"The legal name: as registered with the CRA or your province, or your own name for a sole proprietorship."},"doing_business_as":{"type":["string","null"],"maxLength":255,"description":"The name the business trades under, when it differs from the legal name."},"entity_type":{"type":"string","enum":["SOLE_PROPRIETOR","PRIVATE_PROFIT","PUBLIC_PROFIT","NON_PROFIT","GOVERNMENT"],"description":"The kind of organization. GET /v1/toll-free-verifications/options lists the values with labels."},"business_registration_number":{"type":["string","null"],"maxLength":40,"description":"The CRA business number: 9 digits, optionally followed by a program account such as RC0001. Spaces and hyphens are ignored. Required unless entity_type is SOLE_PROPRIETOR."},"business_address":{"type":"object","properties":{"line1":{"type":"string","minLength":1,"maxLength":255},"city":{"type":"string","minLength":1,"maxLength":100},"province":{"type":"string","enum":["AB","BC","MB","NB","NL","NS","NT","NU","ON","PE","QC","SK","YT"],"description":"Two-letter province or territory code."},"postal_code":{"type":"string","maxLength":10,"description":"Canadian postal code, e.g. K1A 0B1."},"line2":{"type":["string","null"],"maxLength":255}},"required":["line1","city","province","postal_code"],"additionalProperties":false,"description":"Where the business is registered or operates, in Canada. line2 (suite, unit or floor) is optional."},"website":{"type":"string","maxLength":2048,"description":"An https link that shows the business: its website or, for a sole proprietor, a public profile such as LinkedIn or an app store listing."},"business_description":{"type":"string","minLength":20,"maxLength":500,"description":"What the business does and who it serves, 20 to 500 characters."},"contact":{"type":"object","properties":{"first_name":{"type":"string","minLength":1,"maxLength":100},"last_name":{"type":"string","minLength":1,"maxLength":100},"email":{"type":"string","maxLength":254,"format":"email"},"phone":{"type":"string","maxLength":32,"description":"A North American phone number, e.g. +14165550100."}},"required":["first_name","last_name","email","phone"],"additionalProperties":false,"description":"The person HonkIO staff can contact about the review."}},"required":["business_name","entity_type","business_address","website","business_description","contact"],"additionalProperties":false,"description":"Your business and a contact person."}}},"description":"Your business and a contact person."},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"account:m","responses":{"200":{"description":"The saved profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BusinessVerification"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED: a test key cannot change the profile of a verified business, including one whose changed details are in review. It can save a profile that was never verified. Also FORBIDDEN when the key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"BUSINESS_VERIFICATION_NOT_EDITABLE: the profile is in review or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/business-verification/submit":{"post":{"summary":"Submit your business for verification","tags":["Accounts"],"description":"Sends the saved profile to HonkIO staff for review (IN_REVIEW). They check it against public registries and your website, then verify it, ask for changes or decline it; you are emailed either way. A verified business that changed its details stays verified during the review; declining the changes removes the verification. Live keys only (LIVE_KEY_REQUIRED).\n\nRequires `account:w`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"account:w","responses":{"200":{"description":"The profile, now IN_REVIEW.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BusinessVerification"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED: test keys cannot reach live resources here. Also FORBIDDEN when the key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no profile has been saved yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"BUSINESS_VERIFICATION_NOT_EDITABLE: only a DRAFT, CHANGES_REQUESTED or DECLINED profile can be submitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/account/topup-cap-requests":{"post":{"summary":"Request higher top-up limits","tags":["Accounts"],"description":"Asks HonkIO staff to raise the most your balance may hold (requested_max_balance_cents), the most you may add in any 30 days (requested_max_30d_cents), or both. Each figure you name must be above your current one (GET /v1/accounts/{id}/topup-allowance). Needs a verified business (BUSINESS_VERIFICATION_REQUIRED). One request may be pending at a time; you are emailed when it is decided. Live keys only.\n\nRequires `account:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"requested_max_balance_cents":{"type":"integer","minimum":100,"maximum":99999999,"description":"The balance cap you want, in CAD cents (100 to 99999999). Must be above your current one."},"requested_max_30d_cents":{"type":"integer","minimum":100,"maximum":99999999,"description":"The rolling 30-day top-up cap you want, in CAD cents (100 to 99999999). Must be above your current one."},"reason":{"type":"string","minLength":10,"maxLength":1000,"description":"What the higher limits are for. Staff decide on this. 10 to 1000 characters."}},"required":["reason"],"additionalProperties":false,"description":"The limits you want and why."}}},"description":"The limits you want and why."},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"account:w","responses":{"201":{"description":"The request, pending review.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopupCapRequest"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"BUSINESS_VERIFICATION_REQUIRED: verify your business first (PUT /v1/business-verification, then submit it). Also LIVE_KEY_REQUIRED for a test key, or FORBIDDEN when the key lacks the permission this operation requires.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"TOPUP_CAP_REQUEST_PENDING: a request is already pending.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"RATE_LIMITED: 3 requests per day per account.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List your top-up limit requests","tags":["Accounts"],"description":"Requires `account:r`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"account:r","responses":{"200":{"description":"The account's top-up limit requests, newest first.","content":{"application/json":{"schema":{"description":"The account's top-up limit requests, newest first.","type":"object","additionalProperties":true,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TopupCapRequest"}}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/compliance/consents":{"post":{"summary":"Record CASL consent for a phone number or email address","tags":["Compliance / CASL"],"description":"Records express or implied consent per CASL §6-10. Every SMS needs consent on file for its recipient, transactional or commercial (a verification code through the Verify API needs none); email needs it only for commercial sends. Refused with 409 CONSENT_REVOKED_BY_REVIEW when HonkIO revoked this recipient's consent on review of a bulk upload: such consent cannot be recorded again unless HonkIO lifts the block.\n\nRequires `compliance:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["consent_type"],"properties":{"phone_number":{"type":"string","description":"E.164 phone number of subscriber"},"email_address":{"type":"string","maxLength":254,"description":"Email address of the subscriber (email channel). Email consent is available when the email product launches."},"consent_type":{"type":"string","enum":["express","implied"]},"source_description":{"type":"string","maxLength":1000,"description":"How/where consent was obtained (required for express)"},"source_ip":{"type":"string","maxLength":45,"description":"IP address of subscriber at time of consent"},"source_url":{"type":"string","maxLength":2048,"description":"URL where consent was obtained"},"relationship_type":{"type":"string","maxLength":100,"description":"Business relationship type (required for implied; e.g. \"purchase\")"},"last_transaction_date":{"type":"string","format":"date","description":"Date of last transaction (implied consent: expiry calculated from this)"},"expires_at":{"type":"string","description":"Explicit expiry (ISO 8601 date or date-time) for implied consent. Overrides the expiry derived from last_transaction_date. Not accepted for express consent, which never expires (CASL §10)."}}}}}},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"compliance:w","responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"phone_number":{"type":"string"},"email_address":{"type":"string"},"expires_at":{"type":["null","string"],"description":"When this consent lapses (implied only); null for express consent."}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"CONSENT_REVOKED_BY_REVIEW: HonkIO revoked this recipient's consent on review of a bulk upload, so it cannot be recorded again unless HonkIO lifts the block.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"INVALID_PHONE_NUMBER, EMAIL_INVALID_ADDRESS, or VALIDATION_ERROR (provide exactly one of phone_number or email_address; express consent needs source_description and takes no expires_at; implied consent needs relationship_type).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"EMAIL_COMING_SOON: email consent is available when the email product launches (sent only for an email_address request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List consent records for account's subscribers","tags":["Compliance / CASL"],"description":"Requires `compliance:r`.","parameters":[{"schema":{"type":"string"},"in":"query","name":"phone_number","required":false},{"schema":{"type":"string"},"in":"query","name":"email_address","required":false,"description":"Filter to the email consents for this address. Email consent is available when the email product launches."},{"schema":{"type":"string","enum":["ACTIVE","EXPIRED","REVOKED"]},"in":"query","name":"status","required":false},{"schema":{"type":"string","maxLength":64},"in":"query","name":"consent_batch_id","required":false,"description":"Only the consents recorded by this bulk upload."},{"schema":{"type":"integer","minimum":1,"default":1},"in":"query","name":"page","required":false},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"in":"query","name":"limit","required":false}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"compliance:r","responses":{"200":{"description":"A page of consent records, newest first. No total is returned.","content":{"application/json":{"schema":{"description":"A page of consent records, newest first. No total is returned.","type":"object","additionalProperties":true,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Consent"}}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"EMAIL_COMING_SOON: email consent is available when the email product launches (sent only for an email_address request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/compliance/consents/batch":{"post":{"summary":"Record CASL consent for up to 5,000 phone numbers","tags":["Compliance / CASL"],"description":"Each row is `defaults` with the row's own fields on top. Every row gets its own outcome: recorded, invalid, duplicate_in_file (the number appeared on an earlier row), opted_out (the number has opted out; never re-consented by an upload), revoked_by_review (HonkIO revoked the number's consent on review of an earlier upload; it cannot be recorded again unless HonkIO lifts the block) or already_recorded (an active consent at least as strong exists, so re-sending a file records nothing twice). The upload is kept as a consent batch (GET /v1/compliance/consent-batches). SMS only.\n\nRequires `compliance:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["defaults","rows"],"properties":{"defaults":{"type":"object","required":["consent_type"],"additionalProperties":false,"properties":{"consent_type":{"type":"string","enum":["express","implied"]},"source_description":{"type":"string","maxLength":1000},"source_ip":{"type":"string","maxLength":45},"source_url":{"type":"string","maxLength":2048},"relationship_type":{"type":"string","maxLength":100},"last_transaction_date":{"type":"string","maxLength":40},"expires_at":{"type":["null","string"],"maxLength":40}},"description":"The fields every row starts from."},"rows":{"type":"array","minItems":1,"maxItems":5000,"description":"1 to 5,000 rows; each row's fields override the defaults.","items":{"type":"object","required":["phone_number"],"properties":{"phone_number":{"type":"string","maxLength":64},"consent_type":{"type":"string","maxLength":20,"description":"express or implied; any other value makes that row invalid."},"source_description":{"type":"string","maxLength":1000},"source_ip":{"type":"string","maxLength":45},"source_url":{"type":"string","maxLength":2048},"relationship_type":{"type":"string","maxLength":100},"last_transaction_date":{"type":"string","maxLength":40},"expires_at":{"type":["null","string"],"maxLength":40}}}},"file_name":{"type":"string","maxLength":255,"description":"The uploaded file's name, kept on the batch."},"attested":{"type":"boolean","description":"The uploader confirms these people gave this consent or have this relationship. Recorded on the batch."}}}}}},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"compliance:w","responses":{"200":{"description":"Every row's outcome, in the order sent, and the counts. Partial success is normal: only a request that is wrong as a whole is refused.","content":{"application/json":{"schema":{"description":"Every row's outcome, in the order sent, and the counts. Partial success is normal: only a request that is wrong as a whole is refused.","type":"object","additionalProperties":true,"required":["batch_id","counts","results"],"properties":{"batch_id":{"type":"string","description":"cb_…"},"counts":{"type":"object","additionalProperties":true,"required":["rows","recorded","express","implied","already_recorded","opted_out","revoked_by_review","duplicate_in_file","invalid"],"properties":{"rows":{"type":"integer"},"recorded":{"type":"integer"},"express":{"type":"integer"},"implied":{"type":"integer"},"already_recorded":{"type":"integer"},"opted_out":{"type":"integer"},"revoked_by_review":{"type":"integer"},"duplicate_in_file":{"type":"integer"},"invalid":{"type":"integer"}}},"results":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["row","phone_number","status"],"properties":{"row":{"type":"integer","description":"Index into rows."},"phone_number":{"type":"string","description":"E.164, or the input as sent when it did not parse."},"status":{"type":"string","enum":["recorded","invalid","duplicate_in_file","opted_out","revoked_by_review","already_recorded"]},"reason":{"type":"string","description":"Present on invalid rows (the field and what is wrong with it) and on revoked_by_review rows."},"consent_type":{"type":"string","enum":["express","implied"],"description":"Present on recorded rows."},"expires_at":{"type":["string","null"],"description":"Present on recorded rows; null for express."}}}}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: rows empty or over 5,000, or defaults wrong in themselves (unknown consent_type, an unparseable date, expires_at with express).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/compliance/consent-batches":{"get":{"summary":"List bulk consent uploads","tags":["Compliance / CASL"],"description":"Requires `compliance:r`.","parameters":[{"schema":{"type":"integer","minimum":1,"default":1},"in":"query","name":"page","required":false},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"in":"query","name":"limit","required":false}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"compliance:r","responses":{"200":{"description":"A page of bulk uploads, newest first. No total is returned.","content":{"application/json":{"schema":{"description":"A page of bulk uploads, newest first. No total is returned.","type":"object","additionalProperties":true,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ConsentBatch"}}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/compliance/consent-batches/{id}":{"get":{"summary":"Get a bulk consent upload","tags":["Compliance / CASL"],"description":"Requires `compliance:r`.","parameters":[{"schema":{"type":"string","maxLength":64},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"compliance:r","responses":{"200":{"description":"The upload, with the defaults it was sent with.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentBatch"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/compliance/consents/check":{"get":{"summary":"Check if a phone number or email address has valid CASL consent","tags":["Compliance / CASL"],"description":"A consent this check finds past its expiry is marked EXPIRED as a side effect of the check itself, not only by a background job: this call can write, not just read.\n\nRequires `compliance:r`.","parameters":[{"schema":{"type":"string"},"in":"query","name":"phone_number","required":false},{"schema":{"type":"string"},"in":"query","name":"email_address","required":false,"description":"Check an email address instead of a phone number. Email consent is available when the email product launches."}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"compliance:r","responses":{"200":{"description":"Whether a send to the phone number or email address would pass the CASL gate. Exactly one of phone_number/email_address is present, matching the subject queried. consentType and reason are camelCase and mutually exclusive.","content":{"application/json":{"schema":{"description":"Whether a send to the phone number or email address would pass the CASL gate. Exactly one of phone_number/email_address is present, matching the subject queried. consentType and reason are camelCase and mutually exclusive.","type":"object","additionalProperties":true,"required":["allowed"],"properties":{"phone_number":{"type":"string"},"email_address":{"type":"string"},"allowed":{"type":"boolean"},"consentType":{"type":"string","enum":["EXPRESS","IMPLIED"],"description":"Present when allowed."},"reason":{"type":"string","enum":["OPT_OUT","NO_CONSENT","CONSENT_EXPIRED"],"description":"Present when not allowed."}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"INVALID_PHONE_NUMBER, EMAIL_INVALID_ADDRESS, or VALIDATION_ERROR (provide exactly one of phone_number or email_address).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"EMAIL_COMING_SOON: email consent is available when the email product launches (sent only for an email_address request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/compliance/consents/{phone}":{"delete":{"summary":"Revoke CASL consent for a phone number","tags":["Compliance / CASL"],"description":"Revokes consent records for the given number. Every SMS to it is then refused with NO_CONSENT until consent is recorded again. Opt-outs are separate: an opt-out (POST /opt-outs) blocks sends even when consent is on file, and only the recipient can lift it.\n\nRequires `compliance:m` or `compliance:d` (any of).","parameters":[{"schema":{"type":"string"},"in":"path","name":"phone","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"compliance:md","responses":{"204":{"description":"Every active consent for the number is revoked."},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no active consent for the number.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"INVALID_PHONE_NUMBER.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/compliance/consents/email/{address}":{"delete":{"summary":"Revoke CASL consent for an email address","tags":["Compliance / CASL"],"description":"Revokes the ACTIVE email consents for the address. Commercial email to it is then refused with EMAIL_NO_CONSENT until consent is recorded again; transactional email is unaffected. Opt-outs and suppressions are separate.\n\nRequires `compliance:m` or `compliance:d` (any of).","parameters":[{"schema":{"type":"string"},"in":"path","name":"address","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"compliance:md","responses":{"204":{"description":"Every active consent for the email address is revoked."},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no active consent for the email address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"EMAIL_INVALID_ADDRESS.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"EMAIL_COMING_SOON: email consent is available when the email product launches.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/compliance/opt-outs":{"post":{"summary":"Manually record an opt-out for a phone number","tags":["Compliance / CASL"],"description":"Requires `compliance:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phone_number","from_number"],"properties":{"phone_number":{"type":"string","description":"Number that is opting out (subscriber)"},"from_number":{"type":"string","description":"Your sending number they are opting out from"}}}}}},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"compliance:w","responses":{"201":{"description":"Opted out.","content":{"application/json":{"schema":{"description":"Opted out.","type":"object","additionalProperties":true,"required":["status"],"properties":{"status":{"type":"string","enum":["opted_out"]}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"PHONE_NUMBER_NOT_OWNED: a live key recorded an opt-out from a number the account does not hold.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"INVALID_PHONE_NUMBER or VALIDATION_ERROR.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List opt-out records","tags":["Compliance / CASL"],"description":"Requires `compliance:r`.","parameters":[{"schema":{"type":"string"},"in":"query","name":"phone_number","required":false},{"schema":{"type":"string"},"in":"query","name":"from_number","required":false},{"schema":{"type":"integer","minimum":1,"default":1},"in":"query","name":"page","required":false},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"in":"query","name":"limit","required":false}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"compliance:r","responses":{"200":{"description":"A page of opt-out records, newest first. No total is returned.","content":{"application/json":{"schema":{"description":"A page of opt-out records, newest first. No total is returned.","type":"object","additionalProperties":true,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/OptOut"}}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/compliance/opt-in-confirmations":{"post":{"summary":"Send a double opt-in confirmation from a toll-free number","tags":["Compliance / CASL"],"description":"Texts the recipient a fixed confirmation from your verified toll-free number: \"{brand_name}: reply YES to confirm you want text messages from us. Msg & data rates may apply. Reply STOP to opt out.\" (with `language: \"fr\"`: \"{brand_name} : répondez OUI pour confirmer que vous voulez recevoir nos textos. Des frais de messagerie et de données peuvent s’appliquer. Répondez ARRET pour vous désabonner.\").\n\nCanadian toll-free messaging needs double opt-in, per sending number: until the recipient replies YES, Y, OUI or O to this number, sends to them from it are refused with 451 DOUBLE_OPT_IN_REQUIRED (each of your toll-free numbers asks for its own). The reply confirms the confirmation, records the double opt-in for this number, and fires `consent.double_opt_in_confirmed`. A STOP to this number withdraws it. Verification codes (POST /v1/verify) are exempt.\n\nThe recipient must already have an active consent (the first opt-in). The confirmation is billed as a normal message and expires after 7 days. Sending another from the same number replaces the pending one; at most 3 can be sent to one recipient in 24 hours, from any of your numbers. Live keys only.\n\nRequires `compliance:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["from","to","brand_name"],"properties":{"from":{"type":"string","description":"Your toll-free number (E.164) with an approved toll-free verification."},"to":{"type":"string","description":"The recipient (E.164, Canadian), who must already have an active consent."},"brand_name":{"type":"string","minLength":1,"maxLength":64,"description":"The name the recipient knows you by, shown at the start of the text. Plain text: letters, numbers, spaces and . , ' & ! -, not a link."},"language":{"type":"string","enum":["en","fr"],"default":"en","description":"The language of the confirmation text."}}}}}},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"compliance:w","responses":{"201":{"description":"Sent: the confirmation, PENDING until the recipient replies YES.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OptInConfirmation"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"INSUFFICIENT_BALANCE: the balance cannot cover the send. Also PAYMENT_REQUIRED when a LIVE key is used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"TOLL_FREE_NOT_VERIFIED (from has no approved toll-free verification), ACCOUNT_NOT_VERIFIED, LIVE_KEY_REQUIRED (test keys cannot send one), ALLOW_LIST_BLOCKED, DENY_LIST_BLOCKED or SENDING_PAUSED. Also FORBIDDEN when the key lacks compliance:w.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"PHONE_NUMBER_SUSPENDED: the from number is suspended for unpaid rent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"INVALID_PHONE_NUMBER, NOT_A_TOLL_FREE_NUMBER (from is not one of your toll-free numbers), VALIDATION_ERROR (brand_name must be plain text, not a link), NON_CANADIAN_NUMBER, RESERVED_DESTINATION, UNDELIVERABLE_NUMBER or NOT_A_MOBILE_NUMBER. A refused send costs nothing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"OPT_IN_CONFIRMATION_LIMIT (3 to one recipient in 24 hours), DAILY_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.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"451":{"description":"The recipient has no first opt-in: NO_CONSENT, CONSENT_EXPIRED or OPT_OUT_BLOCKED.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"CARRIER_ERROR: the carrier refused the send. Any charge is refunded and no confirmation is kept.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"CARRIER_UNAVAILABLE, CARRIER_THROTTLED or CARRIER_TIMEOUT: see POST /v1/messages. No confirmation is kept; send again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List double opt-in confirmations","tags":["Compliance / CASL"],"description":"Newest first. A pending confirmation past its 7 days is reported EXPIRED. Confirmations are live only, so a test key lists none.\n\nRequires `compliance:r`.","parameters":[{"schema":{"type":"string"},"in":"query","name":"to","required":false,"description":"Only confirmations sent to this recipient (E.164)."},{"schema":{"type":"string","enum":["PENDING","CONFIRMED","EXPIRED","DECLINED"]},"in":"query","name":"status","required":false},{"schema":{"type":"integer","minimum":1,"default":1},"in":"query","name":"page","required":false},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"in":"query","name":"limit","required":false}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"compliance:r","responses":{"200":{"description":"A page of confirmations, newest first. No total is returned. Empty for a test key.","content":{"application/json":{"schema":{"description":"A page of confirmations, newest first. No total is returned. Empty for a test key.","type":"object","additionalProperties":true,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/OptInConfirmation"}}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"INVALID_PHONE_NUMBER (the to filter) or VALIDATION_ERROR.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/compliance/dncl/check":{"post":{"summary":"Check if a phone number is on the CRTC Do Not Call List","tags":["Compliance / DNCL"],"description":"CRTC DNCL checking is coming soon: every call currently answers 501 DNCL_COMING_SOON.\n\nRequires `compliance:r`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phone_number"],"properties":{"phone_number":{"type":"string"}}}}}},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"compliance:r","responses":{"200":{"description":"The number's DNCL status, once DNCL checking is enabled. Field names are camelCase.","content":{"application/json":{"schema":{"description":"The number's DNCL status, once DNCL checking is enabled. Field names are camelCase.","type":"object","additionalProperties":true,"required":["phoneNumber","isRegistered","checkedAt","fromCache"],"properties":{"phoneNumber":{"type":"string"},"isRegistered":{"type":"boolean"},"checkedAt":{"type":"string","format":"date-time"},"fromCache":{"type":"boolean"}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"DNCL_COMING_SOON: DNCL checking is not enabled yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/compliance/dncl/batch-check":{"post":{"summary":"Check multiple numbers against the CRTC DNCL","tags":["Compliance / DNCL"],"description":"CRTC DNCL checking is coming soon: every call currently answers 501 DNCL_COMING_SOON.\n\nRequires `compliance:r`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phone_numbers"],"properties":{"phone_numbers":{"type":"array","items":{"type":"string"},"maxItems":100}}}}}},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"compliance:r","responses":{"200":{"description":"Each number's DNCL status, once DNCL checking is enabled.","content":{"application/json":{"schema":{"description":"Each number's DNCL status, once DNCL checking is enabled.","type":"object","additionalProperties":true,"required":["results"],"properties":{"results":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["phoneNumber","isRegistered","checkedAt","fromCache"],"properties":{"phoneNumber":{"type":"string"},"isRegistered":{"type":"boolean"},"checkedAt":{"type":"string","format":"date-time"},"fromCache":{"type":"boolean"}}}}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"DNCL_COMING_SOON: DNCL checking is not enabled yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/compliance/erasure":{"post":{"summary":"Right to erasure: delete all personal data for a phone number","tags":["Compliance / PIPEDA"],"description":"Complies with PIPEDA and Quebec Law 25 right to erasure. Scrubs message bodies and event payloads for the number, and deletes its consent records, verifications, contacts, and webhook dead letters. Deletes its contact-list entries too, except on a DENY list: that entry is kept so the number stays blocked. Opt-out records are kept: CASL requires proof an opt-out was honoured, so this call never deletes one. So is a block HonkIO placed on the number by revoking the upload it came from: erasure does not make its consent recordable again.\n\nRequires `compliance:d`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phone_number"],"properties":{"phone_number":{"type":"string"},"reason":{"type":"string","maxLength":1000,"description":"Reason for erasure request (for audit log)"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"header","name":"x-step-up-token","required":false,"description":"A token from POST /v1/auth/step-up with the compliance:d scope. Needed when the calling key holds compliance:m but not compliance:d (e.g. the dashboard session key)."}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"compliance:d","responses":{"200":{"description":"Erased. Opt-out records are kept.","content":{"application/json":{"schema":{"description":"Erased. Opt-out records are kept.","type":"object","additionalProperties":true,"required":["status","phone_number","erased_at","opt_outs_preserved"],"properties":{"status":{"type":"string","enum":["erased"]},"phone_number":{"type":"string"},"erased_at":{"type":"string","format":"date-time"},"opt_outs_preserved":{"type":"boolean","enum":[true]}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN (the key lacks compliance:d and sent no compliance:d step-up token) or LIVE_KEY_REQUIRED (test keys cannot erase).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"INVALID_PHONE_NUMBER, CANNOT_ERASE_OWN_NUMBER or VALIDATION_ERROR.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks":{"post":{"summary":"Register a webhook endpoint","tags":["Webhooks"],"description":"Requires `webhooks:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","events"],"properties":{"url":{"type":"string","format":"uri","maxLength":2048,"description":"HTTPS URL to deliver events to. It must answer 2xx directly: deliveries never follow a redirect, so a 3xx is recorded as a failed attempt."},"events":{"type":"array","items":{"type":"string","enum":["message.queued","message.sending","message.sent","message.delivered","message.failed","message.undelivered","message.received","opt_out.recorded","opt_out.reinstated","consent.double_opt_in_confirmed","account.delivery_warning","account.sending_paused","account.spend_warning","account.inbound_email_capped","account.inbound_sms_capped","account.auto_topup_succeeded","account.auto_topup_paused","email.queued","email.sent","email.delivered","email.delivery_delayed","email.bounced","email.complained","email.rejected","email.failed","email.opened","email.clicked","email.unsubscribed","email.cancelled","email.rescheduled","email.received","email_domain.verified","email_domain.verification_failed","email_domain.receiving_verified","email_domain.receiving_failed","phone_number.suspended","phone_number.released","toll_free_verification.approved","toll_free_verification.rejected","toll_free_verification.changes_requested"]},"minItems":1}}}}}},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"webhooks:w","responses":{"201":{"description":"The endpoint. signing_secret is shown only here: store it to verify X-HonkIO-Signature.","content":{"application/json":{"schema":{"description":"The endpoint. signing_secret is shown only here: store it to verify X-HonkIO-Signature.","type":"object","additionalProperties":true,"required":["id","url","events","signing_secret","active","created_at","notice"],"properties":{"id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"signing_secret":{"type":"string","description":"64 hex characters."},"active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"notice":{"type":"string"}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED: test keys cannot reach live resources here. Also FORBIDDEN when the key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR (not a public HTTPS URL, an unknown event, or an email event while email is off) or WEBHOOK_LIMIT_REACHED (10 endpoints per account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List registered webhooks","tags":["Webhooks"],"description":"Requires `webhooks:r`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"webhooks:r","responses":{"200":{"description":"Every endpoint on the account, newest first. Not paginated.","content":{"application/json":{"schema":{"description":"Every endpoint on the account, newest first. Not paginated.","type":"object","additionalProperties":true,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/{id}":{"get":{"summary":"Get a webhook by ID","tags":["Webhooks"],"description":"Requires `webhooks:r`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"webhooks:r","responses":{"200":{"description":"The endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a webhook","tags":["Webhooks"],"description":"Requires `webhooks:m`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri","maxLength":2048,"pattern":"^https://"},"events":{"type":"array","items":{"type":"string","enum":["message.queued","message.sending","message.sent","message.delivered","message.failed","message.undelivered","message.received","opt_out.recorded","opt_out.reinstated","consent.double_opt_in_confirmed","account.delivery_warning","account.sending_paused","account.spend_warning","account.inbound_email_capped","account.inbound_sms_capped","account.auto_topup_succeeded","account.auto_topup_paused","email.queued","email.sent","email.delivered","email.delivery_delayed","email.bounced","email.complained","email.rejected","email.failed","email.opened","email.clicked","email.unsubscribed","email.cancelled","email.rescheduled","email.received","email_domain.verified","email_domain.verification_failed","email_domain.receiving_verified","email_domain.receiving_failed","phone_number.suspended","phone_number.released","toll_free_verification.approved","toll_free_verification.rejected","toll_free_verification.changes_requested"]},"minItems":1},"active":{"type":"boolean"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"webhooks:m","responses":{"200":{"description":"The updated endpoint.","content":{"application/json":{"schema":{"description":"The updated endpoint.","type":"object","additionalProperties":true,"required":["id","url","events","active"],"properties":{"id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"active":{"type":"boolean"}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED: test keys cannot reach live resources here. Also FORBIDDEN when the key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: not a public HTTPS URL, an unknown event, or an email event while email is off.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a webhook","tags":["Webhooks"],"description":"Requires `webhooks:d`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"webhooks:d","responses":{"204":{"description":"Deleted, with its delivery history and dead letters."},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED: test keys cannot reach live resources here. Also FORBIDDEN when the key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/{id}/deliveries":{"get":{"summary":"List recent delivery attempts for a webhook","tags":["Webhooks"],"description":"Requires `webhooks:r`.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"webhooks:r","responses":{"200":{"description":"Recent delivery attempts, newest first.","content":{"application/json":{"schema":{"description":"Recent delivery attempts, newest first.","type":"object","additionalProperties":true,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/{id}/dead-letters":{"get":{"summary":"List events that failed all delivery attempts (dead-letter queue)","tags":["Webhooks"],"description":"Requires `webhooks:r`.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"boolean","default":false},"in":"query","name":"include_replayed","required":false},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"webhooks:r","responses":{"200":{"description":"Events that failed every attempt, newest first.","content":{"application/json":{"schema":{"description":"Events that failed every attempt, newest first.","type":"object","additionalProperties":true,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDeadLetter"}}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED: test keys cannot reach live resources here. Also FORBIDDEN when the key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/dead-letters/{dlid}/replay":{"post":{"summary":"Replay a dead-lettered event against the original webhook URL","tags":["Webhooks"],"description":"Sends the event again, signed with a fresh timestamp and marked X-HonkIO-Replay: true. 200 { status: \"replayed\" } when your endpoint accepted it; 502 { status: \"failed\", http_status, error_reason } when it did not (the event stays in the dead-letter queue and can be replayed again); 404 NOT_FOUND when the dead letter is not on your account; 409 DEAD_LETTER_ALREADY_REPLAYED when it was already replayed or a replay of it is in progress, or 409 ACCOUNT_SUSPENDED while the account is suspended or closed, in which case nothing is sent.\n\nRequires `webhooks:m`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"dlid","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"webhooks:m","responses":{"200":{"description":"Your endpoint accepted the replay.","content":{"application/json":{"schema":{"description":"Your endpoint accepted the replay.","type":"object","additionalProperties":true,"required":["status","http_status"],"properties":{"status":{"type":"string","enum":["replayed"]},"http_status":{"type":["integer","null"]}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED: test keys cannot reach live resources here. Also FORBIDDEN when the key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"DEAD_LETTER_ALREADY_REPLAYED: already replayed, or a replay is in progress. Also ACCOUNT_SUSPENDED while the account is suspended or closed. Either way, nothing was sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"WEBHOOK_REPLAY_FAILED: your endpoint did not accept the replay. The event stays in the dead-letter queue.","content":{"application/json":{"schema":{"description":"WEBHOOK_REPLAY_FAILED: your endpoint did not accept the replay. The event stays in the dead-letter queue.","allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","additionalProperties":true,"required":["status","http_status","error_reason"],"properties":{"status":{"type":"string","enum":["failed"]},"http_status":{"type":["integer","null"]},"error_reason":{"type":["string","null"]}}}]}}}}}}},"/v1/webhooks/dead-letters/{dlid}":{"delete":{"summary":"Discard a dead-lettered event without replaying it","tags":["Webhooks"],"description":"Requires `webhooks:d`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"dlid","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"webhooks:d","responses":{"204":{"description":"Discarded."},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED: test keys cannot reach live resources here. Also FORBIDDEN when the key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/{id}/reactivate":{"post":{"summary":"Reactivate a webhook that was auto-disabled by delivery failures","tags":["Webhooks"],"description":"Turns delivery back on and starts the endpoint's failure streak over. Events that failed while it was off are not resent: list them with GET /v1/webhooks/{id}/dead-letters and replay each one.\n\nRequires `webhooks:m`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"webhooks:m","responses":{"200":{"description":"The endpoint is active again.","content":{"application/json":{"schema":{"description":"The endpoint is active again.","type":"object","additionalProperties":true,"required":["id","active"],"properties":{"id":{"type":"string"},"active":{"type":"boolean"}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED: test keys cannot reach live resources here. Also FORBIDDEN when the key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the URL no longer resolves to a public endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/{id}/rotate-secret":{"post":{"summary":"Rotate a webhook's signing secret","tags":["Webhooks"],"description":"Generates a new signing secret and returns it once. The previous secret stops signing at once. Every delivery attempt, retries included, is signed with the secret the endpoint holds at that moment, so return a non-2xx for a signature you cannot verify and the retry arrives signed with the new secret.\n\nRequires `webhooks:m`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"webhooks:m","responses":{"200":{"description":"The new secret, shown only here. The previous secret stops signing at once; retries of anything the endpoint rejects during the switch are re-signed with this one.","content":{"application/json":{"schema":{"description":"The new secret, shown only here. The previous secret stops signing at once; retries of anything the endpoint rejects during the switch are re-signed with this one.","type":"object","additionalProperties":true,"required":["id","signing_secret","secret_rotated_at","notice"],"properties":{"id":{"type":"string"},"signing_secret":{"type":"string","description":"64 hex characters."},"secret_rotated_at":{"type":"string","format":"date-time"},"notice":{"type":"string"}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED: test keys cannot reach live resources here. Also FORBIDDEN when the key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such resource on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contacts":{"get":{"summary":"List all contacts","tags":["Contacts"],"description":"Requires `contacts:r`.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"integer","minimum":0,"default":0},"in":"query","name":"offset","required":false},{"schema":{"type":"string"},"in":"query","name":"search","required":false,"description":"Filter by name, phone number or email (case-insensitive, partial match)"}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"contacts:r","responses":{"200":{"description":"A page of contacts, newest first.","content":{"application/json":{"schema":{"description":"A page of contacts, newest first.","allOf":[{"$ref":"#/components/schemas/PaginationOffset"},{"type":"object","additionalProperties":true,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}}}}]}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a contact","tags":["Contacts"],"description":"Requires `contacts:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{"phone_number":{"type":"string","description":"E.164 phone number. Provide phone_number, email, or both."},"email":{"type":"string","format":"email","maxLength":254,"description":"Stored lower-cased. Provide phone_number, email, or both."},"name":{"type":"string","maxLength":200},"notes":{"type":"string","maxLength":1000}}}}}},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"contacts:w","responses":{"201":{"description":"The contact.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"CONTACT_ALREADY_EXISTS: the phone number or the email is already a contact.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR (neither phone_number nor email, or a malformed email) or INVALID_PHONE_NUMBER.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contacts/{id}":{"get":{"summary":"Get a contact","tags":["Contacts"],"description":"Requires `contacts:r`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"contacts:r","responses":{"200":{"description":"The contact.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"CONTACT_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a contact","tags":["Contacts"],"description":"Requires `contacts:m`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone_number":{"type":["string","null"],"description":"E.164, or null to clear (refused with 409 CONTACT_IN_USE while the contact is in a group or list, and with 422 when the contact would be left with neither identifier)."},"email":{"type":["string","null"],"format":"email","maxLength":254},"name":{"type":["string","null"],"maxLength":200},"notes":{"type":["string","null"],"maxLength":1000}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"contacts:m","responses":{"200":{"description":"The updated contact.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED when a test key tries to change something a live key's allow/deny list references. Also FORBIDDEN when the key lacks the permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"CONTACT_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"CONTACT_ALREADY_EXISTS (the new phone number or email is another contact's) or CONTACT_IN_USE (clearing the phone of a contact that is in a group or a list).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR (the contact would be left with neither phone_number nor email) or INVALID_PHONE_NUMBER.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a contact","tags":["Contacts"],"description":"Requires `contacts:d`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"contacts:d","responses":{"204":{"description":"Deleted, with its group memberships and list entries."},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED when a test key tries to change something a live key's allow/deny list references; KEY_FENCED when the change would loosen the calling key's own allow/deny fence. Also FORBIDDEN when the key lacks the permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"CONTACT_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contact-groups":{"get":{"summary":"List contact groups","tags":["Contact Groups"],"description":"Requires `contact_groups:r`.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"integer","minimum":0,"default":0},"in":"query","name":"offset","required":false},{"schema":{"type":"string"},"in":"query","name":"search","required":false,"description":"Filter by group name (case-insensitive, partial match)"}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"contact_groups:r","responses":{"200":{"description":"A page of groups, newest first, each with its members.","content":{"application/json":{"schema":{"description":"A page of groups, newest first, each with its members.","allOf":[{"$ref":"#/components/schemas/PaginationOffset"},{"type":"object","additionalProperties":true,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ContactGroup"}}}}]}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a contact group","tags":["Contact Groups"],"description":"Requires `contact_groups:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":100},"description":{"type":"string","maxLength":500}}}}}},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"contact_groups:w","responses":{"201":{"description":"The group.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactGroup"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"CONTACT_GROUP_ALREADY_EXISTS: the name is taken.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contact-groups/{id}":{"get":{"summary":"Get a contact group with its members","tags":["Contact Groups"],"description":"Requires `contact_groups:r`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"contact_groups:r","responses":{"200":{"description":"The group with its members.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactGroup"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"CONTACT_GROUP_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a contact group","tags":["Contact Groups"],"description":"Requires `contact_groups:m`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":100},"description":{"type":"string","maxLength":500}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"contact_groups:m","responses":{"200":{"description":"The updated group.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactGroup"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED when a test key tries to change something a live key's allow/deny list references. Also FORBIDDEN when the key lacks the permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"CONTACT_GROUP_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"CONTACT_GROUP_ALREADY_EXISTS: the name is taken.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a contact group","tags":["Contact Groups"],"description":"Requires `contact_groups:d`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"contact_groups:d","responses":{"204":{"description":"Deleted. Its contacts are kept."},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED when a test key tries to change something a live key's allow/deny list references; KEY_FENCED when the change would loosen the calling key's own allow/deny fence. Also FORBIDDEN when the key lacks the permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"CONTACT_GROUP_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contact-groups/{id}/members":{"post":{"summary":"Add a member to a contact group","tags":["Contact Groups"],"description":"Provide either `contact_id` (existing Contact record) or `phone_number` (will be auto-created if not exists).\n\nRequires `contact_groups:m`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contact_id":{"type":"string"},"phone_number":{"type":"string"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"contact_groups:m","responses":{"201":{"description":"The member added.","content":{"application/json":{"schema":{"description":"The member added.","type":"object","additionalProperties":true,"required":["id","phone_number","name","added_at"],"properties":{"id":{"type":"string","description":"The contact id."},"phone_number":{"type":"string"},"name":{"type":["string","null"]},"added_at":{"type":"string","format":"date-time"}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED when a test key tries to change something a live key's allow/deny list references; KEY_FENCED when the change would loosen the calling key's own allow/deny fence. Also FORBIDDEN when the key lacks the permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"CONTACT_GROUP_NOT_FOUND or CONTACT_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"CONTACT_GROUP_MEMBER_EXISTS.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR (neither contact_id nor phone_number), INVALID_PHONE_NUMBER or CONTACT_HAS_NO_PHONE (the contact has no phone number).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contact-groups/{id}/members/{contactId}":{"delete":{"summary":"Remove a member from a contact group","tags":["Contact Groups"],"description":"Requires `contact_groups:m`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"contactId","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"contact_groups:m","responses":{"204":{"description":"Removed from the group. The contact is kept."},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED when a test key tries to change something a live key's allow/deny list references; KEY_FENCED when the change would loosen the calling key's own allow/deny fence. Also FORBIDDEN when the key lacks the permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"CONTACT_GROUP_NOT_FOUND or CONTACT_GROUP_MEMBER_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contact-groups/{id}/messages":{"post":{"summary":"Send an SMS to all members of a contact group","tags":["Contact Groups"],"description":"Always answers 207, whether every member sent or every member failed: check each entry in `results` rather than the HTTP status. Each result names the member `to` number. A live broadcast repeated with the same Idempotency-Key within 24 hours answers with the first attempt's stored 207, without running the whole-group checks again or using another daily broadcast slot. A later retry with that key runs the checks again (and can use a slot) but never re-messages a member the first attempt already reached.\n\nRequires `messages:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["from","body"],"properties":{"from":{"type":"string","description":"Sending number (E.164, must be owned by account)"},"body":{"type":"string","maxLength":1600,"description":"Up to 1600 characters and 10 SMS parts. Longer bodies are rejected with 422 MESSAGE_TOO_LONG before any member is messaged."},"skip_consent_check":{"type":"boolean"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string","maxLength":128},"in":"header","name":"idempotency-key","required":false}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"messages:w","responses":{"207":{"description":"Always 207: check each result, not the status. A live broadcast repeated with the same Idempotency-Key within 24 hours answers with the first attempt's stored result.","content":{"application/json":{"schema":{"description":"Always 207: check each result, not the status. A live broadcast repeated with the same Idempotency-Key within 24 hours answers with the first attempt's stored result.","type":"object","additionalProperties":true,"required":["group_id","sent_to","failed","results"],"properties":{"group_id":{"type":"string"},"sent_to":{"type":"integer","description":"Members not failed."},"failed":{"type":"integer"},"results":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["to","status"],"properties":{"to":{"type":"string","description":"The member's number."},"status":{"type":"string","description":"Lowercase: the message status (queued, sent, delivered and so on), or failed."},"message_id":{"type":"string","description":"Absent when the member failed before a message was created."},"error":{"type":"string","description":"An error code: why the member failed, or the message's own error_code."}}}},"estimated_completion_at":{"type":["string","null"],"format":"date-time","description":"An estimate of when the carrier will have sent the last member, from the sending number's queue. Null in test mode. Absent from a stored replay of a broadcast sent before it existed."}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"INSUFFICIENT_BALANCE: the balance cannot cover the send. Also PAYMENT_REQUIRED when a LIVE key is used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN (skip_consent_check on a live key, or the key lacks messages:w), ACCOUNT_NOT_VERIFIED, SENDING_PAUSED, or TOLL_FREE_NOT_VERIFIED (a toll-free from number without an approved toll-free verification; test keys too). A live member without a confirmed double opt-in fails alone in the results with DOUBLE_OPT_IN_REQUIRED.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"CONTACT_GROUP_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR, BROADCAST_TOO_LARGE, MESSAGE_TOO_LONG or LINK_SHORTENER_BLOCKED.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"BROADCAST_LIMIT_REACHED, DAILY_LIMIT_REACHED, FANOUT_LIMIT_REACHED, NUMBER_QUEUE_FULL (the broadcast's message parts would overfill the sending number's carrier queue; Retry-After is set when waiting would make room) or RECIPIENT_RATE_LIMITED, or RATE_LIMITED at 100 requests per second.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/lists":{"get":{"summary":"List all contact lists","tags":["Lists"],"description":"Requires `lists:r`.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"integer","minimum":0,"default":0},"in":"query","name":"offset","required":false}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"lists:r","responses":{"200":{"description":"A page of lists, newest first, each with its entries.","content":{"application/json":{"schema":{"description":"A page of lists, newest first, each with its entries.","allOf":[{"$ref":"#/components/schemas/PaginationOffset"},{"type":"object","additionalProperties":true,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ContactList"}}}}]}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a contact list","tags":["Lists"],"description":"Requires `lists:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":100},"description":{"type":"string","maxLength":500}}}}}},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"lists:w","responses":{"201":{"description":"The list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactList"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"CONTACT_LIST_ALREADY_EXISTS: the name is taken.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/lists/{id}":{"get":{"summary":"Get a contact list with entries","tags":["Lists"],"description":"Requires `lists:r`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"lists:r","responses":{"200":{"description":"The list with its entries.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactList"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"CONTACT_LIST_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a contact list","tags":["Lists"],"description":"Requires `lists:m`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":100},"description":{"type":"string","maxLength":500}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"lists:m","responses":{"200":{"description":"The updated list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactList"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED when a test key tries to change something a live key's allow/deny list references. Also FORBIDDEN when the key lacks the permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"CONTACT_LIST_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"CONTACT_LIST_ALREADY_EXISTS: the name is taken.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a contact list","tags":["Lists"],"description":"Requires `lists:d`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"lists:d","responses":{"204":{"description":"Deleted. A key it was assigned to loses that restriction."},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED when a test key tries to change something a live key's allow/deny list references; KEY_FENCED when the change would loosen the calling key's own allow/deny fence. Also FORBIDDEN when the key lacks the permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"CONTACT_LIST_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/lists/{id}/entries":{"post":{"summary":"Add an entry to a contact list","tags":["Lists"],"description":"Provide exactly one of: `phone_number` (direct E.164), `contact_id` (Contact record), or `contact_group_id` (ContactGroup: expands to all members at send time).\n\nRequires `lists:m`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone_number":{"type":"string"},"contact_id":{"type":"string"},"contact_group_id":{"type":"string"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"lists:m","responses":{"201":{"description":"The entry.","content":{"application/json":{"schema":{"description":"The entry.","type":"object","additionalProperties":true,"required":["id","phone_number","contact_id","contact_group_id","added_at"],"properties":{"id":{"type":"string"},"phone_number":{"type":["string","null"]},"contact_id":{"type":["string","null"]},"contact_group_id":{"type":["string","null"]},"added_at":{"type":"string","format":"date-time"}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED when a test key tries to change something a live key's allow/deny list references; KEY_FENCED when the change would loosen the calling key's own allow/deny fence. Also FORBIDDEN when the key lacks the permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"CONTACT_LIST_NOT_FOUND, CONTACT_NOT_FOUND or CONTACT_GROUP_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"CONTACT_LIST_ENTRY_EXISTS.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"CONTACT_LIST_ENTRY_INVALID (not exactly one of phone_number, contact_id and contact_group_id), INVALID_PHONE_NUMBER or CONTACT_HAS_NO_PHONE.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/lists/{id}/entries/{entryId}":{"delete":{"summary":"Remove an entry from a contact list","tags":["Lists"],"description":"Requires `lists:m`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"entryId","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"lists:m","responses":{"204":{"description":"Removed."},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED when a test key tries to change something a live key's allow/deny list references; KEY_FENCED when the change would loosen the calling key's own allow/deny fence. Also FORBIDDEN when the key lacks the permission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"CONTACT_LIST_NOT_FOUND or CONTACT_LIST_ENTRY_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/emails/received/address":{"get":{"summary":"Your managed inbound address","tags":["Emails"],"description":"Requires `emails:r`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"emails:r","responses":{"200":{"description":"The account's managed inbound address (anything@… reaches it), minted on first call and stable after. enabled is the managed-address switch: mail sent to it while off is discarded, uncharged.","content":{"application/json":{"schema":{"description":"The account's managed inbound address (anything@… reaches it), minted on first call and stable after. enabled is the managed-address switch: mail sent to it while off is discarded, uncharged.","type":"object","additionalProperties":true,"required":["domain","example","livemode","enabled"],"properties":{"domain":{"type":"string"},"example":{"type":"string"},"livemode":{"type":"boolean"},"enabled":{"type":"boolean"}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"ACCOUNT_NOT_VERIFIED: the live managed address needs a verified owner mobile number. A test key is never refused; it always mints and returns the test address. Also FORBIDDEN when the key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Turn your managed inbound address on or off (live keys only)","tags":["Emails"],"description":"Requires `emails:m`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean","description":"False stops accepting mail at the managed address; true accepts it again. Your own receiving domains are not affected."}},"required":["enabled"],"description":"Turns the account's live managed inbound address on or off."}}},"description":"Turns the account's live managed inbound address on or off."},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"emails:m","responses":{"200":{"description":"The same address object GET returns, with enabled set to the requested value. The flag is account-wide: turning it off stops the live managed address receiving, so only a live key may change it.","content":{"application/json":{"schema":{"description":"The same address object GET returns, with enabled set to the requested value. The flag is account-wide: turning it off stops the live managed address receiving, so only a live key may change it.","type":"object","additionalProperties":true,"required":["domain","example","livemode","enabled"],"properties":{"domain":{"type":"string"},"example":{"type":"string"},"livemode":{"type":"boolean"},"enabled":{"type":"boolean"}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED: the switch is account-wide and turns the live address off, so a test key cannot change it (GET still reports it). Also ACCOUNT_NOT_VERIFIED (needs a verified owner mobile number) or FORBIDDEN when the key lacks emails:m.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/emails/received":{"get":{"summary":"List received emails","tags":["Emails"],"description":"Requires `emails:r`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"emails:r","responses":{"200":{"description":"A page of received emails, newest first.","content":{"application/json":{"schema":{"description":"A page of received emails, newest first.","type":"object","additionalProperties":true,"required":["data","has_more","next_cursor"],"properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["id","from","from_name","to","cc","subject","message_id","in_reply_to","received_at","status","reject_reason","verdicts","attachments_count","size_bytes","charge_millicents","domain_id","livemode"],"properties":{"id":{"type":"string"},"from":{"type":"string"},"from_name":{"type":["string","null"]},"to":{"type":"array","items":{"type":"string"}},"cc":{"type":"array","items":{"type":"string"}},"subject":{"type":"string"},"message_id":{"type":["string","null"]},"in_reply_to":{"type":["string","null"]},"received_at":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["received","rejected"]},"reject_reason":{"type":["string","null"],"description":"Why a rejected message was stored headers only and uncharged: virus, daily_cap (the daily message cap), daily_mb_cap (the daily cap on billable MB), or insufficient_balance (the balance could not cover its charge). Null for a received message."},"verdicts":{"type":"object","additionalProperties":true,"required":["spf","dkim","dmarc","spam","virus"],"properties":{"spf":{"type":"string"},"dkim":{"type":"string"},"dmarc":{"type":"string"},"spam":{"type":"string"},"virus":{"type":"string"}}},"attachments_count":{"type":"integer"},"size_bytes":{"type":"integer"},"charge_millicents":{"type":"integer"},"domain_id":{"type":["string","null"]},"livemode":{"type":"boolean"}}}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/emails/received/simulate":{"post":{"summary":"Simulate a received email (test keys only)","tags":["Emails"],"description":"Requires `emails:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"from":{"type":"string","format":"email","description":"The sender's address."},"to":{"minItems":1,"maxItems":10,"type":"array","items":{"type":"string","format":"email"},"description":"Up to 10 recipients, each your test managed address or an address at one of your test mode domains.","example":["support@yourdomain.ca"]},"cc":{"maxItems":10,"type":"array","items":{"type":"string","format":"email"},"description":"Up to 10 more recipients, with the same rule as to."},"subject":{"default":"","type":"string","maxLength":998,"description":"Up to 998 characters."},"text":{"type":"string","maxLength":1000000,"description":"The plain text body."},"html":{"type":"string","maxLength":2000000,"description":"The HTML body."},"attachments":{"maxItems":10,"type":"array","items":{"type":"object","properties":{"filename":{"type":"string","minLength":1,"maxLength":255,"description":"The file name."},"content_type":{"type":"string","minLength":1,"maxLength":255,"description":"The MIME type, such as application/pdf."},"content":{"type":"string","description":"The file, base64 encoded."},"content_id":{"type":"string","maxLength":255,"description":"A Content-ID, so html can reference the file as cid:."},"inline":{"type":"boolean","description":"Mark the file inline rather than attached."}},"required":["filename","content_type","content"]},"description":"Up to 10 files, 10 MB in total."},"verdicts":{"type":"object","properties":{"spf":{"type":"string","enum":["PASS","FAIL","GRAY","PROCESSING_FAILED"],"description":"The verdict to record. Left out, it is PASS."},"dkim":{"type":"string","enum":["PASS","FAIL","GRAY","PROCESSING_FAILED"],"description":"The verdict to record. Left out, it is PASS."},"dmarc":{"type":"string","enum":["PASS","FAIL","GRAY","PROCESSING_FAILED"],"description":"The verdict to record. Left out, it is PASS."},"spam":{"type":"string","enum":["PASS","FAIL","GRAY","PROCESSING_FAILED"],"description":"The verdict to record. Left out, it is PASS."},"virus":{"type":"string","enum":["PASS","FAIL","GRAY","PROCESSING_FAILED"],"description":"The verdict to record. Left out, it is PASS."}},"description":"The spam, virus and authentication verdicts to record. virus FAIL stores the message as rejected, as real mail would be."},"in_reply_to":{"type":"string","maxLength":998,"description":"The Message-ID this message replies to, for threading."},"headers":{"maxItems":20,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The header name."},"value":{"type":"string","description":"The header value."}},"required":["name","value"]},"description":"Up to 20 extra headers."}},"required":["from","to"],"description":"A message to receive as if it had arrived by email, run through the same processing, record and email.received webhook as real mail."}}},"description":"A message to receive as if it had arrived by email, run through the same processing, record and email.received webhook as real mail."},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"emails:w","responses":{"201":{"description":"The simulated message, in the same shape GET /v1/emails/received/:id answers with. livemode is always false.","content":{"application/json":{"schema":{"description":"The simulated message, in the same shape GET /v1/emails/received/:id answers with. livemode is always false.","allOf":[{"type":"object","additionalProperties":true,"required":["id","from","from_name","to","cc","subject","message_id","in_reply_to","received_at","status","reject_reason","verdicts","attachments_count","size_bytes","charge_millicents","domain_id","livemode"],"properties":{"id":{"type":"string"},"from":{"type":"string"},"from_name":{"type":["string","null"]},"to":{"type":"array","items":{"type":"string"}},"cc":{"type":"array","items":{"type":"string"}},"subject":{"type":"string"},"message_id":{"type":["string","null"]},"in_reply_to":{"type":["string","null"]},"received_at":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["received","rejected"]},"reject_reason":{"type":["string","null"],"description":"Why a rejected message was stored headers only and uncharged: virus, daily_cap (the daily message cap), daily_mb_cap (the daily cap on billable MB), or insufficient_balance (the balance could not cover its charge). Null for a received message."},"verdicts":{"type":"object","additionalProperties":true,"required":["spf","dkim","dmarc","spam","virus"],"properties":{"spf":{"type":"string"},"dkim":{"type":"string"},"dmarc":{"type":"string"},"spam":{"type":"string"},"virus":{"type":"string"}}},"attachments_count":{"type":"integer"},"size_bytes":{"type":"integer"},"charge_millicents":{"type":"integer"},"domain_id":{"type":["string","null"]},"livemode":{"type":"boolean"}}},{"type":"object","additionalProperties":true,"required":["text","html","html_format","remote_images","headers","references","reply_to","envelope_recipients","body_purged","body_truncated","raw_available","parse_failed","attachments_bytes","attachments"],"properties":{"text":{"type":["string","null"]},"html":{"type":["string","null"]},"html_format":{"type":"string","enum":["cid","links","sanitized"]},"remote_images":{"type":["integer","null"],"description":"Remote images parked in data-remote-src by html_format=sanitized; null for the other formats."},"headers":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["name","value"],"properties":{"name":{"type":"string"},"value":{"type":"string"}}}},"references":{"type":"array","items":{"type":"string"}},"reply_to":{"type":"array","items":{"type":"string"}},"envelope_recipients":{"type":"array","items":{"type":"string"}},"body_purged":{"type":"boolean"},"body_truncated":{"type":"boolean","description":"text or html was cut at 2 MB (UTF-8 bytes) when stored; the raw message keeps it whole."},"raw_available":{"type":"boolean"},"parse_failed":{"type":"boolean"},"attachments_bytes":{"type":"integer","description":"Total size of the attachments, in bytes."},"attachments":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["id","filename","content_type","content_id","inline","size_bytes","expires_at"],"properties":{"id":{"type":"string"},"filename":{"type":"string"},"content_type":{"type":"string"},"content_id":{"type":["string","null"]},"inline":{"type":"boolean"},"size_bytes":{"type":"integer"},"expires_at":{"type":"string","format":"date-time"}}}}}}]}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"TEST_KEY_REQUIRED: this endpoint only accepts test keys.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR (bad input, or attachments over 10 MB total) or SIMULATE_RECIPIENT_NOT_OWNED (a to/cc address is neither the account's test managed address nor one of its own test-mode domains).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"TEST_EMAIL_QUOTA_EXCEEDED: the account has simulated 200 received emails in the last 24 hours, counted apart from test sends (details carry direction inbound, limit, used, requested and window_hours). Or RATE_LIMITED at 100 requests per second.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/emails/received/{id}":{"get":{"summary":"Get a received email","tags":["Emails"],"description":"Requires `emails:r`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"emails:r","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"The received email. html is rewritten to point cid: images at the attachment route unless html_format=cid asks for the original; html_format=sanitized also strips scripts and unsafe markup and parks remote images in data-remote-src, counted in remote_images.","content":{"application/json":{"schema":{"description":"The received email. html is rewritten to point cid: images at the attachment route unless html_format=cid asks for the original; html_format=sanitized also strips scripts and unsafe markup and parks remote images in data-remote-src, counted in remote_images.","allOf":[{"type":"object","additionalProperties":true,"required":["id","from","from_name","to","cc","subject","message_id","in_reply_to","received_at","status","reject_reason","verdicts","attachments_count","size_bytes","charge_millicents","domain_id","livemode"],"properties":{"id":{"type":"string"},"from":{"type":"string"},"from_name":{"type":["string","null"]},"to":{"type":"array","items":{"type":"string"}},"cc":{"type":"array","items":{"type":"string"}},"subject":{"type":"string"},"message_id":{"type":["string","null"]},"in_reply_to":{"type":["string","null"]},"received_at":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["received","rejected"]},"reject_reason":{"type":["string","null"],"description":"Why a rejected message was stored headers only and uncharged: virus, daily_cap (the daily message cap), daily_mb_cap (the daily cap on billable MB), or insufficient_balance (the balance could not cover its charge). Null for a received message."},"verdicts":{"type":"object","additionalProperties":true,"required":["spf","dkim","dmarc","spam","virus"],"properties":{"spf":{"type":"string"},"dkim":{"type":"string"},"dmarc":{"type":"string"},"spam":{"type":"string"},"virus":{"type":"string"}}},"attachments_count":{"type":"integer"},"size_bytes":{"type":"integer"},"charge_millicents":{"type":"integer"},"domain_id":{"type":["string","null"]},"livemode":{"type":"boolean"}}},{"type":"object","additionalProperties":true,"required":["text","html","html_format","remote_images","headers","references","reply_to","envelope_recipients","body_purged","body_truncated","raw_available","parse_failed","attachments_bytes","attachments"],"properties":{"text":{"type":["string","null"]},"html":{"type":["string","null"]},"html_format":{"type":"string","enum":["cid","links","sanitized"]},"remote_images":{"type":["integer","null"],"description":"Remote images parked in data-remote-src by html_format=sanitized; null for the other formats."},"headers":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["name","value"],"properties":{"name":{"type":"string"},"value":{"type":"string"}}}},"references":{"type":"array","items":{"type":"string"}},"reply_to":{"type":"array","items":{"type":"string"}},"envelope_recipients":{"type":"array","items":{"type":"string"}},"body_purged":{"type":"boolean"},"body_truncated":{"type":"boolean","description":"text or html was cut at 2 MB (UTF-8 bytes) when stored; the raw message keeps it whole."},"raw_available":{"type":"boolean"},"parse_failed":{"type":"boolean"},"attachments_bytes":{"type":"integer","description":"Total size of the attachments, in bytes."},"attachments":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["id","filename","content_type","content_id","inline","size_bytes","expires_at"],"properties":{"id":{"type":"string"},"filename":{"type":"string"},"content_type":{"type":"string"},"content_id":{"type":["string","null"]},"inline":{"type":"boolean"},"size_bytes":{"type":"integer"},"expires_at":{"type":"string","format":"date-time"}}}}}}]}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"RECEIVED_EMAIL_NOT_FOUND: no received email with that id on this account. A test key finds only messages received on the test managed address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: html_format is not cid, links or sanitized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/emails/received/{id}/raw":{"get":{"summary":"Download the raw MIME of a received email","tags":["Emails"],"description":"Requires `emails:r`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"emails:r","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"The raw MIME message, exactly as received.","content":{"message/rfc822":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"RECEIVED_EMAIL_NOT_FOUND: no received email with that id on this account. A test key finds only messages received on the test managed address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"ATTACHMENT_EXPIRED: the raw message or attachment is no longer stored (40-day retention).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/emails/received/{id}/attachments/{attachmentId}":{"get":{"summary":"Download a received attachment","tags":["Emails"],"description":"Requires `emails:r`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"emails:r","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"attachmentId","required":true}],"responses":{"200":{"description":"The attachment bytes, with Content-Disposition set to the original filename.","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"RECEIVED_EMAIL_NOT_FOUND: no received email with that id on this account. A test key finds only messages received on the test managed address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"ATTACHMENT_EXPIRED: the raw message or attachment is no longer stored (40-day retention).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/emails":{"post":{"summary":"Send an email","tags":["Emails"],"description":"Requires `emails:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"from":{"type":"string","minLength":1,"description":"Sender, optionally with a name. Its domain must be a verified sending domain on your account. Optional when the template sets one.","example":"Acme <hello@yourdomain.ca>"},"to":{"anyOf":[{"type":"string","format":"email"},{"type":"array","items":{"type":"string","format":"email"}}],"description":"A string or an array; at most 50 recipients across to, cc and bcc.","example":"sam@example.com"},"cc":{"anyOf":[{"type":"string","format":"email"},{"type":"array","items":{"type":"string","format":"email"}}],"description":"A string or an array."},"bcc":{"anyOf":[{"type":"string","format":"email"},{"type":"array","items":{"type":"string","format":"email"}}],"description":"A string or an array."},"reply_to":{"anyOf":[{"type":"string","format":"email"},{"type":"array","items":{"type":"string","format":"email"}}],"description":"A string or an array."},"subject":{"type":"string","minLength":1,"maxLength":998,"description":"Up to 998 characters. Leave out subject, html and text when you send a template."},"html":{"type":"string","description":"The HTML body: at most 2 MB, before and after variables are substituted. Give html, text or both, unless you send a template."},"text":{"type":"string","description":"The plain text body: at most 2 MB, before and after variables are substituted. Give html, text or both, unless you send a template."},"template":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":200,"description":"The template's id or alias. A live key sends its published version; a test key sends its draft."},"variables":{"default":{},"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"}]},"description":"Values for the template's variables, by key."}},"required":["id"],"additionalProperties":false,"description":"Send a stored template instead of subject, html and text: its id or alias, and values for its variables."},"variables":{"default":{},"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"}]},"description":"Values to substitute into placeholders in subject, html and text. Ignored when template is set; a template's own variables go in template.variables instead."},"headers":{"default":{},"type":"object","additionalProperties":{"type":"string"},"description":"Extra email headers, sent as given. Headers HonkIO sets itself (To, From, Subject, Content-Type, List-Unsubscribe and the like) and any name starting with ARC- or X-SES- are refused with EMAIL_HEADER_INVALID."},"attachments":{"default":[],"maxItems":10,"type":"array","items":{"type":"object","properties":{"filename":{"type":"string","minLength":1,"maxLength":255,"description":"The file name the recipient sees."},"content":{"type":"string","description":"The file, base64 encoded. Give exactly one of content, content_base64 or path."},"content_base64":{"type":"string","description":"The file, base64 encoded (the same as content)."},"path":{"type":"string","maxLength":2048,"format":"uri","description":"An HTTPS URL HonkIO fetches the file from."},"content_type":{"type":"string","maxLength":255,"pattern":"^[\\w.+-]+\\/[\\w.+-]+$","description":"The MIME type, such as application/pdf. Inferred when left out."},"content_id":{"type":"string","pattern":"^[A-Za-z0-9._@-]{1,100}$","description":"Embeds the file inline: reference it from html as cid: followed by this id."}},"required":["filename"]},"description":"Up to 10 files and 25 MB in total. The first 2 MB per recipient are included; each started MB above that is charged per recipient (GET /v1/pricing). Executable and script types are refused."},"tags":{"default":[],"maxItems":10,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,256}$"},"value":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,256}$"}},"required":["name","value"]},"description":"Up to 10 name and value pairs, each part 1 to 256 letters, digits, underscores or hyphens. Names starting with honkio_ are reserved. Tags are kept after the subject and body are purged, so keep personal information out of them."},"is_commercial":{"default":false,"type":"boolean","description":"False by default. True marks a commercial message under CASL: it goes to exactly one recipient, needs consent on file for that address, and carries an unsubscribe footer and header."},"scheduled_at":{"default":null,"type":["string","null"],"format":"date-time","description":"Send later: an ISO 8601 date-time with an offset, 1 minute to 30 days ahead."},"tracking":{"default":{},"type":"object","properties":{"opens":{"type":"boolean","description":"Track opens with a pixel."},"clicks":{"type":"boolean","description":"Track clicks by rewriting links."}},"description":"Open and click tracking for this email, overriding the sending domain's setting."},"disable_unsubscribe_footer":{"default":false,"type":"boolean","description":"Leave out the unsubscribe footer HonkIO adds to a commercial email. The one click unsubscribe header is still sent: put your own unsubscribe link in the body."}},"required":["to"],"description":"One email: a subject with html, text or both, or a template."}}},"description":"One email: a subject with html, text or both, or a template."},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"emails:w","responses":{"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN (the key lacks emails:w), EMAIL_DOMAIN_NOT_ALLOWED (this key may not send from that domain) or SENDING_PAUSED (live email is paused for this account after a high bounce or complaint rate; details carry paused_until, reason and channel: \"email\"). Test keys are never paused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"TEST_EMAIL_QUOTA_EXCEEDED: the account's test keys have created 200 emails in the last 24 hours (details carry limit, used, requested and window_hours); live keys have no such quota. Or RATE_LIMITED at 100 requests per second.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List emails","tags":["Emails"],"description":"Requires `emails:r`.","parameters":[{"schema":{"type":"string"},"in":"query","name":"limit","required":false,"description":"1 to 100. Default 50."},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"next_cursor from the previous page."},{"schema":{"type":"string"},"in":"query","name":"status","required":false,"description":"One of queued, scheduled, sending, sent, delivered, bounced, complained, rejected, failed, cancelled."},{"schema":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"in":"query","name":"tag","required":false,"description":"name:value. Repeat to require several; all must match."},{"schema":{"type":"string"},"in":"query","name":"to","required":false,"description":"A full address in the email's to list (not cc or bcc), matched exactly whatever its case."},{"schema":{"type":"string"},"in":"query","name":"from","required":false,"description":"The full sender address, matched exactly whatever its case."},{"schema":{"type":"string"},"in":"query","name":"since","required":false,"description":"ISO 8601 date-time with an offset: created_at at or after this moment."},{"schema":{"type":"string"},"in":"query","name":"until","required":false,"description":"ISO 8601 date-time with an offset: created_at at or before this moment."},{"schema":{"type":"string"},"in":"query","name":"domain_id","required":false,"description":"The sending domain's id (GET /v1/email-domains)."}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"emails:r","responses":{"200":{"description":"A page of sent emails in the calling key's mode (a live key never lists a test send, nor a test key a live one), newest first.","content":{"application/json":{"schema":{"description":"A page of sent emails in the calling key's mode (a live key never lists a test send, nor a test key a live one), newest first.","type":"object","additionalProperties":true,"required":["data","has_more","next_cursor"],"properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["id","from","to","subject","body_purged_at","status","ses_message_id","is_commercial","attachments_count","livemode","tags","created_at"],"properties":{"id":{"type":"string"},"from":{"type":"string"},"to":{"type":"array","items":{"type":"string"}},"subject":{"type":"string"},"body_purged_at":{"type":["string","null"],"format":"date-time","description":"Set once retention purged the subject and body."},"status":{"type":"string","enum":["queued","scheduled","sending","sent","delivered","bounced","complained","rejected","failed","cancelled"]},"ses_message_id":{"type":["string","null"]},"is_commercial":{"type":"boolean"},"attachments_count":{"type":"integer"},"livemode":{"type":"boolean"},"tags":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["name","value"],"properties":{"name":{"type":"string"},"value":{"type":"string"}}}},"created_at":{"type":"string","format":"date-time"}}}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/emails/{id}":{"get":{"summary":"Get an email by id","tags":["Emails"],"description":"Requires `emails:r`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"emails:r","responses":{"200":{"description":"The sent email, with its event timeline.","content":{"application/json":{"schema":{"description":"The sent email, with its event timeline.","allOf":[{"type":"object","additionalProperties":true,"required":["id","from","to","subject","body_purged_at","status","ses_message_id","is_commercial","attachments_count","livemode","tags","created_at"],"properties":{"id":{"type":"string"},"from":{"type":"string"},"to":{"type":"array","items":{"type":"string"}},"subject":{"type":"string"},"body_purged_at":{"type":["string","null"],"format":"date-time","description":"Set once retention purged the subject and body."},"status":{"type":"string","enum":["queued","scheduled","sending","sent","delivered","bounced","complained","rejected","failed","cancelled"]},"ses_message_id":{"type":["string","null"]},"is_commercial":{"type":"boolean"},"attachments_count":{"type":"integer"},"livemode":{"type":"boolean"},"tags":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["name","value"],"properties":{"name":{"type":"string"},"value":{"type":"string"}}}},"created_at":{"type":"string","format":"date-time"}}},{"type":"object","additionalProperties":true,"required":["from_name","cc","bcc","html","text","template","attachments","cost_cents","charge_millicents","attachment_bytes","scheduled_at","updated_at","events"],"properties":{"from_name":{"type":["string","null"]},"cc":{"type":"array","items":{"type":"string"}},"bcc":{"type":"array","items":{"type":"string"}},"html":{"type":["string","null"]},"text":{"type":["string","null"]},"template":{"anyOf":[{"type":"null"},{"type":"object","additionalProperties":true,"required":["id","version"],"properties":{"id":{"type":"string"},"version":{"type":["integer","null"],"description":"The published version a live send rendered. Null for a test send, which renders the draft."}}}]},"attachments":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["filename","content_type","content_id","size_bytes"],"properties":{"filename":{"type":"string"},"content_type":{"type":"string"},"content_id":{"type":["string","null"]},"size_bytes":{"type":"integer"}}}},"cost_cents":{"type":"integer","description":"Whole cents this send debited from the balance; often 0 for one sub-cent email."},"charge_millicents":{"type":"integer","description":"The exact price the send consumed, in CAD millicents."},"attachment_bytes":{"type":"integer","description":"Total attachment bytes the charge was computed on."},"scheduled_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"events":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["type","occurred_at","payload"],"properties":{"type":{"type":"string"},"occurred_at":{"type":"string","format":"date-time"},"payload":{}}}}}}]}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND (details.resource \"email\"): no email with that id on this account in the calling key's mode. A test key finds only test sends, a live key only live ones.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Cancel a scheduled email","tags":["Emails"],"description":"Requires `emails:d`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"emails:d","responses":{"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Reschedule a scheduled email","tags":["Emails"],"description":"Requires `emails:m`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"scheduled_at":{"type":"string","format":"date-time","description":"An ISO 8601 date-time with an offset, 1 minute to 30 days ahead.","example":"2026-10-01T14:00:00Z"}},"required":["scheduled_at"],"description":"The new send time of a scheduled email."}}},"description":"The new send time of a scheduled email."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"emails:m","responses":{"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/emails/batch":{"post":{"summary":"Send a batch of emails","tags":["Emails"],"description":"Requires `emails:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"A JSON array of up to 100 independent emails, or one message for up to 500 recipients as an object with a recipients list. Attachments are not available in a batch, and scheduling only in the object form. The request body may be at most 10 MB; send a larger email on its own.","oneOf":[{"minItems":1,"maxItems":100,"type":"array","items":{"type":"object","properties":{"from":{"type":"string","minLength":1,"description":"Sender, optionally with a name. Its domain must be a verified sending domain on your account. Optional when the template sets one.","example":"Acme <hello@yourdomain.ca>"},"to":{"anyOf":[{"type":"string","format":"email"},{"type":"array","items":{"type":"string","format":"email"}}],"description":"A string or an array; at most 50 recipients across to, cc and bcc.","example":"sam@example.com"},"cc":{"anyOf":[{"type":"string","format":"email"},{"type":"array","items":{"type":"string","format":"email"}}],"description":"A string or an array."},"bcc":{"anyOf":[{"type":"string","format":"email"},{"type":"array","items":{"type":"string","format":"email"}}],"description":"A string or an array."},"reply_to":{"anyOf":[{"type":"string","format":"email"},{"type":"array","items":{"type":"string","format":"email"}}],"description":"A string or an array."},"subject":{"type":"string","minLength":1,"maxLength":998,"description":"Up to 998 characters. Leave out subject, html and text when you send a template."},"html":{"type":"string","description":"The HTML body: at most 2 MB, before and after variables are substituted. Give html, text or both, unless you send a template."},"text":{"type":"string","description":"The plain text body: at most 2 MB, before and after variables are substituted. Give html, text or both, unless you send a template."},"template":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":200,"description":"The template's id or alias. A live key sends its published version; a test key sends its draft."},"variables":{"default":{},"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"}]},"description":"Values for the template's variables, by key."}},"required":["id"],"additionalProperties":false,"description":"Send a stored template instead of subject, html and text: its id or alias, and values for its variables."},"variables":{"default":{},"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"}]},"description":"Values to substitute into placeholders in subject, html and text. Ignored when template is set; a template's own variables go in template.variables instead."},"headers":{"default":{},"type":"object","additionalProperties":{"type":"string"},"description":"Extra email headers, sent as given. Headers HonkIO sets itself (To, From, Subject, Content-Type, List-Unsubscribe and the like) and any name starting with ARC- or X-SES- are refused with EMAIL_HEADER_INVALID."},"tags":{"default":[],"maxItems":10,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,256}$"},"value":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,256}$"}},"required":["name","value"]},"description":"Up to 10 name and value pairs, each part 1 to 256 letters, digits, underscores or hyphens. Names starting with honkio_ are reserved. Tags are kept after the subject and body are purged, so keep personal information out of them."},"is_commercial":{"default":false,"type":"boolean","description":"False by default. True marks a commercial message under CASL: it goes to exactly one recipient, needs consent on file for that address, and carries an unsubscribe footer and header."},"tracking":{"default":{},"type":"object","properties":{"opens":{"type":"boolean","description":"Track opens with a pixel."},"clicks":{"type":"boolean","description":"Track clicks by rewriting links."}},"description":"Open and click tracking for this email, overriding the sending domain's setting."},"disable_unsubscribe_footer":{"default":false,"type":"boolean","description":"Leave out the unsubscribe footer HonkIO adds to a commercial email. The one click unsubscribe header is still sent: put your own unsubscribe link in the body."}},"required":["to"]},"description":"An array of up to 100 emails, each sent on its own. Every item is checked before any is sent."},{"type":"object","properties":{"from":{"type":"string","minLength":1,"description":"Sender, optionally with a name. Its domain must be a verified sending domain on your account. Optional when the template sets one.","example":"Acme <hello@yourdomain.ca>"},"subject":{"type":"string","minLength":1,"maxLength":998,"description":"Up to 998 characters. Leave out subject, html and text when you send a template."},"html":{"type":"string","description":"The HTML body: at most 2 MB, before and after variables are substituted. Give html, text or both, unless you send a template."},"text":{"type":"string","description":"The plain text body: at most 2 MB, before and after variables are substituted. Give html, text or both, unless you send a template."},"template":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":200,"description":"The template's id or alias. A live key sends its published version; a test key sends its draft."},"variables":{"default":{},"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"}]},"description":"Values for the template's variables, by key."}},"required":["id"],"additionalProperties":false,"description":"Send a stored template instead of subject, html and text: its id or alias, and values for its variables."},"headers":{"default":{},"type":"object","additionalProperties":{"type":"string"},"description":"Extra email headers, sent as given. Headers HonkIO sets itself (To, From, Subject, Content-Type, List-Unsubscribe and the like) and any name starting with ARC- or X-SES- are refused with EMAIL_HEADER_INVALID."},"tags":{"default":[],"maxItems":10,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,256}$"},"value":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,256}$"}},"required":["name","value"]},"description":"Up to 10 name and value pairs, each part 1 to 256 letters, digits, underscores or hyphens. Names starting with honkio_ are reserved. Tags are kept after the subject and body are purged, so keep personal information out of them."},"is_commercial":{"default":false,"type":"boolean","description":"False by default. True marks a commercial message under CASL: it goes to exactly one recipient, needs consent on file for that address, and carries an unsubscribe footer and header."},"scheduled_at":{"default":null,"type":["string","null"],"format":"date-time","description":"Send later: an ISO 8601 date-time, 1 minute to 30 days ahead."},"tracking":{"default":{},"type":"object","properties":{"opens":{"type":"boolean","description":"Track opens with a pixel."},"clicks":{"type":"boolean","description":"Track clicks by rewriting links."}},"description":"Open and click tracking for this email, overriding the sending domain's setting."},"disable_unsubscribe_footer":{"default":false,"type":"boolean","description":"Leave out the unsubscribe footer HonkIO adds to a commercial email. The one click unsubscribe header is still sent: put your own unsubscribe link in the body."},"recipients":{"minItems":1,"maxItems":500,"type":"array","items":{"type":"object","properties":{"to":{"type":"string","format":"email","description":"The recipient's address."},"variables":{"default":{},"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"}]},"description":"Values for this recipient's placeholders or template variables."}},"required":["to"]},"description":"Up to 500 recipients, each sent their own email.","example":[{"to":"sam@example.com"}]}},"required":["recipients"],"description":"One message for up to 500 recipients, each with its own variables."}]}}},"description":"A JSON array of up to 100 independent emails, or one message for up to 500 recipients as an object with a recipients list. Attachments are not available in a batch, and scheduling only in the object form. The request body may be at most 10 MB; send a larger email on its own."},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"emails:w","responses":{"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN, EMAIL_DOMAIN_NOT_ALLOWED or SENDING_PAUSED (live email is paused; details carry paused_until, reason and channel: \"email\"). Refused before any item is sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"TEST_EMAIL_QUOTA_EXCEEDED: with a test key, the batch would take the account past 200 test emails in 24 hours, counting one per recipient of the object form and one per email of the array form. It is refused whole, before any email is created (details carry limit, used, requested and window_hours). Or RATE_LIMITED at 100 requests per second.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/email-domains":{"post":{"summary":"Add a sending domain","tags":["Email Domains"],"description":"Requires `email_domains:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"type":"string","minLength":1,"maxLength":253,"description":"A domain or subdomain you control, such as mail.yourdomain.ca. Publish the DNS records the response lists, then verify it.","example":"yourdomain.ca"}},"required":["domain"],"description":"The domain to send from."}}},"description":"The domain to send from."},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"email_domains:w","responses":{"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List sending domains","tags":["Email Domains"],"description":"Requires `email_domains:r`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"email_domains:r","responses":{"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/email-domains/{id}":{"get":{"summary":"Get a sending domain","tags":["Email Domains"],"description":"Requires `email_domains:r`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"email_domains:r","responses":{"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a sending domain's open/click tracking or receiving","tags":["Email Domains"],"description":"Requires `email_domains:m`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"open_tracking":{"type":["boolean","null"],"description":"Track opens on this domain's email. Null restores the platform default."},"click_tracking":{"type":["boolean","null"],"description":"Track clicks on this domain's email. Null restores the platform default."},"receiving":{"type":"boolean","description":"Receive email at this domain. The domain must be verified for sending first; the response adds the inbound MX record to publish."}},"description":"The settings to change: at least one of open_tracking, click_tracking or receiving."}}},"description":"The settings to change: at least one of open_tracking, click_tracking or receiving."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"email_domains:m","responses":{"200":{"description":"The updated domain, with the DNS records to publish (the inbound MX while receiving is on).","content":{"application/json":{"schema":{"description":"The updated domain, with the DNS records to publish (the inbound MX while receiving is on).","type":"object","additionalProperties":true,"required":["id","domain","status","region","verified_at","last_checked_at","failure_reason","revalidation_failed_at","open_tracking","click_tracking","receiving_status","receiving_requested_at","receiving_verified_at","receiving_failure_reason","receiving_revalidation_failed_at","receiving_last_checked_at","receiving_warning","created_at","records","receiving_missing"],"properties":{"id":{"type":"string"},"domain":{"type":"string"},"status":{"type":"string","enum":["pending","verifying","verified","failed","temporarily_failed"]},"region":{"type":"string"},"verified_at":{"type":["string","null"],"format":"date-time"},"last_checked_at":{"type":["string","null"],"format":"date-time"},"failure_reason":{"type":["string","null"]},"revalidation_failed_at":{"type":["string","null"],"format":"date-time"},"open_tracking":{"type":["boolean","null"]},"click_tracking":{"type":["boolean","null"]},"receiving_status":{"type":"string","enum":["off","pending","verified","failed"]},"receiving_requested_at":{"type":["string","null"],"format":"date-time"},"receiving_verified_at":{"type":["string","null"],"format":"date-time"},"receiving_failure_reason":{"type":["string","null"]},"receiving_revalidation_failed_at":{"type":["string","null"],"format":"date-time"},"receiving_last_checked_at":{"type":["string","null"],"format":"date-time","description":"When the inbound MX was last checked on its own; last_checked_at dates the sending check."},"receiving_warning":{"type":["string","null"],"enum":["apex_mx",null],"description":"apex_mx while receiving is on at a likely registrable apex: an MX there takes over the domain's own mail."},"created_at":{"type":"string","format":"date-time"},"records":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["type","name","value","purpose"],"properties":{"type":{"type":"string","enum":["TXT","MX"]},"name":{"type":"string"},"value":{"type":"string"},"purpose":{"type":"string","enum":["dkim","mail_from","spf","dmarc_recommended","inbound"]}}}},"receiving_missing":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["record","expected"],"properties":{"record":{"type":"string"},"expected":{"type":"string"}}},"description":"The inbound MX, when this request just turned receiving on and it is not published yet; [] otherwise. A lookup only: POST /v1/email-domains/{id}/verify moves receiving_status."}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"ACCOUNT_NOT_VERIFIED: turning receiving on for a live domain needs a verified owner mobile number. Turning receiving off, and every other field, is unaffected. Also FORBIDDEN when the key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such domain on this account. A test key finds test-mode domains only, so a live domain answers it the same way; a live key finds both.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"RECEIVING_REQUIRES_VERIFIED_DOMAIN: receiving can only be turned on for a domain verified for sending.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a sending domain","tags":["Email Domains"],"description":"Requires `email_domains:d`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"email_domains:d","responses":{"204":{"description":"Deleted. Email from the domain stops until it is added and verified again."},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such domain on this account. A test key finds test-mode domains only, so a live domain answers it the same way; a live key finds both.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"EMAIL_DOMAIN_HAS_INFLIGHT: the domain still has queued, scheduled or sending emails.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/email-domains/{id}/verify":{"post":{"summary":"Re-check DNS and SES verification status","tags":["Email Domains"],"description":"Requires `email_domains:w`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"email_domains:w","responses":{"200":{"description":"The result of the check. missing lists sending records not found; receiving_missing the inbound MX when receiving is on and it is not published.","content":{"application/json":{"schema":{"description":"The result of the check. missing lists sending records not found; receiving_missing the inbound MX when receiving is on and it is not published.","type":"object","additionalProperties":true,"required":["id","status","verified","missing","ses_status","mail_from_status","receiving_status","receiving_missing","receiving_warning"],"properties":{"id":{"type":"string"},"status":{"type":"string","enum":["pending","verifying","verified","failed","temporarily_failed"]},"verified":{"type":"boolean"},"missing":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["record","expected"],"properties":{"record":{"type":"string"},"expected":{"type":"string"}}}},"ses_status":{"type":["string","null"]},"mail_from_status":{"type":["string","null"]},"receiving_status":{"type":"string","enum":["off","pending","verified","failed"]},"receiving_missing":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["record","expected"],"properties":{"record":{"type":"string"},"expected":{"type":"string"}}}},"receiving_warning":{"type":["string","null"],"enum":["apex_mx",null],"description":"apex_mx while receiving is on at a likely registrable apex: an MX there takes over the domain's own mail."}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND: no such domain on this account. A test key finds test-mode domains only, so a live domain answers it the same way; a live key finds both.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/email-suppressions":{"post":{"summary":"Suppress an email address","tags":["Email Suppressions"],"description":"Adds the address to the suppression list with the reason `manual`. A send to a suppressed address is refused with `EMAIL_SUPPRESSED`. Live keys only: the list has no test mode.\n\nRequires `email_suppressions:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email_address":{"type":"string","format":"email","description":"The address. No email is sent to it again until you remove it."},"reason":{"type":"string","enum":["manual"],"description":"Always manual: bounces, complaints and unsubscribes are added automatically."}},"required":["email_address","reason"],"description":"The address to block."}}},"description":"The address to block."},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"email_suppressions:w","responses":{"201":{"description":"The suppressed address, lowercased.","content":{"application/json":{"schema":{"description":"The suppressed address, lowercased.","type":"object","additionalProperties":true,"required":["email_address","reason"],"properties":{"email_address":{"type":"string"},"reason":{"type":"string","enum":["manual"]}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED: the suppression list has no test mode and live sends are checked against it, so a test key can read it but not change it. Also FORBIDDEN when the key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"VALIDATION_ERROR: the request failed validation; details names the fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List suppressed email addresses","tags":["Email Suppressions"],"description":"Newest first. Filter with `reason` (`hard_bounce`, `complaint`, `unsubscribe` or `manual`) and page with `limit` (default 50, up to 100) and `cursor`.\n\nRequires `email_suppressions:r`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"email_suppressions:r","responses":{"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/email-suppressions/{address}":{"delete":{"summary":"Remove an address from the suppression list","tags":["Email Suppressions"],"description":"Answers 204, or 404 when the address is not suppressed. Live keys only: the list has no test mode.\n\nRequires `email_suppressions:d`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"email_suppressions:d","parameters":[{"schema":{"type":"string"},"in":"path","name":"address","required":true}],"responses":{"204":{"description":"Removed: the address can receive email again."},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED: the suppression list has no test mode and live sends are checked against it, so a test key can read it but not change it. Also FORBIDDEN when the key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT_FOUND (details.resource \"email_suppression\"): the address is not suppressed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/email-templates":{"post":{"summary":"Create an email template","tags":["Email Templates"],"description":"Saves a draft. A live send uses a template once it is published; a send with a test key renders the draft, published or not, so a test key can build and try a template without publishing it.\n\nRequires `email_templates:w`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"A label for you, up to 200 characters."},"alias":{"default":null,"type":["string","null"],"pattern":"^[a-z0-9][a-z0-9_-]{0,62}$","description":"Send with it instead of the id: 1 to 63 lowercase letters, digits, - or _, starting with a letter or digit. Null removes it."},"subject":{"type":"string","minLength":1,"maxLength":998,"description":"Up to 998 characters. Variables go in double braces, such as {{ first_name }}."},"html":{"type":["string","null"],"description":"The HTML body. A template needs html, text or both. Variable values are always HTML escaped.","example":"<p>Hi {{ first_name }}, your order has shipped.</p>"},"text":{"type":["string","null"],"description":"The plain text body."},"from":{"type":["string","null"],"description":"The default sender. A send can set its own."},"reply_to":{"anyOf":[{"type":"string","format":"email"},{"type":"array","items":{"type":"string","format":"email"}}],"description":"The default reply address: a string or an array. A send can set its own."},"variables":{"default":[],"type":"array","items":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":64,"description":"The variable's name: up to 64 characters."},"fallback":{"type":"string","maxLength":1000,"description":"Used when a send gives no value: up to 1000 characters."}},"required":["key"],"additionalProperties":false},"description":"Every variable the template uses, each optionally with a fallback. A send that leaves one with neither a value nor a fallback is refused."}},"required":["name","subject"],"additionalProperties":false,"description":"A new template. It saves as a draft: publish it before sending with it from a live key."}}},"description":"A new template. It saves as a draft: publish it before sending with it from a live key."},"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"email_templates:w","responses":{"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List email templates","tags":["Email Templates"],"description":"Requires `email_templates:r`.","security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"email_templates:r","responses":{"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/email-templates/{idOrAlias}":{"get":{"summary":"Get an email template by id or alias","tags":["Email Templates"],"description":"Requires `email_templates:r`.","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":200},"in":"path","name":"idOrAlias","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"email_templates:r","responses":{"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Edit an email template draft","tags":["Email Templates"],"description":"Changes the draft. Live sends keep the published version until you publish again; a send with a test key renders the draft. The alias is not part of the draft: a change to it applies to sends at once, so on a template with a published version a test key cannot change it (LIVE_KEY_REQUIRED).\n\nRequires `email_templates:m`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"A label for you, up to 200 characters."},"alias":{"type":["string","null"],"pattern":"^[a-z0-9][a-z0-9_-]{0,62}$","description":"Send with it instead of the id: 1 to 63 lowercase letters, digits, - or _, starting with a letter or digit. Null removes it."},"subject":{"type":"string","minLength":1,"maxLength":998,"description":"Up to 998 characters. Variables go in double braces, such as {{ first_name }}."},"html":{"type":["string","null"],"description":"The HTML body. A template needs html, text or both. Variable values are always HTML escaped."},"text":{"type":["string","null"],"description":"The plain text body."},"from":{"type":["string","null"],"description":"The default sender. A send can set its own."},"reply_to":{"anyOf":[{"type":"string","format":"email"},{"type":"array","items":{"type":"string","format":"email"}}],"description":"The default reply address: a string or an array. A send can set its own."},"variables":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":64,"description":"The variable's name: up to 64 characters."},"fallback":{"type":"string","maxLength":1000,"description":"Used when a send gives no value: up to 1000 characters."}},"required":["key"],"additionalProperties":false},"description":"Every variable the template uses, each optionally with a fallback. A send that leaves one with neither a value nor a fallback is refused."}},"additionalProperties":false,"description":"The fields to change in the draft. Live sends keep using the published version until you publish again."}}},"description":"The fields to change in the draft. Live sends keep using the published version until you publish again."},"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":200},"in":"path","name":"idOrAlias","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"email_templates:m","responses":{"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED: a test key cannot change the alias of a template that has a published version, because live sends may use the alias. It can edit every other field of the draft. Also FORBIDDEN when the key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete an email template and its versions","tags":["Email Templates"],"description":"Sends that name the template then answer NOT_FOUND. Refused with EMAIL_TEMPLATE_IN_USE while a scheduled email uses it. Live keys only (LIVE_KEY_REQUIRED).\n\nRequires `email_templates:d`.","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":200},"in":"path","name":"idOrAlias","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"email_templates:d","responses":{"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED: templates have no test mode, and this changes what live sends get. A test key can create a template, edit its draft and send test emails that render the draft. Also FORBIDDEN when the key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/email-templates/{idOrAlias}/publish":{"post":{"summary":"Publish the draft as a new version","tags":["Email Templates"],"description":"Live sends use the new version at once. Live keys only (LIVE_KEY_REQUIRED): a send with a test key renders the draft and needs no publish.\n\nRequires `email_templates:m`.","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":200},"in":"path","name":"idOrAlias","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"email_templates:m","responses":{"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED: templates have no test mode, and this changes what live sends get. A test key can create a template, edit its draft and send test emails that render the draft. Also FORBIDDEN when the key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/email-templates/{idOrAlias}/rollback":{"post":{"summary":"Publish an earlier version again as a new version","tags":["Email Templates"],"description":"Replaces the draft with the earlier version and publishes it. Live keys only (LIVE_KEY_REQUIRED).\n\nRequires `email_templates:m`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"version":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"A published version number, from GET /v1/email-templates/{idOrAlias}/versions."}},"required":["version"],"additionalProperties":false,"description":"The version to publish again."}}},"description":"The version to publish again."},"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":200},"in":"path","name":"idOrAlias","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"email_templates:m","responses":{"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"LIVE_KEY_REQUIRED: templates have no test mode, and this changes what live sends get. A test key can create a template, edit its draft and send test emails that render the draft. Also FORBIDDEN when the key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/email-templates/{idOrAlias}/versions":{"get":{"summary":"List published versions, newest first","tags":["Email Templates"],"description":"Requires `email_templates:r`.","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":200},"in":"path","name":"idOrAlias","required":true}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"x-honkio-permission":"email_templates:r","responses":{"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no balance, or a LIVE key was used before the first top-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key lacks the permission this operation requires, or the account is suspended or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit reached: 100 requests per second per account, or a route-specific limit (Retry-After set where one applies).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/oauth/authorize":{"get":{"summary":"Start an authorization (browser)","tags":["OAuth"],"description":"OAuth 2.1 authorization endpoint. Sends the user to the HonkIO consent screen, then back to redirect_uri with a code. PKCE S256 is required. Client ids may be Client ID Metadata Document URLs (https), dynamically registered ids, or pre-registered ids. A request that fails validation is answered with an error page; it is redirected back with an error only when redirect_uri is loopback or the client is pre-registered.","parameters":[{"schema":{"type":"string"},"in":"query","name":"response_type","required":false,"description":"Must be code."},{"schema":{"type":"string"},"in":"query","name":"client_id","required":false},{"schema":{"type":"string"},"in":"query","name":"redirect_uri","required":false,"description":"Must match one registered for the client; loopback URIs match regardless of port."},{"schema":{"type":"string"},"in":"query","name":"scope","required":false,"description":"Space-separated. Omit for the default set (everything except account:manage)."},{"schema":{"type":"string"},"in":"query","name":"state","required":false,"description":"At most 512 characters."},{"schema":{"type":"string"},"in":"query","name":"code_challenge","required":false,"description":"43 to 128 base64url characters (RFC 7636)."},{"schema":{"type":"string"},"in":"query","name":"code_challenge_method","required":false,"description":"Must be S256."},{"schema":{"type":"string"},"in":"query","name":"resource","required":false,"description":"RFC 8707 resource indicator, the MCP endpoint URL."}],"security":[],"responses":{"302":{"description":"Redirect to the consent screen. A request that fails validation is redirected back to redirect_uri with an error, state and iss only when that URI is loopback or the client is pre-registered.","headers":{"Location":{"schema":{"type":"string"}}}},"400":{"description":"An HTML page (never a redirect) when client_id or redirect_uri cannot be trusted, when state is longer than 512 characters, or when a dynamically registered or metadata-document client sends a request that fails validation with a redirect_uri that is not loopback.","content":{"text/html":{"schema":{"type":"string"}}}},"429":{"description":"slow_down: 30 requests a minute from one address.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}}}}},"/oauth/token":{"post":{"summary":"Exchange a code or refresh token for tokens","tags":["OAuth"],"description":"Form-encoded per RFC 6749. authorization_code needs code, redirect_uri, client_id and code_verifier; refresh_token needs refresh_token and client_id. Refresh tokens rotate on every use; presenting a used one revokes the whole grant. Access tokens live one hour, refresh tokens thirty days. Confidential clients authenticate with HTTP Basic or client_secret in the form.","requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"grant_type":{"type":"string","description":"authorization_code or refresh_token."},"code":{"type":"string"},"redirect_uri":{"type":"string"},"client_id":{"type":"string"},"client_secret":{"type":"string"},"code_verifier":{"type":"string"},"refresh_token":{"type":"string"},"scope":{"type":"string","description":"On refresh, may not widen the granted scopes. A narrower request is accepted, but the tokens keep the whole granted scope, and the response scope says so."},"resource":{"type":"string"}}}}}},"security":[],"responses":{"200":{"description":"Tokens. The access token is presented as a Bearer to the MCP endpoint.","content":{"application/json":{"schema":{"description":"Tokens. The access token is presented as a Bearer to the MCP endpoint.","type":"object","properties":{"access_token":{"type":"string"},"token_type":{"type":"string"},"expires_in":{"type":"integer"},"refresh_token":{"type":"string"},"scope":{"type":"string"}}}}}},"400":{"description":"invalid_request, invalid_grant, invalid_scope, invalid_target or unsupported_grant_type.","content":{"application/json":{"schema":{"description":"invalid_request, invalid_grant, invalid_scope, invalid_target or unsupported_grant_type.","type":"object","properties":{"error":{"type":"string"},"error_description":{"type":"string"}}}}}},"401":{"description":"invalid_client.","content":{"application/json":{"schema":{"description":"invalid_client.","type":"object","properties":{"error":{"type":"string"},"error_description":{"type":"string"}}}}}},"429":{"description":"slow_down: 60 requests a minute from one address.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}}}}},"/oauth/register":{"post":{"summary":"Register a client dynamically (RFC 7591)","tags":["OAuth"],"description":"Public clients only: token_endpoint_auth_method must be none and no secret is issued. Prefer a Client ID Metadata Document, which needs no registration. redirect_uris must be https or loopback http. A registration that no user approves within two days is removed, and the client must register again.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"client_name":{"type":"string","description":"Required. At most 100 characters."},"redirect_uris":{"type":"array","items":{"type":"string"},"description":"Required, at least one; https or loopback http."},"token_endpoint_auth_method":{"type":"string","description":"Only none is accepted here."},"grant_types":{"type":"array","items":{"type":"string"}},"response_types":{"type":"array","items":{"type":"string"}},"application_type":{"type":"string","description":"web or native."},"client_uri":{"type":"string"},"logo_uri":{"type":"string"}}}}}},"security":[],"responses":{"201":{"description":"The registered client.","content":{"application/json":{"schema":{"description":"The registered client.","type":"object","properties":{"client_id":{"type":"string"},"client_id_issued_at":{"type":"integer"},"client_name":{"type":"string"},"redirect_uris":{"type":"array","items":{"type":"string"}},"token_endpoint_auth_method":{"type":"string"},"grant_types":{"type":"array","items":{"type":"string"}},"response_types":{"type":"array","items":{"type":"string"}}}}}}},"400":{"description":"invalid_redirect_uri or invalid_client_metadata.","content":{"application/json":{"schema":{"description":"invalid_redirect_uri or invalid_client_metadata.","type":"object","properties":{"error":{"type":"string"},"error_description":{"type":"string"}}}}}},"429":{"description":"slow_down: 10 registrations a minute from one address.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"503":{"description":"temporarily_unavailable: too many registrations across all callers are waiting on a first approval, so new ones are paused. Retry later, or use a Client ID Metadata Document, which needs no registration.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}}}}},"/oauth/revoke":{"post":{"summary":"Revoke a grant by one of its tokens (RFC 7009)","tags":["OAuth"],"description":"Revokes the whole grant the access or refresh token belongs to, including the API key behind it. Answers 200 whether or not there was anything to revoke; only an Authorization header whose Basic credentials cannot be decoded gets 401 invalid_client.","requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"Required."},"token_type_hint":{"type":"string","description":"access_token or refresh_token."},"client_id":{"type":"string"},"client_secret":{"type":"string"}}}}}},"security":[],"responses":{"200":{"description":"Revoked, or nothing to revoke.","content":{"application/json":{"schema":{"description":"Revoked, or nothing to revoke.","type":"object","properties":{}}}}},"401":{"description":"invalid_client: the Basic credentials could not be decoded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"429":{"description":"slow_down: 30 requests a minute from one address.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}}}}}},"servers":[{"url":"https://api.honkio.ca","description":"Production"},{"url":"http://localhost:3000","description":"Local development"}],"security":[{"apiKey":[]},{"apiKeyHeader":[]}],"tags":[{"name":"Auth","description":"Dashboard login and password management"},{"name":"OAuth","description":"OAuth 2.1 authorization server used by MCP clients (Claude, ChatGPT, Gemini) to sign in instead of carrying an API key. Discovery: /.well-known/oauth-authorization-server"},{"name":"Messages","description":"Send and receive SMS messages"},{"name":"Phone Numbers","description":"Provision and manage Canadian phone numbers"},{"name":"Accounts","description":"Account management and API keys"},{"name":"Compliance / CASL","description":"CASL consent and opt-out management"},{"name":"Compliance / DNCL","description":"CRTC Do Not Call List lookups (coming soon)"},{"name":"Compliance / PIPEDA","description":"Privacy compliance and right to erasure"},{"name":"Webhooks","description":"Register endpoints to receive event notifications"},{"name":"Verify","description":"Phone number verification (SMS OTP) with per-verification upcharge"},{"name":"Pricing","description":"Current per-send prices, so an integrator can quote a cost before sending"},{"name":"Contacts","description":"Manage contact phone numbers and metadata"},{"name":"Contact Groups","description":"Group contacts and send group broadcasts"},{"name":"Lists","description":"Allow/deny contact lists assignable to API keys"},{"name":"Emails","description":"Send and read email"},{"name":"Email Domains","description":"Verify sending domains for email"},{"name":"Email Suppressions","description":"Manage email suppression lists"},{"name":"Email Templates","description":"Store email templates and publish versions to send by id or alias"}]}