API Changelog

Notable changes to the public ArelHub API contract. Breaking changes are highlighted explicitly.

Policy

  • Breaking changes require a minimum 60-day deprecation notice before removal.
  • Non-breaking additions (new optional fields, new endpoints) do not require a deprecation period.
  • Staff, internal, and deferred endpoints are excluded from this changelog.

A changelog entry is mandatory when a public route path changes, a required field is added or removed, a response field integrators rely on is renamed, a status code changes meaning, or auth scheme requirements change.


2026-10-18 — Recipient consent

Additive, with one behaviour change for STOP replies

Added

  • Account routes under v1/accounts/{accountId}/recipient-consent (list, preferences, opt-out, opt-in) for account-level sending. An account opt-out takes a required scope of accountLevelOnly or entireAccount.
  • CSV import and export of opt-outs, and storage, listing, import and export of opt-in evidence (opt-in-records), on both the account and the subaccount routes.
  • API-key rights ReadRecipientConsent and ManageRecipientConsent. Existing keys do not have them. The subaccount consent routes now accept API keys with these rights, and subaccount users.
  • Webhook events RecipientOptedOut and RecipientOptedIn. They are separate subscriptions: existing endpoints do not receive them until you add them to subscribedEvents.
  • Error codes RECIPIENT_OPT_OUT_SCOPE_REQUIRED, RECIPIENT_NOT_OPTED_OUT, RECIPIENT_OPT_OUT_NOT_LIFTABLE and CONSENT_IMPORT_INVALID.
  • scope and subaccountId on each opt-out in the list responses.

Changed

  • A STOP reply now applies to the number that received it: the subaccount that owns the number, or the whole account for an account-level number. Before, it blocked the whole account.
  • A recipient who replied STOP is blocked on every channel attempt of a message, including a fallback channel.
  • A subaccount route you cannot reach answers 404 instead of 403. Re-subscribe refusals use RECIPIENT_OPT_OUT_NOT_LIFTABLE or RECIPIENT_NOT_OPTED_OUT instead of CONFLICT.
  • GET v1/accounts/{accountId}/inbound-messages is no longer available to an API key scoped to a subaccount.

2026-10-16 — Delivery-receipt fallback

Additive

Added

  • receiptTimeoutSeconds (60 to 86400; 0 turns it off) and fallbackOnDeliveryFailure on PUT /v1/subaccounts/{subaccountId}/routing-policy, and on the policy resource. A message that is sent but not confirmed within the timeout, or whose delivery fails, is sent on the next channel of the routing policy. Leave a field out to keep it.

Notes for integrators

  • The first message is not recalled, so the recipient may receive it on both channels. Both settings are off by default.
  • Sending a message (POST /v1/accounts/{accountId}/messages and POST /v1/subaccounts/{subaccountId}/messages) now checks the account's status, subscription and quota, as documented. A send from an account without an active subscription returns 402 NO_ACTIVE_SUBSCRIPTION, and a send over quota returns 429. These checks were not being applied to these routes before. Every accepted send counts against the quota, a scheduled message when it is accepted.

2026-10-16 — Message priority

Additive

Added

  • priority (Normal, the default, or Urgent) on POST /v1/accounts/{accountId}/messages and POST /v1/subaccounts/{subaccountId}/messages, and on the message resource. Urgent messages go ahead of normal ones whenever sending has to wait: retries, messages released from an account hold, and scheduled messages, which are sent as soon as their sendAt arrives.

2026-10-16 — Message expiry

Additive

Added

  • expiresAt on POST /v1/accounts/{accountId}/messages and POST /v1/subaccounts/{subaccountId}/messages: after this UTC time the message is no longer sent or charged. It is also returned on the message resource.
  • Message state Expired and webhook event MessageExpired.
  • replyStatus value Expired, and "afterExpiry": true in the reply block of a MessageReceived webhook for a reply that arrived after the message expired.
  • Error code EXPIRES_AT_INVALID (400).

Notes for integrators

  • MessageExpired is part of the "all message events" group. An endpoint subscribed to that group receives it; one that lists events by name does not until you add it.
  • Code that switches on the message state should treat Expired as final and not sent.

2026-10-16 — Scheduled sends

Additive

