Skip to main content

2026-10-05

  • Numbers and IVRs are in the public API. See IVRs and numbers.
    • Numbers: GET /dids, GET /dids/{id} (new), POST /dids, PATCH /dids/{id}, DELETE /dids/{id}.
    • IVRs: GET /ivr-flows, POST /ivr-flows, GET /ivr-flows/{id} (the definition), PATCH /ivr-flows/{id}, DELETE /ivr-flows/{id}, POST /ivr-flows/{id}/publish.
  • The IVR designer in a popup. POST /ivr-flows/{id}/designer-session answers a one-time URL that opens FireTone’s designer on one IVR, with no FireTone sign-in. The designer posts ivr.saved, ivr.published and ivr.closed to the page that opened it.
  • POST /calls/ivr rings a number and runs an IVR’s published revision for whoever answers. It answers 202 like POST /calls/ai, supports ?dry_run=true, and shares the limit on calls placed (60 a minute per key).
  • A call request gains flow_id, and kind can be ivr_call.
  • A new API key preset, IVRs & numbers: numbers and IVRs, placing calls, and the lists a number or a step points at.
All of these are additions: nothing a client already sends or reads has changed.

2026-09-30

  • A module that is off has no API. The operations of an optional module (AI agent, Outbound, Tickets) now answer the same way everywhere:
    • 403 module_disabled while the module is switched off or outside the licence;
    • 404 on a server built without it.
    This was already true of most of them. It now also covers POST /calls/ai and GET /virtual-agent-profiles, which went on answering before; without the module in the build they answer 404 module_not_installed.
  • Each server’s API description lists only what it serves. /api/v1/openapi.json and /api/v1/openapi-public.json leave out the operations of modules the server does not run. Every such operation carries x-module naming its module, and the reference here says which module it needs. A client generated from your server’s own description has no methods that can only fail.
  • A call record gains client_trunk_id: the client trunk (a client’s own PBX) that sent the call. It is absent on every other call. GET /cdrs also gains it as a filter.
  • GET /calls/active gains paging and filters: limit, offset, sort, order, q, and the filters state (RINGING, ACTIVE, …), direction and organisation_id. Without limit it still answers every call, as before.
  • GET /conference-rooms/{id}/sessions gains since (sessions started at or after an instant) and live (only sessions still running), with sort, order and q.
All of these are compatible: nothing a client already sends or reads has changed meaning.

2026-09-25

  • Notes to the phone. POST /calls/{uuid}/info sends a note to the phone(s) on a live call as SIP INFO; POST /extensions/{id}/message sends one to a person’s device as SIP MESSAGE, with or without a call. The note is the FireTone app’s own format: application/json, title, body, level (info | warning | alert), ack, and every other field a row. GET /calls/{uuid}/infos and GET /extensions/{id}/messages read back what was sent and when the person acknowledged it. Two webhook events, call.info.sent and call.info.acked. Organisations gain call_info_url and call_info_secret: a URL asked on every answered call what the phone should show. Conference rooms tell every phone in them who joined or left. Every listing carries a receipt per phone told (recipients, with each person’s acked_at) and keeps a send that reached nobody as status: failed with its reason. See Messages to the phone.

How the API changes

  • The public API is /api/v1. Operations in the public document don’t change incompatibly within v1. New fields, new events and new optional parameters can appear at any time, so ignore what you don’t recognise.
  • An incompatible change means a new version. The old one keeps working for at least six months after the new one is announced here.
  • Endpoints outside the public document are the panel’s own interface and can change without notice.

2026-09-24: which switch carried a call

  • A call record gains fs_node_id: the switch node that carried the call. The column has existed since the call record did and was never written, which with one node was obvious and with two is the first question about any call. Absent on every call recorded before today, and on a call the control plane could not attribute to a node. On GET /cdrs, GET /cdrs/{uuid} and the export.
  • GET /cdrs gains node_id as a filter: only the calls one switch carried.

2026-09-23: conference rooms, and who spoke

  • Conference rooms join the public API. Creating and editing rooms, their default members and PIN exemptions, dialling somebody in, a room’s sessions and recordings, and running a room while it is up — mute, kick, lock, record, end — are all in the public document now and change only as versioned changes. They were served and documented before this; what changes today is the commitment. See Conference rooms.
  • GET /conferences, GET /conferences/{room} and GET /conferences/by-call/{uuid} describe their answers. The document said 204 with no body for all three, copied from the controls beside them. They are reads and always returned a body: the room, its members and whether you may control it. A client generated before today has a void return type for them and should be regenerated.
  • A transcript turn gains speaker: who said it, when several people could have. It carries a name on a conference session a virtual agent sat in, and is empty on an ordinary call, where the caller is the caller. On GET /conversations/{id} and GET /cdrs/{uuid}/conversation.
  • Extensions gain codec_string: the codecs that extension offers, in order, chosen one by one. Empty means its codec profile decides.
  • GET /extensions/{id} — the single read beside the list.
  • A new webhook event, conference.minutes: a virtual agent that sat in a room wrote the session’s minutes. It carries the session, the room, who was there, the minutes themselves and where they were sent. One per session in which minutes were written.

2026-09-20: capacity refusals, and a call’s currency

  • 503 node_busy on POST /calls and POST /calls/ai, with Retry-After: 30: the call was not placed because the node has no room right now — it is at its channel limit, the machine is loaded, or every AI session it allows is in use. Nothing rang and nothing was billed; send the same request again. This is deliberately not 422, which still means the request itself is wrong.
  • Call records gain currency: the ISO 4217 code that sell_amount, cost_amount and margin are in. It is absent on calls recorded before this date — the field existed and was never filled.
  • With margin:read, call records also gain cost_currency, cost_amount_original, fx_rate and cost_unconverted: what the carrier charged in its own currency, the exchange rate applied, and whether a rate was missing. When cost_unconverted is true, margin is not a margin — it is one currency subtracted from another — and must not be shown as money.

2026-09-15: the public API

  • Integration keys:
    • owned by the organisation, with presets;
    • a compulsory IP allowlist;
    • per-key rate limits;
    • Idempotency-Key.
  • Calls:
    • click-to-call by from (extension, agent or login email);
    • POST /calls/ai for an AI agent to call someone;
    • reference and signed callback_url on every call;
    • ?dry_run=true;
    • GET /calls/{uuid} and /call-requests/{id}.
  • Campaigns:
    • contacts loaded with your references and variables;
    • start, pause, resume and stop;
    • per-contact results to result_callback_url, with GET /campaigns/{id}/results.
  • Contacts and data:
    • GET /contacts/lookup;
    • PUT /contacts/by-reference/{ref};
    • GET /cdrs/export.
  • Webhooks:
    • durable delivery, retried for about 15 hours and switched off after three days of failures;
    • redelivery;
    • new events: call.missed, voicemail.received, recording.ready, conversation.completed, callback.requested, ticket.created, ticket.updated, contact.created, campaign.contact.completed, campaign.completed, csat.submitted.
  • The Developer section in the panel: console, callback inbox, request log and test numbers.