API changelog
Integration-relevant changes to the Vorbal API
Check this page for public API changes that can affect your integration and the actions you need to take.
How changes are published
- Added identifies a new, backward-compatible capability.
- Changed identifies a contract or behavior change and includes migration guidance when needed.
- Fixed identifies an implementation or documentation correction.
- Deprecated identifies a supported capability that you should no longer use and includes its replacement and planned removal timeline.
Use the endpoint reference for the current contract. Use this changelog to understand how that contract differs from an earlier integration.
October 21, 2026
Added
- Outbound trunk selection.
POST /v1/sessions:dialaccepts optionaltrunk_idto pin an eligible route andcaller_id_session_uuidto present the matching caller ID from a live inbound phone Session owned by the same application. Without a pin, the platform selects the lowest-priced route with available capacity. If an eligible route is saturated, the platform tries the next eligible route. A pinned request never falls back. A missing caller ID or price match returns503 unavailable; saturation across all eligible routes returns HTTP529with codeunavailable, messageoverloaded, andRetry-After: 5. Both are rejected before monthly dial quota admission.
Changed
-
Outbound trunk catalog requires caller ID prefixes, prices, and capacity limits. When no catalog exists, the platform initializes an initial HOT route without a manual catalog write. The route allows only its configured caller ID prefixes; calls with other caller IDs remain unavailable until an operator adds an eligible route. Additional providers and their rates remain operator-configured. An existing catalog, including an empty one, is preserved. Migrate catalog fields as follows: replace
caller_id_mode: "transparent"withcaller_id_prefixes: ["+"]; replace a fixedcaller_idslist with explicit allowedcaller_id_prefixes; replaceprice_micros_per_minutewithprice_agorot_per_minuteby dividing the old integer value by 10,000. The new price accepts up to four decimal places and has no micros-field alias. Add the requiredlimitsobject withmax_concurrent_calls,max_concurrent_setups, andmax_setups_per_secondfrom 0 through 10,000; zero disables the per-trunk rolling-second limit. Replacemin_setup_interval_mswith the desired maximum number of new setup attempts in any rolling second; zero means no rate limit. The platform no longer uses the previous default outbound route. Calls without an enabled route that matches the caller ID and destination price are unavailable. -
Application limits can cap concurrent outbound setup. Set
limits.max_concurrent_dial_setupsin Admin application settings from 0 through 10,000.GET /api/v1/appreturns the effective value in itslimitsobject. Clients that validate an exact set oflimitsfields must acceptmax_concurrent_dial_setups. API-key callers cannot change it throughPATCH /api/v1/app. The default is 1; zero means unlimited. Pending and ringing Sessions count across all outbound routes until answer or confirmed termination. If the cap is full, a new dial returns HTTP529withRetry-After: 5before monthly dial quota admission. The platform does not wait for an application setup slot. -
Outbound call starts are asynchronous and paced.
202 Acceptedconfirms Session admission, but it does not confirm that the destination is ringing. The platform starts call attempts asynchronously and enforces each route’s maximum starts in a rolling one-second window. Each new initial attempt counts even if the provider does not respond. A new attempt can start while an earlier one is ringing when trunk and application setup capacity remain. Use Session events to follow call progress and completion.
October 19, 2026
Fixed
-
WebSocket Session terminal events reflect connection readiness. A standalone WebSocket Session that fails before connecting now ends with
answered: falseandduration_seconds: 0. After a successful connection, duration starts when your server accepts the WebSocket connection. Previously, terminal events could report an answered Session and count setup time even when the connection failed. Phone Session behavior is unchanged. Usewebsocket.connectedto observe readiness; event names and fields are unchanged. -
WebSocket connection events wait for your server.
websocket.connectedand the Session’s transition fromconnectingtoanswerednow wait until your server accepts the WebSocket handshake and the connection is ready to exchange audio. Previously, they could occur before your server accepted the connection. Setup failures in DNS, TCP, TLS, or the WebSocket handshake emitwebsocket.failedwith the existing sanitized reason and handshake status, without a precedingwebsocket.connectedfor that attempt. Usewebsocket.connectedto confirm connection readiness; it does not prove that audio has been sent, received, or heard. Event names and payload fields are unchanged. Integrations that previously treated a transientconnectedfollowed by failure as a successful connection should handle setup failure directly.
October 18, 2026
Fixed
-
Fresh Room join requests wait for confirmed participation. While a Session is still joining a Room, a new participant-add request returns HTTP 409 with code
conflict, including requests with a new idempotency key. Previously, a fresh request for the same Room could incorrectly return200 already_joinedbefore participation was confirmed. Retry the original accepted request with its original key to replay202 joining; useroom.participant.joinedto confirm participation before sending a fresh request. Confirmed same-Room requests still return200 already_joined. -
Per-file events for finite Session Playback. Each URL file that starts playing now emits
playback.file.endedwhen it completes or is stopped, including a play request with one file. The event carriessession_uuid, the original playoperation_uuid, the zero-basedfile_indexin the request’surlsarray, the originalurl, and areasonofcompletedorstopped. It precedes the operation’s terminalplayback.stoppedorplayback.failedevent. Files that fail or never start have no per-file ended event. If a finite file cannot be read or decoded, the operation emitsplayback.failedwithreason: media_unavailableand no ended event for that file. Silence, live streams, and pushed audio do not emit per-file events. Consumers can track playlist progress by file while continuing to use the terminal event for the operation’s final outcome. -
Room playback reports when each started finite file ends. The new
room.playback.file.endedwebhook identifies the original play operation, zero-basedfile_index, and caller-suppliedurl. Itsreasoniscompletedorstopped. It also fires for a single-file request and arrives before the operation’s terminal event. Failed files and files that never started do not produce this event. Withloop: true, each completed repeat produces another event for the samefile_index. Use it to track progress through a playlist; continue usingroom.playback.endedorroom.playback.failedfor the operation’s final outcome.
Changed
-
SMS quotas count one unit per 70 characters or remaining fraction. Previously, each submission attempt consumed one unit regardless of length. Quota usage now equals
ceil(character_count / 70), counting Unicode code points for all text. For example, 70 characters consume one unit, 71 consume two, and 1,600 consume 23. The defaultmonthly_messageslimit remains 3,000 units. If the remaining quota cannot cover the whole message, the API returns429 resource_limit_exceededwithout consuming units or submitting it. Replays do not consume additional units, and usage recorded before this change keeps its original count. Update usage estimates for messages longer than 70 characters; one request still sends the complete text. -
Initial monthly quota for outbound SMS. Previously, SMS had request-rate limits but no monthly cap. After the quota is exhausted, creating a message returns HTTP
429with coderesource_limit_exceededand does not submit it. The quota resets at the start of each calendar month inAsia/Jerusalem. Application configuration responses includelimits.monthly_messages; Admin limits accept an override, with0meaning unlimited and omission selecting the default. Applications that need more than 3,000 submissions must increase this limit. A validated, authorized request counts when admitted for submission, including later submission failures and failed deliveries. Replaying a stored outcome or retrying an in-progress request with its original idempotency key does not count again. Retrying after a definitive provider refusal, or reusing a key after its seven-day expiry, creates a new counted attempt. Invalid or unauthorized requests and quota rejections do not count. One request consumes one slot, regardless of SMS segmentation. Update clients to handle the quota response and retry after the quota resets or the limit increases. The character-based accounting above replaces this initial per-request count. -
Monthly call and dial-attempt quotas accept
0for unlimited usage. In Admin application limits,monthly_call_minutes: 0removes the monthly connected-time cap for incoming and outgoing calls, andmonthly_dial_attempts: 0removes the outbound dial-attempt cap. Each setting is independent; usage continues to be counted. Previously,0selected the default quota of 10,000. Replace requests that used0to restore a default with10000. Omitting a quota from a replacement limits configuration still selects its default; omitting the entirelimitsobject on update preserves the existing settings. Responses include0for an unlimited quota. -
Each Session has its own limit of four concurrent recordings. A fifth
POST /v1/sessions/{uuid}/recordingsreturns HTTP 409 with codeconcurrent_recording_limit_reachedand starts no recording. Recordings waiting to start or finish count toward that Session limit. Other Sessions and Rooms do not count; a Room separately permits one active Room Recording at a time. Previously, Session recordings had no concurrent limit. If your application starts more than four, stop one recording, wait forrecording.endedorrecording.failed, then retry with a new idempotency key. If a 409 persists briefly after the event, retry after release completes. Replaying an already accepted request with its original key still returns its accepted response.
October 14, 2026
Fixed
- Session recordings retain audio from one-sided playback and the final audio fragment at stop. Audio captured while a Recording is masked stays silent after unmasking. Request and webhook schemas are unchanged; no migration is required.
Changed
- Session recordings continue when an audio queue fills. Audio that cannot
enter a full queue is dropped while the retained audio remains available.
Queue saturation alone no longer causes
recording.failed; after successful finalization,recording.endedcan refer to a recording with missing audio from the overloaded interval. Actual capture and file-write failures still reportrecording.failedwith no media URL. Event schemas are unchanged; consumers must not interpretrecording.endedas proof that every audio sample was captured.
October 13, 2026
Changed
- SMS delivery tracking is limited to queue acceptance after the provider
switch. When this change is deployed, new accepted messages remain
sending_status: queuedanddelivery_status: pendinguntil the 72-hour monitoring window ends withdelivery_status: unknown. The integration does not yet producemessage.sentor confirmeddeliveredorfailedupdates. Unresolved messages accepted before the switch also close asunknownif they have no saved final result. Request and response schemas are unchanged. If your application previously relied on confirmed delivery, handleunknownexplicitly and keep queue acceptance separate from recipient delivery. Do not resend automatically because delivery is unconfirmed. Authenticated delivery reports require further integration work.
October 4, 2026
Changed
- Breaking: Session playback no longer accepts
interrupt_digits. The old behavior let atype: filesrequest list keys that stopped that playback automatically. The replacement is application-controlled: handledtmf.received, then callPOST /v1/sessions/{uuid}/playback/stopwhen the key should stop audio. The explicit stop can cancel accepted playback beforeplayback.started; after it completes, submit the action selected by the key. Removeinterrupt_digitsfrom playback requests and move key selection into your application. The October 2 entry records the former capability.
Fixed
-
Live-stream playback startup is improved and deployed. Real HLS playback verification confirmed the startup improvement. Retrieving the source still affects when playback begins. Request and webhook schemas are unchanged; no integration change is required.
-
Explicit playback stop works before playback starts.
POST /v1/sessions/{uuid}/playback/stopcan cancel accepted playback beforeplayback.started. Stop dispatch does not wait for playlist preparation to finish; media admission is released after the platform confirms the stop. The normal terminal playback event still identifies the stopped operation. -
Completed playlists release media admission before resource cleanup. After a file playlist ends,
dtmf/collectcan begin while playback resources are being cleaned up, instead of returning repeated409 conflictresponses. Request and event schemas are unchanged; no migration is required. -
Recording completion resumes after a temporary failure without another call event. Pending recording completion and Session termination now retry after the dependency recovers, including when the call has already ended. Completion webhooks retain their original identities and ordering. Request and webhook schemas are unchanged; no migration is required.
-
Discarded empty Session recordings no longer block Session termination. A recording discarded at close because it contains no audio reports
recording.failedwith its original recording-createoperation_uuid, itsrecording_uuid, and no media URL. Temporary recording finalization failures still retry normally. Request and webhook schemas are unchanged; no migration is required.
October 3, 2026
Fixed
- Replacing or stopping Session playback keeps its stop outcome after a
delayed source-read failure. If the platform acknowledges the exact stop
before the playback completion reports a source failure, the stopped
operation now sends one
playback.stoppedwebhook instead ofplayback.failedwithfile_not_found. A source failure without that acknowledged stop still sendsplayback.failed. Correlate either terminal with the original playbackoperation_uuid. Request and event schemas are unchanged; no migration is required.
October 2, 2026
Added
- Session file playback can stop on selected keypad keys. Add
interrupt_digitsto atype: filesrequest atPOST /v1/sessions/{uuid}/playback/playto list the single DTMF keys (0–9,*, or#) that stop that playback. Other keys leave it playing. The normal Session DTMF event andplayback.stoppedwebhook still arrive; correlateplayback.stoppedwith the accepted playbackoperation_uuid. Omit the field to keep existing playback behavior. The option is unavailable for Room, push, stream, and silence playback.
Fixed
-
Inbound Session reads retain the call identity. After
session.created,GET /v1/sessions/{uuid}andGET /v1/sessionsreturn thecaller_idanddidreported by that event instead of empty strings. Response and webhook schemas are unchanged; no migration is required. -
Playback completion permits the next recording command. After receiving
playback.stopped, callers can stop a recording without a temporary409 conflictcaused by that completed playback. UsePOST /v1/sessions/{uuid}/recordings/{recording_uuid}/stopas before. Request and event schemas are unchanged; no migration is required. -
Room recordings can remain active after the last participant leaves. The configuration repair is deployed and verified in staging. Explicitly stopping a Room recording now produces a completed download in the verified occupied and empty-Room scenarios. Request and event schemas are unchanged; no migration is required.
-
Completed charge declines survive a lost live event. If the charge was declined and its live event was lost, Session cleanup now delivers
payment.charge.completedwith the sanitized decline details beforesession.ended. Request and event schemas are unchanged; no integration change is required. -
SMS delivery tracking survives concurrent submissions. An accepted SMS that joins a cohort while an earlier message is being reconciled remains tracked through its final delivery result or the 72-hour
unknownoutcome. Message and webhook schemas are unchanged. No integration change is required. -
Inbound calls survive routing updates. Calls admitted while routing changes propagate now continue into the normal Session flow, including
session.created, instead of being silently discarded. Request and event schemas are unchanged. No integration change is required. -
Room participant events follow the current Room visit. A delayed join signal from a previous Room, including an earlier visit to the same Room, no longer makes a Session appear joined or lets mute and unmute target another participant. Request and event schemas are unchanged; no migration is required.
-
Room playback start detection follows the current operation. A delayed start signal from a completed play can no longer confirm a subsequent play. A start signal that arrives immediately after the Room accepts its playback command is retained, so the start watchdog does not incorrectly fail a play that began. Requests, responses, and event schemas are unchanged. No integration change is required.
-
Room playback completion follows the originating operation. A delayed completion or failure signal from an earlier play no longer stops a subsequent play or sends its terminal webhook. A play that ends immediately after admission now reports its requested URLs in the terminal event. Event schemas are unchanged. No integration change is required.
October 1, 2026
Fixed
-
Inbound Session reads retain caller and destination numbers.
GET /v1/sessions/{uuid}now returns canonicalcaller_idanddidvalues for newly created inbound phone Sessions. Previously these fields could be empty even when thesession.createdwebhook contained both numbers. No request or response schema changes are needed. -
A rejected Room switch preserves the caller’s original membership. When the destination Room is full, the request returns
409 room_fullwithout removing the caller from the original Room, changing its participant count, or emittingroom.participant.left. A rejected command admission also preserves the original membership. Request and event schemas are unchanged; no migration is required. -
Answer webhooks no longer stall with low event-delivery concurrency. Previously, a successful call answer could leave
session.answeredundelivered when the delivery service was configured with concurrency 4. Applications now receive the answer event in that configuration. Request and event schemas are unchanged; no migration is required. -
Stopping a file playlist no longer waits for remaining files to load.
POST /v1/sessions/{uuid}/playback/stopcould previously return202 Acceptedwhile background preparation delayed the actual stop and itsplayback.stoppedevent. The platform now admits the stop independently of that preparation. The response remains asynchronous; useplayback.stoppedto confirm completion. Request and event schemas are unchanged. No migration is required.
September 30, 2026
Fixed
-
Inbound phone call setup no longer fails on affected routes. Some inbound calls could previously fail during setup because of an incorrect route selection. These calls can now connect. Request and webhook schemas are unchanged; no migration is required.
-
Hosted AAC and HLS playback accepts an explicit
format.POST /v1/sessions/{uuid}/playback/playpreviously returned400withinvalid_requestwhen atype: streamrequest includedformat: "aac"orformat: "hls", despite those values appearing in the API reference. The request now reaches playback admission. Applications can send either value or omitformatfor automatic detection; no migration is required.
September 29, 2026
Changed
- Seeking a live stream now returns
400 invalid_request.POST /v1/sessions/{uuid}/playback/seekandPOST /api/v1/rooms/{room_id}/playback/seekpreviously returned409 conflictwhiletype: streamplayback was active. They now return400 invalid_requestwith no seek operation admitted. Handle this response as400 invalid_requestrather than409 conflict, and usetype: filesfor playback that supports seek. Retrying a seek cannot make a live stream seekable.
Fixed
- Answering an already answered call now returns
409 Conflict. Previously, a newPOST /v1/sessions/{uuid}/answerrequest could return202 Acceptedand create an operation that never completed. New answer requests now return the stableconflicterror code without creating an operation. Answer admission requires an inbound phone Session in ringing or early-media state. Retry an admitted request with its originalIdempotency-Keyto replay the original response; no migration is required.
September 25, 2026
Fixed
-
Stopping a Room Recording now completes promptly. After the API accepts a request to stop a Room Recording, the corresponding
recording.endedevent is no longer delayed by the stop operation. Recording event payloads and API schemas are unchanged. No migration is required. -
Quiet outbound phone calls no longer end based on event silence. Previously, an active outbound phone Session with no lifecycle events for about 120 seconds could receive
session.ended. The platform now treats the call as ended only after a terminal outcome, or after the configuredmax_duration_secand a short close grace have elapsed; inactivity alone does not end it. Other Session types are unchanged. Event schemas and payloads are unchanged. No migration is required.
September 24, 2026
Fixed
-
Completed Recording events now link to available media. Session and Room
recording.endedevents are delivered only after the exact Recording has finished finalization. Previously, an event could contain amedia_urlthat returned404when finalization failed after the Recording closed. Temporary finalization failures are now retried before the event is delivered, and the event is still delivered exactly once. Event payloads and API schemas are unchanged. No migration is required; applications can fetchmedia_urlas soon as they receiverecording.ended. -
WebSocket Sessions now join Rooms before application audio starts. The last confirmed working Room join was September 11, 2026. The first confirmed failure was September 24, 2026 at 00:18 UTC: an accepted
POST /api/v1/rooms/{room_id}/participantsrequest returned202withstatus: "joining", but the WebSocket Session did not become a Room participant. Applications could write audio to the WebSocket while callers in the Room heard nothing.The request did not return an HTTP error or stable error code. The expected
room.participant.joinedwebhook for the WebSocket Session was absent; other Session lifecycle events continued normally. Request, response, and webhook schemas are unchanged. No migration is required.
September 22, 2026
Changed
-
Room playback no longer reports a
waitingstatus. After the API accepts a live stream playback request, the Room reportsstoppeduntil playback starts, then reportsplaying. Removewaitingfrom Room status handling and use the operation webhooks to track the accepted request before playback starts. -
Messages support SMS only.
POST /v1/messagesno longer acceptschannel: "fax", and the temporary PDF upload endpoints under/v1/filesare no longer available. Remove fax requests and file uploads from your integration. SMS request, resource, and event contracts are unchanged. -
Payment charge metadata length uses Unicode characters. The 128-character limit now counts Unicode code points instead of UTF-8 bytes. Existing ASCII input is unchanged, and names, addresses, or notes in languages such as Hebrew can use the full documented limit.
Fixed
-
Sequential DTMF collections report the terminator used by each operation. When one collection ended with
#or*and the next collection on the same Session ended after reachingmax_digits, the seconddigits.collectedevent could incorrectly repeat the first operation’sterminator. The second event now returns an emptyterminator, matching how that collection ended. Request and event schemas are unchanged, and no integration changes are required. -
Accepted Room live streams now report a terminal playback event. A live stream that ends emits
room.playback.ended. A source failure emitsroom.playback.failed. Previously, either source event could leave an accepted operation without the terminal webhook documented by the endpoint. Correlate the event with theoperation_uuidreturned by the202response. -
Generated API schemas match the values returned and accepted by the API.
GET /v1/sessions/{uuid}now documents the returned lifecycle field asstate; Recording creation lists only the supporteddirection: "both"andchannels: "mono"values; and a Nedarim charge amount is documented as whole ILS. Existing runtime behavior is unchanged for these fields. Regenerate clients that use these enums or response properties after the updated OpenAPI is published. -
The Idempotency guide documents the 24-hour replay window. It previously stated one hour and overstated which mutation endpoints support the header. Check each endpoint reference for
Idempotency-Keysupport.
September 18, 2026
Fixed
- Successful outbound faxes now reach their terminal Message status. After
the fax result confirms that every page was transferred, the Message changes
from
queued/pendingtosent/delivered. Itsfax.pages_sentandfax.delivery_extentfields and terminal delivery webhooks now reflect the completed transmission. No integration changes are required.
September 17, 2026
Fixed
-
Recording downloads use the Vorbal recording host on standard HTTPS. New
recording.became_availableevents usehttps://recordings.vorbal.devfor signedmedia_urlandlive_urlvalues, and newrecording.endedevents use it formedia_url. The URLs use port443without an explicit port. Previously, affected events usedhttps://recordings.telekesher.dev:30900; that host stopped resolving after the branding migration, so clients received a DNS failure before any HTTP response or stable API error code.Update network allowlists and any host validation to accept
recordings.vorbal.devon port443. Read the signed URL from each webhook instead of constructing or pinning its host. Request paths, signatures, expiration, and audio behavior are unchanged.
September 16, 2026
Added
- Outbound fax and temporary PDF uploads. Send and track fax Messages using a PDF URL from the Files API or another HTTPS source. Existing SMS integrations are unchanged; the Message list remains SMS-only.
Changed
-
Fax admission uses one queued event. Fax creation now returns
sending_status: "queued"instead of"accepted". Update handlers formessage.acceptedto usemessage.queued; starting transmission no longer emits another queued event. SMS behavior is unchanged. See Messages. -
Production API host. The canonical API base URL is now
https://api.vorbal.dev. Existing integrations that usehttps://api.telekesher.devremain supported temporarily for backward compatibility. Request paths, authentication, and API behavior are unchanged. Update API clients and network allowlists to usehttps://api.vorbal.dev.
September 14, 2026
Fixed
-
WebSocket Sessions remain connected after joining Rooms. From the first affected platform revision on September 8, 2026 at 04:19 Israel time until this fix, an accepted
POST /api/v1/rooms/{room_id}/participantsrequest withstatus: "joining"could end the WebSocket Session about two seconds after it joined. The last known-good platform revision predates that deployment; the first confirmed failure was September 14, 2026 at 12:39 Israel time.The request did not return an HTTP error or stable error code. The failure appeared as a terminal event such as
{"event":"session.ended","session_uuid":"00000000-0000-4000-8000-000000000101","hangup_cause":"completed","duration_seconds":0}. Request, response, and webhook schemas are unchanged. No migration is required.
September 13, 2026
Fixed
- Playback seek validation for active live streams. Seek requests to
type: "stream"Playback now return400 invalid_requestinstead of being incorrectly admitted as in progress.
September 10, 2026
Changed
-
Requests that accept only server-sent events now return
406 Not Acceptable. Public API endpoints previously ignoredAccept: text/event-streamand could return200with a JSON response. A request now returns406when itsAcceptheader requests an event-stream representation without permitting anapplication/jsonresponse. The response hasContent-Type: application/json, codeinvalid_request, and messageRequested response format is not supported; use application/json. The request is rejected before authentication, rate limiting, or endpoint processing.A mixed
Acceptheader still receives the endpoint’s normal JSON response when content negotiation permits JSON throughapplication/json,application/*, or*/*at a positive quality value. An exactapplication/json;q=0range makes JSON unacceptable even if a broader wildcard has a positive quality value.Migration: send
Accept: application/jsonfor API responses, and removetext/event-streamif the request doesn’t accept JSON. Use signed webhooks instead of server-sent events to receive lifecycle updates.
September 7, 2026
Changed
-
Breaking:
POST /v1/sessions/{uuid}/payment/chargesrequires merchant and customer metadata forprovider: "nedarim". Three request fields are now required, and three optional fields were added:- required:
category,customer_name,customer_address - optional:
customer_email,customer_phone,note
Each is a string of at most 128 characters with no control characters. The three required fields must be present but their value may be an empty string. They are forwarded verbatim to Nedarim (
Groupe,ClientName,Adresse,Mail,Phone,Avour).Nedarim’s
Onlineacquiring path rejects card-not-present charges that reach it from a data-center source address without customer identification, so a realcustomer_nameandcustomer_addressare needed for charges to clear in that environment even though the provider marks the fields optional.Migration: add
category,customer_name, andcustomer_addressto every charge request. Send the customer’s name and address where you have them; an empty string is accepted by the API but may be declined by the acquirer. The platform does not receive, store, or log a card number, security code, or identity number — these fields are ordinary charge metadata. - required:
Fixed
- Recording stops now emit
recording.ended. Explicit Session recording stops again deliver their terminal webhook.
September 6, 2026
Changed
-
Breaking: all
Idempotency-Keyreplay windows are now 24 hours. The platform now applies one retention window to every endpoint that supportsIdempotency-Key. General API mutations and secure-payment charges increase from one hour to 24 hours; message creation decreases from seven days to 24 hours.Operation-Idremains a separate correlation mechanism, including its existing 10-minute duplicate-admission behavior on supported media controls.Reuse the same key only for the same logical request and retry it within 24 hours. After that window, the key is treated as new and the operation may run again. In particular, reconcile message and payment status before retrying an expired request, because a new submission can create another billed external side effect. Existing keys expire according to the TTL assigned when they were created; the uniform window applies to newly admitted keys.
-
Breaking: Room members are now Room participants. The Room API now uses participant terminology consistently and does not retain compatibility aliases. Update integrations as follows:
/api/v1/rooms/{room_id}/members→/api/v1/rooms/{room_id}/participants/api/v1/rooms/{room_id}/members/{uuid}→/api/v1/rooms/{room_id}/participants/{uuid}- response field
members→participants member_count→participant_count- application setting
limits.max_members_per_room→limits.max_participants_per_room room.member.*webhooks →room.participant.*- volume directions
to_memberandfrom_member→to_participantandfrom_participant
The Session UUID remains the participant identity, and request/response behavior is otherwise unchanged. Migrate every endpoint, field, webhook subscription, event handler, and volume-direction value before upgrading.
September 3, 2026
Changed
-
Provider-authored decline messages were removed from charge webhooks. The
error.messagefield is no longer emitted inpayment.charge.completed. Provider text can contain sensitive card data, so the platform now discards it at the payment-vault boundary. Integrations that displayed this optional field must use their own copy based on the stableerror.type,error.code, anderror.decline_codevalues instead. -
Room member removal is now physically ordered.
DELETE /api/v1/rooms/{room_id}/members/{uuid}still returns202withstatus: "leaving", but it no longer emitsroom.member.leftor updates the room member count at request admission. The webhook and count update are committed only after the underlying media layer confirms the matching member has left the requested room. Correlate the webhook by bothsession_uuidandroom_id; retries and duplicate media events produce at most one webhook and one count decrement. Integrations that treated the202response as proof of physical departure must wait forroom.member.leftinstead.
Fixed
-
Answered Session durations no longer report zero because of a timestamp conversion error.
session.ended.duration_secondsreports whole seconds from answer to termination, excluding ringing time. -
Playback can start again after a previous playback has finished.
POST /v1/sessions/{uuid}/playback/playcould return503with codeunavailablefor the rest of a Session after an earlier playback completed. This also affected flows that collected digits between the two playbacks. Completed playback now releases its ownership so later playback requests can be accepted normally. Request and webhook schemas are unchanged. After the fix is deployed and verified in your environment, remove any workaround that usesdtmf/collectsolely to play an announcement. -
Live push playback emits an empty
urlsarray when playback starts. Aplayback.startedwebhook for live push playback now returnsurls: [], as required by the webhook schema. It previously returnedurls: null. Live push playback has no URL-based sources, so consumers should continue to parseurlsas an array. No migration is required. -
Secure-payment terminal webhooks release collection ownership before delivery. When
payment.card.collectedorpayment.cvv.collectedbecomes visible to your webhook handler, the completed secure collection no longer owns the Session’s media controls. You can immediately submit the next media or DTMF collection command from that handler without receiving a transient conflict from the completed collection.Webhook schemas and outcomes are unchanged. Continue to correlate events by
operation_uuid, deduplicate at-least-once delivery, and reuse the sameOperation-IdandIdempotency-Keyfor an exact request retry. No migration is required.
September 2, 2026
Changed
-
Breaking: secure-payment collections accept at most four prompt files.
POST /v1/sessions/{uuid}/payment/card/collectandPOST /v1/sessions/{uuid}/payment/cvv/collectpreviously accepted up to 128 entries inprompt_files; they now accept up to four. If a flow uses more than four prompt files, concatenate them into at most four WAV or MP3 files before submitting the collection request.The platform checks the complete prompt list within one eight-second deadline and applies an application-wide admission budget to these outbound checks. A burst that exceeds the budget returns
429 Too Many RequestswithRetry-After. Retry later with the sameOperation-Idand, if you supplied one, the sameIdempotency-Key. -
New idempotency keys have an application-wide allocation budget. Exact retries of an existing
Idempotency-Keydo not consume this budget. A burst of high-cardinality new keys can return429 Too Many RequestswithRetry-After. Reuse one key for every retry of the same logical request and create a new key only for a new operation. Rejected requests do not retain a replay result; an uncertain server failure remains protected from duplicate execution. -
Payment state is retained for at most one hour. The platform previously retained some sanitized collection, charge-recovery, dispatch, and deduplication records for up to 24 hours. It now applies the same one-hour limit to all payment-specific records, including the
Idempotency-Keyrequest identity and sanitized HTTP result. State transitions don’t restart the one-hour window, and delayed internal processing can’t recreate an expired payment workflow.Reuse the same
Idempotency-KeyandOperation-Idfor exact retries only during the one-hour window. After it expires, the HTTP result is no longer replayable and the old operation is no longer available for recovery or dispatch fencing. Reconcile the terminalpayment.charge.completedwebhook before taking further action. If you intentionally start another charge, collect new card and CVV references and use a newOperation-IdandIdempotency-Key.
Fixed
- Outbound
session.createdclassification. Sessions created byPOST /v1/sessions:dialnow emitsession.createdwithtype: "phone_out", the originating number incaller_id, and the destination number indid. These events previously reportedtype: "unknown"with emptycaller_idanddidvalues. Inboundsession.createdbehavior is unchanged.
August 31, 2026
Added
-
Secure card payments. Use
POST /v1/sessions/{uuid}/payment/card/collectandPOST /v1/sessions/{uuid}/payment/cvv/collectto receive call-scoped references without receiving card or CVV digits. A successfulpayment.card.collectedwebhook includes acard_uuidreference starting withcard_and safe display values underpayment_method_details.card.last4and.brand. A successfulpayment.cvv.collectedwebhook includes acvv_uuidreference starting withcvv_. Invalid input produces a sanitized outcome, such asreason: "luhn_check_failed", without disclosing the rejected digits.Secure collection and Room membership are mutually exclusive. Starting collection for a Session that is a Room member, or adding a Session to a Room while collection is active, returns
409 Conflict. While collection is active, the platform also rejects new Recordings andunmaskrequests. After the terminal collection webhook arrives, each Recording that the platform masked or paused remains in that state; call the Recording’sunmaskendpoint when the application is ready to resume writing sensitive-free audio.Submit the opaque references to
POST /v1/sessions/{uuid}/payment/chargeswhile the Session is connected. Includeexp_month,exp_year, a positiveamountin major currency units, lowercasecurrency: "ils",provider: "nedarim", and the customer’s seven-digitterminal_number. For ILS,amountmay have at most two fractional digits; excess precision is rejected rather than rounded. Providers can impose narrower limits. Nedarim currently accepts only whole-ILS charges, so use a whole number such asamount: 18with that provider.Idempotency-Keyis optional. Supply one unique key per logical charge and reuse it for retries to receive one-hour HTTP replay protection. Omitting the key disables retry deduplication, so repeating the request can create another charge attempt.A charge’s first provider attempt atomically consumes its
card_uuidandcvv_uuid. An exact replay with the sameIdempotency-Keyreturns the original sanitized HTTP result and terminal outcome. A new operation cannot reuse either reference, so collect new card and CVV references before another charge attempt. An unused reference expires no later than one hour after collection, and Session cleanup normally removes it sooner.The terminal
payment.charge.completedwebhook reports the trusted amount, currency, provider, terminal number, status, and retryability. A successful result can also include the transaction creation time,receipt_created, validated card details, installment count, and normalized transaction type, card classification, issuer, and acquirer. Usestatusand the originatingoperation_uuidto correlate the outcome. The webhook excludes provider transaction identifiers, authorization codes, voucher numbers, UIDs, raw responses, provider codes, PAN, CVV, expiry, identity numbers, customer PII, receipt URLs, and bearer capabilities. A decline can include anerrorobject with Stripe-compatibletype,code, anddecline_codevalues when a safe closed mapping is known. Provider-authored text is discarded and is never included in the webhook. Observed Nedarim error004maps tocard_error,card_declined, andgeneric_decline; the raw provider code is not exposed.The platform durably stages each accepted collection result. A service interruption or Session end completes the staged result, or emits a terminal failed result if collection cannot be recovered. A charge timeout is reported as
indeterminatewithretryable: falseand is never automatically retried.
Changed
-
Nedarim terminals must be assigned to the calling application. Continue sending the customer’s seven-digit
terminal_numberwith every charge; the value is not a secret and is not replaced by a server-side default. The platform now verifies that the terminal is assigned to the authenticated application before accepting the charge. An unassigned terminal returns403 Forbiddenwithout creating a charge operation. Ask the platform operator to assign each customer’s terminal before sending its first charge. -
Every accepted charge retains a terminal outcome across Session end. If the Session hangs up or a service restarts before provider processing begins, the platform emits a terminal failed outcome. If provider submission already began and the result cannot be proven, it emits
indeterminatewithretryable: false; a stored sanitized completion remains replayable. Do not create a replacement charge merely because the Session ended. Wait for and deduplicatepayment.charge.completedbyoperation_uuid.
Fixed
- Standalone WebSocket lifecycle webhook delivery. The platform again
delivers
websocket.connected,websocket.disconnected,websocket.failed, andwebsocket.audio_playback_completedfor standalone WebSocket Sessions. The last confirmed working configuration was August 18, 2026 at 01:31 Israel time; the first affected configuration was August 21, 2026 at 04:30 Israel time. During that interval, these events could be omitted before webhook delivery, so there was no webhook HTTP response or stable error code. For example, an expected redacted event such as{"event":"websocket.disconnected","session_uuid":"00000000-0000-4000-8000-000000000101"}could be absent. Missed historical events are not replayed. No request or webhook schema changed, and no migration is required; continue normal webhook handling and at-least-once deduplication.
August 30, 2026
Added
-
Optional inclusion of recording gaps.
POST /v1/sessions/{uuid}/recordingsnow accepts the optional booleaninclude_gaps, which defaults tofalse. Omit it or set it tofalseto remove redacted periods and periods without recordable audio from the artifact. Set"include_gaps": trueto include those periods as silence. The setting applies only to that Recording and is returned by the Recording list and inspection endpoints. Existing requests do not need to change. -
Session hold-state webhooks. Applications now receive
session.heldwhen a Session enters hold andsession.resumedwhen it returns from hold. Both events carrysession_uuidin addition to the standard webhook fields. Existing integrations do not need to change. Add either event to your webhook handler when your application needs to track whether a Session is on hold. -
MP3 file playback. Session Playback, Room Playback, and optional DTMF collection prompts now accept finite HTTP or HTTPS MP3 URLs in addition to WAV URLs. Submit the original
.mp3URL inurlsorprompt_files; no client-side conversion or alternate filename is required. Existing WAV integrations are unchanged. -
Opt-in Room voice-activity events.
POST /api/v1/roomsnow accepts the optional booleanvoice_activity_events. Set it totruewhen the Room must emitroom.member.voice_activity_changedas members start and stop talking. It defaults tofalse, so existing Room creation requests continue to work without receiving these high-frequency events. The Room resource reports the selected value. The event was previously documented without an activation control, although Rooms did not emit it. If your integration was built from that earlier documentation and expects voice-activity events, add"voice_activity_events": truewhen creating each Room; existing Rooms must be replaced with Rooms created with the option enabled. -
Room-member volume results.
POST /v1/sessions/{uuid}/room/volumenow reports its asynchronous result through exactly one terminal webhook.room.member.volume_changedconfirms that every requested direction was applied.room.member.volume_change_failedreports an execution failure and includesapplied_directionsandfailed_directions, so adirection: bothrequest can report a partial result without implying an atomic two-direction change. Both events include the submittedvolumeanddirection, plus thesession_uuid,room_id, and originatingoperation_uuid. -
Reject inbound calls or cancel outbound dialing before answer. Use
POST /v1/sessions/{uuid}/rejectwith a requiredreasonofnumber_not_found,invalid_number,timeout,busy, orrejectedfor an unanswered inbound phone call. UsePOST /v1/sessions/{uuid}/cancelto stop an outbound phone call before the callee answers. Both operations return202 Accepted, followed by the existingsession.endedwebhook. For a rejection, that webhook reports the selectedreasonunchanged ashangup_cause. Both operations return409 Conflictfor an incompatible Session or lifecycle state. UseDELETE /v1/sessions/{uuid}when you want to disconnect regardless of answer state.
Changed
-
Session Playback request variants are now named separately in OpenAPI. The
POST /v1/sessions/{uuid}/playback/playrequest remains oneoneOfcontract, with named schemas for file playback, pushed audio, and silence. Generated-audio uploads useSessionPlaybackPushRequest. This schema-organization change does not alter endpoint URLs or wire payloads beyond the changes documented separately below, and requires no additional migration. -
Breaking: generated-audio Session Playback uses
type: pushand an HTTPS streaming upload.POST /v1/sessions/{uuid}/playback/playnow distinguishes audio pushed by the application from live HTTP/HTTPS URL sources. Sendtype: pushwith the raw audio format. The202 Acceptedresponse includes aningestobject with the short-lived URL, required HTTP method and headers, and expiry. Follow that object to stream raw mono audio in one request body. Authenticate the ingest request with the same application Bearer credentials that you use for other Voice API requests. The response does not repeat those credentials iningest.headers. There is no compatibility alias: a Session request that usestype: streamwithmedia_formatnow returns400 Bad Request.Replace flat upload metadata with the nested ingest contract:
The initial documentation described the upload as a WebSocket and later as a streaming
PUT. Replace either transport with the returned ingest contract:Supported formats are little-endian PCM16 at 8, 16, 24, or 48 kHz and G.711 mu-law at 8 kHz. The body is unframed raw audio. Close it after the final audio bytes. A 2xx ingest response confirms that the platform accepted the complete request body; it does not confirm that the caller heard all audio. The Voice API verifies the Playback operation before it accepts the upload. Do not send Voice API credentials to any URL other than the Voice API origin that your integration is configured to use.
type: streamremains the discriminator for live HTTP/HTTPS URL playback, such as radio. Room Playback keeps its{"type":"stream","url":"https://..."}request shape; live Room streams remain unsupported and return502. This entry supersedes the earlier August 22 statement that live streaming upload was not part of the contract. -
Breaking: Session recordings are addressable resources. A Session can now own multiple concurrent recordings, including recordings with identical settings. Each accepted create request returns a new
recording_uuid, and subsequent inspection and control requests address that exact Recording. There is no singleton Recording, implicit “current recording,” or stop-all operation. Update endpoint usage as follows:The old endpoints have been removed without compatibility aliases. Persist the
recording_uuidreturned by the create request and use it for later commands. UseGET /v1/sessions/{uuid}/recordingsto reconcile active or stopping recordings, orGET /v1/sessions/{uuid}/recordings/{recording_uuid}to inspect one live Recording. These endpoints do not provide recording history.Create requests now require
direction(both,session_in, orsession_out) andchannels(monoorstereo). The initial release accepts only{"direction":"both","channels":"mono"}; other valid combinations return HTTP400as unsupported.Voice-activity events remain optional on each create request and default to disabled. Each Recording configures voice-activity detection independently, so multiple concurrent Recordings may enable events with different
voice_activity_configvalues. Each Recording receives its own events, correlated byrecording_uuidand the originatingoperation_uuid. Stopping one Recording stops only its events and does not affect the other active Recordings. -
Breaking: WebSocket failure reason renamed. The
websocket.failedwebhook now reportsreason: connection_precondition_failedinstead ofrelay_precondition_failedwhen the platform cannot satisfy a prerequisite for opening the WebSocket Session connection. Update enum validation and failure handling to use the new value; there is no compatibility alias. The event name,errorbucket, HTTP status semantics, and other reason values are unchanged. -
Room-wide webhook ordering. The platform now delivers all
room.*events for one Room in production order. Failures block only that Room. Payloads are unchanged, and no migration is required. -
Breaking: Room member volume directions. The
directionfield inPOST /v1/sessions/{uuid}/room/volumerequests and the correspondingdirection,applied_directions, andfailed_directionswebhook fields now use names relative to the Room member. Renameintofrom_memberandouttoto_member;bothis unchanged.from_memberis the member’s audio sent into the Room, whileto_memberis the Room mix sent to the member. WebSocket media-format fields remainfrom_sessionandto_sessionbecause those directions are relative to the Session. -
Silence playback admission is fully asynchronous.
POST /v1/sessions/{uuid}/playback/playwithtype: silencenow returns202 Acceptedafter durable workflow admission, without waiting for the earlier playback stop to complete. The platform still stops existing playback before starting indefinite silence, but performs that sequence in the background and reports progress or failure through the existing playback webhooks. Treat202as admission only. A silence request no longer returns503solely because confirmation of the preceding playback stop was not received during the HTTP request. The background replacement now waits for the preceding playback to finish stopping before it starts the replacement; this fixes replacements that could be accepted and reportplayback.startedwhile remaining silent. Rapid successive replacements continue to be accepted and converge on the latest request. No request, response, or webhook schema changed, and applications do not need to migrate. -
Playback start webhook timing. For file playback, the platform now publishes
playback.startedat a later point in startup, using a best-effort estimate that playback of the first source has begun. Previously, the event followed an earlier source-preparation signal.
Fixed
-
One logical terminal Session event per call. The platform now creates one
session.endedevent when multiple termination signals or recovery checks observe the same call ending. The event uses the publicsession_uuid; an internal call identifier is no longer emitted as a separate Session event. The event schema is unchanged, and no migration is required. Webhook delivery remains at least once, so continue to deduplicate deliveries by(aud, Webhook-Id). -
Terminal event after unrecovered playback media failure. If playback media fails after a request is accepted, the platform first attempts to recover it. A successful recovery remains transparent. If recovery is exhausted, the platform now emits the operation’s existing terminal event:
playback.failedwithreason: media_unavailablefor Session playback,room.playback.failedfor Room playback, ordigits.collectedwithstatus: failurefor prompt-and-collect. Failures before acceptance remain synchronous HTTP errors and do not emit these webhooks. Treat webhook delivery as at least once and deduplicate by(aud, Webhook-Id).
August 29, 2026
Changed
- Breaking: recording media URL renamed. Recording API responses and
webhook payloads now expose
media_urlinstead ofpull_url. Update your response and webhook models to readmedia_url; there is no compatibility alias. The URL behavior and expiry are unchanged: a GET starts at byte zero and follows the growing recording until EOF. After finalization, the same URL serves a finite, range-capable WAV.live_urlis unchanged and continues to join near the current position.
August 28, 2026
Added
- SMS Messages API. Send outbound SMS with
POST /v1/messages, retrieve a message’s latest status withGET /v1/messages/{message_uuid}, and list messages withGET /v1/messages. Use Message webhooks for status updates.
Changed
-
Breaking: retry timing moved to the standard response header. Public HTTP error response bodies no longer include the top-level
retry_afterfield. Retry timing remains available in the standardRetry-Afterresponse header for429rate-limit and529overload responses. Those JSON bodies now keep the uniformcodeandmessageshape, and their human-readable messages end withSee Retry-After header.Update your response model and retry handling as follows:
Continue to branch on the stable
code, not the human-readablemessage. This change does not remove unrelated error fields: playback source failures retaindetail, and unexpected500responses retainsupport_id.
Fixed
- Session command bodies with idempotency keys. Session commands that send
a nonempty JSON request body with
Idempotency-Keyno longer reject the valid body as invalid JSON.
August 27, 2026
Added
- Read application configuration.
GET /api/v1/appreturns assigned inbound phone numbers, permitted outbound caller IDs, webhook URL, blocked webhook events, and effective Room limits. Useinbound_phone_numbersandoutbound_caller_idsto populate your own number-assignment interface. TheGEToperation is read-only: it returns assigned numbers and configuration but does not assign numbers. Existing integrations do not need to change.
Changed
-
Breaking: application configuration replaces webhook-specific routes. Replace
GET /api/v1/app/webhookwithGET /api/v1/app, and replacePUT /api/v1/app/webhookwithPATCH /api/v1/app. Use the PATCH request to setwebhook_url. A successful update returns204without a response body. Use the generic GET operation to verify the saved setting. The removed routes have no compatibility aliases. -
Breaking: HTTP response fields standardized. All Voice Platform HTTP API responses, including successful and error responses, no longer include the transport-level
successfield. Use the HTTP status code to determine whether a request succeeded or failed, and removesuccessfrom your response models and checks.Error responses now use top-level
codeandmessagefields. Rename the previouserrorstring tomessage; for example, change{"success":false,"error":"...","code":"invalid_request"}to{"code":"invalid_request","message":"..."}. Unexpected-error fields are also top-level, so change{"error":{"code":"internal_error","message":"...","support_id":"..."}}to{"code":"internal_error","message":"...","support_id":"..."}. At the time of this change,detailandretry_afterremained available when applicable. Genuine domain lifecyclestatusfields are unchanged. -
Breaking: specific Authorization header error code. A missing or malformed application
Authorizationheader now returns HTTP400with codeinvalid_authorization_headerinstead ofinvalid_request. Update any error handling that matches these header failures by code. Themessageidentifies whether the header is missing or invalid and shows the expected format:Bearer <app_uuid>:<api_key>. Use the app UUID and API key issued to your application. A credential with that structure but a rejected API key remains HTTP401 unauthorized; other invalid requests remaininvalid_request. -
Breaking: one request-validation error per response. Request-schema validation failures now return exactly one issue with top-level
codeandmessagefields. The message identifies the relevant request path and explains how to correct it. For example:{"code":"invalid_request","message":"The \"to\" phone number must match E.164 format"}.Validation responses no longer include
success,error, bodystatus,detail,errors,param,location, orvalue. Readcodeandmessageand stop parsingdetail.errors. When a request has multiple issues, fix the returned issue and retry to reveal a later issue. The platform selects the returned issue deterministically according to the product contract, not the validator’s raw finding order. -
JSON request header compatibility. Continue to send
Content-Type: application/jsonwith JSON request bodies. For compatibility, the platform now tolerates a missing or blankContent-Typeheader and attempts JSON parsing. This does not add another supported request media type. An explicitly supplied unsupported or malformed media type is still rejected with HTTP415. -
Clearer Dial validation errors. Invalid request bodies now return
422 application/jsonwith one readablemessage. Runtime phone-policy errors remain400 invalid_request.
August 26, 2026
Changed
- Choose when recording starts.
POST /v1/sessions/{uuid}/recording/startnow accepts the optional booleanstart_only_after_answer. Omit it or set it tofalseto start as early as media is available; the recording may include ringing or early media. Set it totrueto wait for canonical answer.
August 24, 2026
Changed
- Breaking: standalone WebSocket event names. Standalone WebSocket Session
lifecycle events now use the public
websocket.*namespace instead of the removed sidecar relay’s internal name. Update webhook filters as follows:websocket_relay.connected→websocket.connectedwebsocket_relay.disconnected→websocket.disconnectedwebsocket_relay.failed→websocket.failedwebsocket_relay.audio_playback_completed→websocket.audio_playback_completedThis pre-launch change has no compatibility aliases. Payload fields and delivery behavior are unchanged.
August 23, 2026
Added
- G.711 mu-law relay audio. WebSocket relay
media_format.from_sessionandmedia_format.to_sessionnow independently acceptencoding: "mulaw"with the requiredsample_rate: 8000. Binary messages contain raw mono G.711 mu-law bytes without a WAV header, JSON, or base64 wrapping. Existingpcm_s16leintegrations do not need to change. This adds the audio encoding only; it does not implement the Twilio Media Streams message protocol. - Custom WebSocket ports. External WebSocket URLs now accept any TCP port
from
1through65535for bothws://andwss://. Ports other than the scheme defaults must be explicit. You can omit the port:ws://defaults to80, andwss://defaults to443. This applies to thewebsocketobject accepted byPOST /v1/sessions:connectWebsocket. You can connect a standalone WebSocket Session directly to a public server on a custom port, such aswss://media.example.com:8443/audio. This change supersedes the August 21 requirement to include an explicit default port.
Changed
-
Breaking: sidecar WebSocket relay removed. The API no longer registers
POST /v1/sessions/{uuid}/websocket-relay,DELETE /v1/sessions/{uuid}/websocket-relay, or thePOSTroutes ending in/pause,/resume,/mute,/unmute, and/flush. Requests to those paths return404, and the operations are absent from the OpenAPI endpoint reference. This pre-launch removal has no compatibility alias or deprecation period. For passive call audio, use the Recording API and its signedmedia_urlorlive_url. For a bidirectional agent, create a standalone WebSocket Session and add it with the phone Sessions to a Room. For one-way audio, use Session or Room Playback. Live streaming upload is not part of the current contract. -
Breaking: Finite-file requests to
POST /api/v1/rooms/{room_id}/playback/playnow requiretype: "files". The endpoint no longer treats an omittedtypeasfiles; requests withouttypereturn HTTP400with codeinvalid_request. Change{"urls":[...]}to{"type":"files","urls":[...]}. Requests for a live stream continue to requiretype: "stream"andurl. -
Breaking: explicit request choices. The API no longer supplies default values for the non-boolean request fields below. Requests that omit a required field return HTTP
400with codeinvalid_request. To preserve the previous behavior, send the former value explicitly:For DTMF collection, an explicit
"terminators":[]now disables terminators. Send"terminators":["#"]to preserve the previous behavior. Boolean fields whose omitted behavior isfalseremain optional. This includesenable_voice_activity_events,muted,loop, andstart_muted. Sendtrueonly when you want to enable the corresponding behavior.
August 21, 2026
Added
- HTTP/2 support. The public Voice API edge now negotiates HTTP/2 over TLS with ALPN and keeps HTTP/1.1 connections reusable. Existing HTTP/1.1 integrations do not need to change requests or authentication. Clients can use HTTP/2 multiplexing or reuse one HTTP/1.1 connection instead of opening a connection per request. HTTP/3 is not supported by this edge.
Changed
- Breaking: playback source errors. Synchronous
502failures while admitting a playback or DTMF prompt source now returncode: playback_source_errorinstead of the genericcode: unavailable. The response includes bounded diagnosticdetail:reasonis one ofunsupported,not_found,access_denied,timeout,unreachable,rejected,invalid_response, orpreparation_failed.source_indexidentifies the failing zero-based item when the request contains multiple URLs.retryablesays whether the same source may succeed if retried later.upstream_statuscontains the source origin’s HTTP status when one was received. Update integrations that branch oncode: unavailablefor these502responses to branch onplayback_source_errorand usedetail.reasonfor handling. The API does not return source URLs, credentials, raw upstream response bodies, filesystem paths, or media-engine details.
Fixed
- Plaintext webhook and WebSocket URLs. Webhook delivery URLs accept both
http://andhttps://, and WebSocket relay URLs accept bothws://andwss://. Plaintext schemes do not require an operator opt-in flag. Relay URLs must include a nonempty path. At the time of this change, relay URLs also required an explicit default port. The August 23 change supersedes that port requirement. Existinghttps://webhook andwss://relay configurations do not need to change. Usehttps://andwss://unless you explicitly accept exposing webhook payloads and signature headers, or call audio and relay connection headers, without transport encryption. Both URL types remain subject to address-safety checks; the host must resolve to a permitted public address.
August 20, 2026
Added
- WebSocket relay failure reasons. The
websocket_relay.failedwebhook event now carries areasonfield: a closed, stable enum that classifies why the relay could not be established. It is delivered alongside the existingerrorandhttp_statusfields, which are unchanged. Existing consumers that only readerrorandhttp_statusdo not need to change. Thereasonvalues are:session_codec_unsupported— the active call is not carried in an audio codec the relay supports. This is a platform-side precondition, not a problem reaching your WebSocket server.destination_unreachable— the target WebSocket server could not be reached (DNS resolution, TCP connection, or routing failed).tls_failed— the TLS handshake to the target WebSocket server failed.handshake_rejected— the target returned a non-success HTTP status to the WebSocket upgrade request.relay_precondition_failed— a precondition for starting the relay was not met. Switch onreasonfor actionable handling rather than parsingerror. Treatsession_codec_unsupportedas a platform-side precondition failure, not a network problem on your side.
Fixed
- Faster finite WAV playback start. Session Playback for finite PCM WAV
files now starts without first reading the complete file. Existing playback
requests do not need to change;
playback.startedmay arrive sooner, especially for larger files or files hosted on slower origins.
August 19, 2026
Changed
- Breaking: caller-supplied operation IDs. For applicable asynchronous
commands, move caller-supplied correlation from body or query
operation_uuidto the optionalOperation-Idheader. Responses and webhooks continue to useoperation_uuid;Idempotency-Keyremains separate for retry deduplication.
Fixed
- Sidecar WebSocket playback limitation. The API reference now documents a playback limitation: you cannot play a file while a sidecar WebSocket is connected to the same Session. We are working to remove this limitation. Disconnect the sidecar WebSocket before starting Session Playback. To play both sources at once, add a standalone WebSocket Session and the phone Session to the same Room, then use Room Playback.
- JSON route errors. Unknown public paths and unsupported HTTP methods on
known public paths now return the standard JSON error envelope with
Content-Type: application/jsoninstead of plain text. A route-level404usescode: not_found; a405usescode: invalid_request. Integrations that special-cased these plain-text responses can now parse them like other API errors and branch oncode.
August 15, 2026
Changed
- Breaking: Standard Webhooks signing. Webhook delivery now follows
Standard Webhooks with Ed25519.
Update delivery headers and verification as follows; the former headers and
signing input are no longer sent or accepted:
Idempotency-Key(delivery) →Webhook-Id- signature timestamp field
t=→Webhook-Timestamp X-Webhook-Signature: t=...,akid=...,v1a=...→Webhook-Signature: v1a,...X-Webhook-App-UUID→ required signed body claimaudv1a\n<t>\n<app_uuid>\n<idempotency_key>\n<raw_body>→<webhook-id>.<webhook-timestamp>.<raw-body>Requireaudto equal the configured application UUID after signature verification and before side effects. During key rotation,Webhook-Signaturecan contain two space-delimitedv1asignatures; accept when at least one verifies against the active public-key set. The incoming API request headerIdempotency-Keyis unchanged.
- Breaking: WebSocket relay resource. The per-session WebSocket relay is
now a singleton resource.
Update request URLs and methods as follows; the former command routes are no
longer accepted:
POST /v1/sessions/{uuid}/websocket/start→POST /v1/sessions/{uuid}/websocket-relayPOST /v1/sessions/{uuid}/websocket/stop→DELETE /v1/sessions/{uuid}/websocket-relayPOST /v1/sessions/{uuid}/websocket/{pause|resume|mute|unmute|flush}→POST /v1/sessions/{uuid}/websocket-relay/{pause|resume|mute|unmute|flush}
- Relay deletion requests. Send relay deletion without a request body. To
supply your own correlation
identifier, send the optional request header
Operation-Id: <lowercase-uuid-v4>; otherwise, the platform generates one. The202 Acceptedresponse returns the selected value in itsoperation_uuidfield.Operation-Idis the only request input that selects this correlation value; unrelated query parameters receive no special handling. Continue to useIdempotency-Keyseparately when retrying the same deletion. - Breaking: WebSocket text messages. Custom text messaging over the
WebSocket relay is no longer
supported in either direction. The removed
final_textdeletion property has no replacement, andwebsocket.message_receivedis removed without a replacement event. Exchange application media as binary audio frames only. A backend text frame that is not a platform-reserved relay control closes the WebSocket connection with code1003; reserved controls are internal and are not an application extension point. - WebSocket relay event names. WebSocket relay lifecycle events moved from
the generic
websocketnamespace to the relay resource namespace. Update webhook filters as follows:websocket.connected→websocket_relay.connectedwebsocket.disconnected→websocket_relay.disconnectedwebsocket.failed→websocket_relay.failedwebsocket.audio_playback_completed→websocket_relay.audio_playback_completed
- Superseded: webhook delivery IDs. This intermediate pre-launch contract
moved the platform-generated delivery key from
X-Idempotency-KeytoIdempotency-Key. Under that contract, the key value and Ed25519 signing preimage were unchanged, and the fourth preimage line remained the unmodified delivery-key value. The delivery header was distinct from the client-generatedIdempotency-Keyon supported API mutation requests. The Standard Webhooks change above superseded this contract; useWebhook-Idfor the final delivery header.
August 12, 2026
Added
- Room Playback correlation. Every Room Playback command now accepts an
optional client-generated
lowercase UUID v4 as
operation_uuid. When supplied, the API echoes that exact value in the command’s202 Acceptedresponse; when omitted, the platform generates one. Use it for correlation, not request deduplication. - Idempotent Room Playback pause and stop.
POST /api/v1/rooms/{room_id}/playback/pauseandPOST /api/v1/rooms/{room_id}/playback/stopnow supportIdempotency-Key, completing idempotent retry support across Room Playback commands. Keepoperation_uuidstable for correlation and reuseIdempotency-Keyseparately when retrying the same request.
Changed
- Breaking: WebSocket media format. WebSocket relay requests now require a
media_formatobject for each audio direction. This applies toPOST /v1/sessions/{uuid}/websocket/startand thewebsocketobject accepted byPOST /v1/sessions:connectWebsocket. Replace the removed fields as follows:from_session_audio_rate→media_format.from_session.sample_rateto_session_audio_rate→media_format.to_session.sample_rate
- Required PCM encoding. Add
encoding: "pcm_s16le"to both directions. Both directions, encodings, and sample rates are required, with no defaults. Sample rates must be multiples of8000from8000through192000. Old flat fields and other encodings are rejected; additional encodings are planned but not yet accepted.
Fixed
- Room Playback terminal correlation.
room.playback.endedandroom.playback.failednow always carry a non-empty, lowercase UUID v4operation_uuidfor the originating play request. The platform suppresses a malformed terminal event instead of delivering one whose correlation identifier is missing or invalid. Consumers may validate this required field as a UUID. - WebSocket audio wire format. Both directions use raw signed 16-bit little-endian mono PCM in binary frames. Send samples without a WAV header and decode received frames at the configured sample rate.
August 11, 2026
Added
- Promptless DTMF collection. DTMF collection can now run without prompt
playback. Omit
prompt_filesfromPOST /v1/sessions/{uuid}/dtmf/collectto collect input without playing audio first. Existing requests withprompt_filesstill play those prompts before collecting input.
Changed
-
WebSocket connection headers. The WebSocket relay request property
headersis nowconnection_headersforPOST /v1/sessions/{uuid}/websocket/startand thewebsocketobject accepted byPOST /v1/sessions:connectWebsocket. Update serialized request bodies;headersis no longer accepted. -
Reserved WebSocket headers.
connection_headersaccepts only customer-controlled HTTP headers. Requests are rejected when the map containsUser-Agent,Origin,Host,Connection,Upgrade,Sec-WebSocket-*,Content-Length,Transfer-Encoding,Proxy-*,Forwarded,X-Forwarded-*,X-Real-IP, orCF-Connecting-IP, case-insensitively. The platform sendsUser-Agent: Voice-Platform/1.0and derivesOriginfrom the target WebSocket URL. -
Operation resource removed. The API no longer models command acknowledgements as a separate
Operationresource.operation_uuidis unchanged in requests, responses, and webhooks; it remains an opaque token for correlating lifecycle events.
August 10, 2026
Added
- Send DTMF. Applications can send ordered DTMF keypad presses and timed
waits to the
remote party on an answered phone session with
POST /v1/sessions/{uuid}/dtmf/send. Only one sequence can run per session; ringing calls and standalone WebSocket sessions are unsupported.202 Acceptedis the sole admission acknowledgement and does not confirm remote handling. - Cancel DTMF. Applications can cancel the active outbound DTMF sequence
with
DELETE /v1/sessions/{uuid}/dtmf/send. A key already submitted for transmission may finish, but later keys and waits are discarded.202 Acceptedis the sole cancellation acknowledgement; no terminal webhook is emitted.
Changed
- Command admission webhooks.
command.*.acceptedadmission webhooks are no longer emitted. Treat202 Acceptedas the sole admission acknowledgement and use later domain lifecycle webhooks only for observed state changes or outcomes. Removecommand.*.acceptedfilters; correlate lifecycle events throughoperation_uuidwhen present. - DTMF collection routes. DTMF collection routes moved to the
Keypad API namespace. Update request
URLs as follows; the former routes are no longer accepted:
POST /v1/sessions/{uuid}/play_and_get_digits→POST /v1/sessions/{uuid}/dtmf/collectDELETE /v1/sessions/{uuid}/play_and_get_digits→DELETE /v1/sessions/{uuid}/dtmf/collect
- Recording silence duration. Session recording voice-activity detection
now accepts
silence_duration_msup to3,600,000milliseconds (one hour), increased from10,000. Existing values remain valid. Use the larger range to tolerate extended quiet periods beforerecording.speech.ended.
Fixed
- WebSocket relay restart. Starting a WebSocket relay after
websocket.disconnectedorwebsocket.failednow reliably creates a fresh relay instead of failing on a remnant of the previous connection. Applications can retryPOST /v1/sessions/{uuid}/websocket/startafter either event without first sending a stop command. - WebSocket URL terminology. WebSocket relay URLs now consistently document
ws://andwss://as accepted schemes and call the remote endpoint the WebSocket server. This is a documentation correction; existing relay URLs and thebackendmute-leg value are unchanged.
August 7, 2026
Added
- Webhook delivery URL. Applications could read and update their own
webhook delivery URL with
GET /api/v1/app/webhookandPUT /api/v1/app/webhook. Sendingnullor an empty string disabled delivery. These routes were replaced on August 27 byGET /api/v1/appandPATCH /api/v1/app. - Health endpoints. Unauthenticated
GET /healthandGET /livezare now part of the public contract. Use/healthfor dependency readiness and/livezonly for process liveness.
Changed
- Webhook delivery window. Webhook events more than five minutes old at delivery time go to the dead-letter queue without an HTTP attempt. After a prolonged endpoint outage or delivery backlog, reconcile current resource state instead of assuming every lifecycle event will arrive. See Webhook delivery.
- Session base path. The canonical API base URL is
https://api.telekesher.dev. Session endpoints moved from/api/v1/sessions...to/v1/sessions.... Remove the/apisegment from constructed Session request URLs.
August 6, 2026
Changed
- Session event names. Session webhook names now use the
sessionresource namespace:call.created,call.answered,call.ringing_started,call.early_media_started, andcall.endedbecame their correspondingsession.*events. Update event filters and webhook dispatch logic to use the new names. - Room Playback terminal events. Terminal room playback events
are now
room.playback.endedfor natural completion or an API stop, androom.playback.failedfor errors and timeouts. The formerroom.playback.finishedis no longer used. Inspect the event’sreasonto distinguish the terminal outcome. - Session lifecycle fields. Session lifecycle fields are endpoint-specific.
Items returned by
GET /sessionsusestate, whileGET /sessions/{session_uuid}usestatus. Both fields have the same lifecycle values. Applications that deserialize both responses into one model should accept the field used by each endpoint. - Shared rate-limit bucket.
playback/seek,recording/mask, andrecording/unmaskdraw from one sharedseekrate-limit bucket. A burst of one of these commands can reduce the capacity available to the other two. See Rate limits. - Idempotent command
503responses. A503after an idempotent command may mean the command completed but its saved response could not be returned. Retry once with the sameIdempotency-Key. If it remains unavailable, reconcile from the resulting resource or webhook events before issuing a new key. - WebSocket pause and resume. WebSocket pause and resume affect audio in both directions. Pausing preserves buffered backend audio, and resuming does not change mute state.
Fixed
- Zero-value playback seeks. Playback seek requests now reject
seek_ms: 0. Use a positive value to seek forward or a negative value to rewind. - Authentication error responses. Authentication and client-error
responses now distinguish malformed
credentials (
400), missing or invalid credentials (401), forbidden operations (403), and resources hidden by application ownership (404). Error responses expose a stablecodefor programmatic handling.