Added

  • sendAt on POST /v1/accounts/{accountId}/messages and POST /v1/subaccounts/{subaccountId}/messages: send the message at a later UTC time, between 60 seconds and 30 days ahead. sendAt is also returned on the message resource.
  • Message states Scheduled (waiting for its sendAt) and Canceled.
  • POST /v1/accounts/{accountId}/messages/{messageId}/cancel and POST /v1/subaccounts/{subaccountId}/messages/{messageId}/cancel: cancel a message that is still Scheduled.
  • Error codes SEND_AT_OUT_OF_RANGE (400) and MESSAGE_NOT_CANCELABLE (409).

Notes for integrators

  • Code that switches on the message state should treat Scheduled as "not yet sent" and Canceled as final.

2026-10-15 — Reply options

Additive

Added

  • replyOptions on POST /v1/templates and PUT /v1/templates/{templateId}, and on the template resource: the short answers a recipient may text back to a message sent from an SMS template (key, value, aliases). On update, omit it to keep the options, send [] to remove them. Up to 10 options with up to 5 aliases each; keys and aliases are 1 to 20 letters or digits and the words CANCEL, END, HELP, INFO, QUIT, START, STOP, STOPALL, UNSTOP, UNSUBSCRIBE and YES are reserved.
  • replyStatus (None, Awaiting, Answered), replyOptionKey, replyOptionValue and answeredAt on the message resource.
  • replyOptionKey and replyOptionValue on each reply returned by the inbound-message and replies endpoints.
  • data.reply ({ "matched": true, "key", "value" } or { "matched": false }) on the MessageReceived webhook, present when the message answered offered reply options.
  • Error codes REPLY_OPTION_RESERVED_KEYWORD (2203) and REPLY_OPTION_INVALID (2204), returned as 400 when a template's reply options are refused.

Fixed

  • PUT /v1/templates/{templateId} no longer overwrites the template's channel when channelType is left out of the request.

2026-10-15 — Replies

Additive · One behaviour change to number pools

Added

  • clientReference (up to 128 characters) on POST /v1/accounts/{accountId}/messages and POST /v1/subaccounts/{subaccountId}/messages. It is returned on the message, in the data of every webhook for the message, and with every reply to it.
  • Replies from recipients are stored and linked to the message they answer: GET /v1/accounts/{accountId}/inbound-messages, GET /v1/subaccounts/{subaccountId}/inbound-messages (newest first, paged with cursor / nextCursor), GET /v1/accounts/{accountId}/messages/{messageId}/replies and GET /v1/subaccounts/{subaccountId}/messages/{messageId}/replies (oldest first).
  • Webhook event MessageReceived, sent when a recipient replies. It is a separate subscription: it is not part of the message-status events, so existing endpoints do not receive it until MessageReceived is added to their subscribedEvents.
  • fromAddress on each entry of a message's channelHistory: the number that attempt was sent from.
  • Number pool strategy Sticky ("strategy": "Sticky" on POST /v1/number-pools), which keeps a recipient on the number they last heard from.

Changed

  • Number pools using RoundRobin or LeastUsed now rotate across their numbers. Until now every send from a pool used its first active number unless the strategy was Random. Make sure every number in a pool is ready to send (for a long code, assigned to a campaign) before relying on rotation.

2026-10-06 — Passkeys

Additive · Off until an environment turns them on

Added

  • Passkeys and security keys (WebAuthn) as an additional second factor for a user who already has MFA on: GET /v1/auth/mfa/webauthn, POST /v1/auth/mfa/webauthn/register/options and /register/complete, and PATCH / DELETE /v1/auth/mfa/webauthn/{id}. Registration and removal need a step-up token for MfaManage.
  • Sign-in: MFA_REQUIRED lists webauthn in mfaMethods when the user has a passkey. Use POST /v1/auth/login/mfa/webauthn/options, then POST /v1/auth/login/mfa with method: "webauthn".
  • Step-up: POST /v1/auth/mfa/step-up/webauthn/options, then POST /v1/auth/mfa/step-up with method: "webauthn".
  • hasPasskeys on GET /v1/auth/mfa, and error code WEBAUTHN_VERIFICATION_FAILED.

Changed

  • Turning MFA off, or a staff MFA reset, also removes the user's passkeys.

2026-10-06 — Account-required MFA

Additive · Applies only to accounts whose owner turns the policy on

Added

  • GET and PUT /v1/accounts/{accountId}/security-policy: an account owner (Admin) can require MFA for every user on the account and its subaccounts. PUT needs a step-up token for AccountSecurityPolicy, and the Admin must have MFA on first.
  • 401 MFA_ENROLLMENT_REQUIRED (with enrollmentToken and enrollmentTokenExpiresAt) from POST /v1/auth/login and POST /v1/auth/external-login for a user without MFA on a policy account.
  • POST /v1/auth/login/mfa/enroll/setup and /confirm (anonymous). Confirm signs the user in and returns recoveryCodes once.
  • mfaEnabled on the items of GET /v1/account/users, and error code MFA_REQUIRED_BY_ACCOUNT.

Changed

  • On a policy account, POST /v1/auth/token/refresh returns 401 MFA_REQUIRED for a session that did not pass MFA, even without a factor, and DELETE /v1/auth/mfa returns 409 MFA_REQUIRED_BY_ACCOUNT.

2026-10-06 — Authenticator step-up

Additive for users without MFA · Changes step-up for users with MFA on

Added

  • POST /v1/auth/mfa/step-up: a user with MFA on proves step-up with a current authenticator code ({ "action", "code" }) and receives the single-use stepUpToken. Errors: 400 INVALID_CODE, 409 MFA_NOT_ENABLED, 429 LOCKED. Recovery codes are not accepted.
  • POST /v1/email-otp/issue accepts an optional action.
  • Error codes MFA_ENROLLMENT_REQUIRED and STEP_UP_FACTOR_REQUIRED.

Changed

  • A user with MFA on gets 403 STEP_UP_FACTOR_REQUIRED from the email OTP step-up routes and must use POST /v1/auth/mfa/step-up. The exception is MfaManage, so a user who lost their authenticator can still disable MFA with a recovery code.

2026-10-06 — Unlink hardening

Additive

Changed

  • POST /v1/auth/unlink returns 409 CONFLICT when the identity is the user's only sign-in method and the account has no password.
  • The optional password field was removed from the POST /v1/auth/unlink request body. The server never read it, and the step-up is the proof of control. If you still send it, it is ignored.

2026-10-05 — Per-user multi-factor authentication

Additive · Removes operations that never worked

Added

  • 401 MFA_REQUIRED from POST /v1/auth/login and POST /v1/auth/external-login when the user has MFA enabled, with the extensions mfaToken, mfaTokenExpiresAt and mfaMethods. No tokens are issued. Users without MFA are unaffected.
  • POST /v1/auth/login/mfa, the anonymous second step (totp or recovery_code).
  • POST /v1/auth/token/refresh can return 401 MFA_REQUIRED (no mfaToken) for a session created before MFA was enabled.
  • Self-service routes: GET /v1/auth/mfa, POST /v1/auth/mfa/totp/setup, POST /v1/auth/mfa/totp/confirm, POST /v1/auth/mfa/recovery-codes, DELETE /v1/auth/mfa. Setup, recovery-code regeneration and disable require a step-up token for the MfaManage action.
  • Error codes MFA_REQUIRED, MFA_CHALLENGE_INVALID, MFA_ALREADY_ENABLED and MFA_NOT_ENABLED.
  • POST /v1/auth/unlink now requires an UnlinkIdentity step-up.

Removed

  • The earlier v1/auth/mfa operations config/{providerId}, methods/{providerId}, required/{providerId}, challenge and challenge/verify. They never delivered a code and could not complete a verification, so no deprecation period applies. The v1/auth/mfa prefix is reused by the routes above.
  • The unpublished delegation (v1/auth/delegations) and identity-merge (v1/auth/identity-merge) operations, which were never part of the public contract.

2026-08-12 — API baseline

Non-breaking · New public surface

The public API is now represented by one curated OpenAPI contract covering customer authentication, platform operations, messaging, and webhooks. Internal and staff endpoints remain outside the public contract.

Production reference artifacts

Integrators can download the public contract in JSON or YAML format:

  • /openapi/public.json — OpenAPI JSON document
  • /openapi/public.yaml — OpenAPI YAML document

These endpoints provide the curated contract without exposing the internal Swagger UI or private API documents.

Auth scheme normalization

All public endpoints now carry explicit per-operation security annotations. Endpoints requiring JWT show jwt only; endpoints accepting either scheme show both alternatives; anonymous endpoints show no security requirement.

Baseline error responses

All public endpoints now consistently document 401, 403, 422, 429, and 500 responses. All error responses use ProblemDetails format (RFC 7807).


Older changes predating the M41 baseline are not reflected here. This changelog covers the public contract surface only.


← API Reference