Skip to main content

Deprecation policy

Breaking changes ship as a new major path (/v2) with a migration guide, a deprecation notice on the old path and a sunset date announced here at least 90 days ahead. Additive changes (new fields, new routes, new event types) ship on /v1 without notice and are safe to ignore.
AI disclosure setting
  • Each agent has an AI disclosure setting (ai_disclosure in the draft, compliance.yaml.ai_disclosure.enabled), off by default for new and existing agents. On: the agent says it is a virtual assistant right after the owner’s greeting, as before. Off: the owner’s greeting is said word for word, with no AI sentence added. Changing it is a HIGH-risk change, decided by a named person on Publish and kept in the agent’s change and version history. The recording notice is unchanged: it follows the greeting whenever calls are recorded and is never said when they are not. The truthful answer to “are you a person?” does not depend on the setting.
saved links and client guidance
  • Legacy App links retain their query parameters and fragment at the final workspace redirect, including repeated filters. Parameters and anchors explicitly set by a migration take precedence. Sign-in still uses the same-origin return address; a visitor without a workspace still goes to onboarding, without automatic saved-link resumption afterward.
  • Supported clients now distinguishes the source-available TypeScript and Python SDKs from package publication. The MCP guide makes the complete bearer header value explicit. No API route, credential boundary or database schema changed.
  • POST /v1/calls accepts mode: "prompt": the conversation is only task (or settings.prompt), with first_sentence, voice and language for this call. agent_id remains the line for number, consent, do-not-contact, calling hours, caps and billing. A prompt is required. agent_version, production and environment are refused. prompt_only is not a field and skips no check. Sessions and calls and TypeScript and Python SDKs.
recovering a stale scenario save
  • POST /v2/agents/{agent_id}/scenarios/{id} keeps its existing revision check and 409 CONFLICT envelope, adding reason: stale_scenario and the current draft revision only for a stale save, including a race at the atomic draft write. Other conflicts and missing or foreign pathways disclose no revision. The App asks Keep my changes or Discard changes before recovery.
Sempera voice and engine identities
  • GET /v1/voices accepts Sempera engine aliases in engine or provider, rejects invalid aliases with reason: invalid_voice_id, and answers catalogue refusals in Sempera’s words.
  • Spec exports and frozen environment settings seal both speech legs’ engine and model identities, including retained staging snapshots created before this change. Stored agents, voices and version hashes are unchanged. No data migration is required.
  • Conversation details, call-log summaries, live-call engines and voice handovers use Sempera identities for locally served speech too. Advanced views are not an exception. Operators retain the original runtime record.
credits

Added

  • GET /v1/credits and GET /v1/credits/history: a workspace’s credit balance, what each action costs, and every movement of it (Billing boundaries).
  • left_unscored on eval runs and quick runs: what a run’s credits did not reach.
  • 402 credits_exhausted: building actions (POST /v1/tests, POST /v1/evals/run, POST /v1/evals/quick-run, simulation and Standards runs, knowledge uploads and re-reads, and a call with test) now spend credits, and refuse with needed, balance, debt and, where buying can work, buy_path when the balance cannot cover them. POST /v1/calls without test and POST /v1/sessions are unchanged and spend none.
sign-in for AI assistants

Added

  • OAuth 2.1 sign-in on POST /v1/mcp: discovery documents, client registration, authorization code with PKCE, exact redirects, audience binding, rotating refresh tokens and an owner consent page in the App.
  • phone:read, phone:call and phone:text scopes. A credential holding only these can connect to POST /v1/mcp.
  • GET /v1/client-grants, POST /v1/client-grants/rest and DELETE /v1/client-grants/{grant_id}: one list of connected clients, a scoped REST credential for clients that cannot do OAuth, and revocation of one client at a time.

Changed

  • A 401 on POST /v1/mcp now carries WWW-Authenticate with the protected resource metadata address. Its body is unchanged, and bearer keys work as before.
official SDKs

Added

  • TypeScript (@sempera/sdk) and Python (sempera-sdk) client libraries, generated from openapi.json with one method per operation, typed errors, retries that honor Retry-After, automatic idempotency keys, cursor paging and webhook signature verification (TypeScript and Python SDKs). They are not published to npm or PyPI yet. No route changed.
metric alerts

Added

  • PATCH /v1/alerts/{id} takes an optional expected_updated_at, the updated_at the caller read, including when enabling or disabling a rule. The check and save are one step; if another owner saved first, the answer is 409 conflict with reason: stale_alert and the updated_at now on file, and nothing is written, audited or cleared. A value that is not a time with a zone is 400 validation_failed on expected_updated_at. A rule of another workspace, or one deleted meanwhile, is 404 not_found and names no updated_at. Without the field a save behaves as before (Metric alerts).
docs and contract alignment

Added

  • openapi.json now describes what the docs already said: the runtime_identity block on a session, an EvalRun schema for GET /v1/evals/runs and GET /v1/evals/runs/{run_id}, the eval_id and call_ids fields of POST /v1/evals/run, and the seven tags (MCP, Models, Prompts, Scenarios, Widgets, Citation schemas, Developer settings) that routes used without a description.
  • Errors and limits lists the stable reason values a refusal can carry, including invalid_voice_id (a value that looks like a Sempera voice id and is not one), and Sessions and calls documents the fields of runtime_identity.
  • Owner features in the App says which staging and analytics features are App-only (served under /api, not /v1).
what ran a session

Added

  • Evals: GET /v1/evals/runs and GET /v1/evals/runs/{run_id} read the evaluation runs POST /v1/evals/run now records, and that call’s answer carries run_id (Tests and evals).
  • GET /v1/sessions/{session_id} returns runtime_identity for a session recorded from now on: the engine and model that heard, thought and spoke (named by Sempera’s opaque engine names), whether any leg fell back, whether Sempera’s own servers or a hosted call service ran it, the reply and voice timings, and whether the session was a test or a customer’s conversation. verified is false when the stored block no longer matches its own integrity checksum. Older sessions return null.
voices

Changed

  • Every voice a customer reads is named by a Sempera voice id (voice_ then lowercase letters and digits) and neutral fields, never by the speech engine or the engine’s own id for the voice: GET /v1/voices (each row is {id, voice_id, name, display_name, description, language, source, production_eligible, preview_available, available}, plus visibility for a custom voice), the Agent’s voice, a draft’s voice, a session’s voice.voice_id, a persona’s and a call’s voice, and the agent export’s agent.yaml. The provider and engine fields of GET /v1/voices are removed. A voice of a published Sempera model keeps the name its model lists.
  • The ids are stable and safe to store, and one voice has one id whichever transport of an engine it is spoken through. A raw voice id from before this change is still accepted wherever a voice is written (a draft, a session, a favorite, an agent file) and keeps meaning the same voice; reads return only Sempera voice ids.
  • An id that looks like a Sempera voice id but is not one is refused the same way everywhere: 400 with reason: invalid_voice_id (code: invalid_voice_id on the App’s own routes), on a draft, a session, a voice’s detail and preview, and a favorite.
  • A voice refusal is in Sempera’s words (“That voice is not in the voice list.”) and never repeats the engine or the engine’s id for the voice.
  • POST /v1/sessions: voice and speech_recognition no longer carry provider, fallback_provider or model, except a leg on a published Sempera model, which is still named by its Sempera model id. voice.voice_id is the Sempera voice id.
  • GET and PATCH /v1/agents/{id}/draft: the draft’s fields name a speech engine (runtime_tts_provider, runtime_tts_fallback, runtime_stt_provider and a speech selection’s provider) by a sealed Sempera engine name (engine_ then letters and digits), and a voice by its Sempera voice id. A PATCH takes the sealed name or the engine id. The draft summary previews a speech engine by its plain label (“Studio voice”, “Standard recognition”).
  • The runtime reading names a speech engine and its model by a sealed Sempera name too: the runtime block’s hearing and speaking legs (options[].value, identifier and model, the configured and effective values, the speaking pace’s engine) in the result of a draft PATCH and on GET /v1/agents/{id}. A PATCH takes the sealed name back, and the engine id still writes. An option’s reason_detail is the same owner sentence as its reason.
  • A refused voice reads by the refusal’s code, never its internal sentence: voice_unknown, engine_unavailable, engine_not_allowed, catalog_unavailable, preview_unavailable, and for a custom voice’s consent consent_no_workspace, consent_store_unavailable and consent_missing (as reason beside code: voice_consent_required).
  • Agent files and GitHub sync: agents/<slug>/agent.yaml names each voice by its Sempera voice id (voice: {id, name}) and each speech engine by its sealed name, so nothing the agent’s repository holds names the engine. Importing reads both back to the same runtime values, an older file with raw ids still imports, and the content hash Sempera reports for a file tree is the same in either form. The first sync after this change commits the rewritten agent.yaml once.
webhooks

Added

  • PATCH /v1/webhooks/{id} takes an optional expected_updated_at, the updated_at the caller read. The endpoint is saved only if it still has that updated_at, in the same step as the check, whatever the edit changes (the address, the subscriptions, a pause or a new signing secret); if another owner saved first, the answer is 409 conflict with reason: stale_webhook and the updated_at now on file, and nothing is written. A value that is not a time with a zone is 400 validation_failed on expected_updated_at. An endpoint of another workspace, or one deleted meanwhile, is 404 not_found and names no updated_at. Without the field a save behaves as before (Webhooks).
widgets

Added

  • Saving a website widget in the App takes an optional expected_updated_at, the updated_at the owner read. The widget is saved only if it still has that updated_at, in the same step as the check; if another owner saved first, the save is refused with 409 and code: stale_widget, the answer carries the widget now on file, and nothing is written or audited. A value that is not a time with a zone is 400 on expected_updated_at. Without the field a save behaves as before. No /v1 route changes: GET /v1/widgets still leaves updated_at null (Website widgets).
triggers

Added

  • PATCH /v1/automation/triggers/{id} takes an optional expected_updated_at, the updated_at the caller read. The trigger is saved only if it still has that updated_at, in the same step as the check; if another owner saved first, the answer is 409 conflict with reason: stale_trigger and the updated_at now on file, and nothing is written or audited. A value that is not a time with a zone is 400 validation_failed on expected_updated_at. Without the field a save behaves as before (Automations).
documentation

Added

  • Three pages: Security and privacy, Billing boundaries and Supported clients. The quick start gains what to have before you start and what the API does not do yet. API overview lists the routes that need no credential and explains the difference between supported and compatibility routes. Agents, Workspaces and organizations and Tools and integrations now name the five supported routes they had left out: GET /v1/agents/{id}/versions/{version_id}, POST /v1/agents/{id}/unarchive, GET /v1/organizations/{organization_id}, GET /v1/integrations/{integration_id} and GET /v1/integrations/{integration_id}/actions. No route changed.

Changed

  • Examples use the demonstration agent northwind-barbers in place of a customer’s agent id, and invented ids, digests and timestamps in place of any value from a customer’s record. No route or field changed.
  • The quick start and Authentication and credentials no longer say the App has no key screen: a workspace owner creates API keys in the App under Settings, API keys, and a workspace’s first key is created there. No route or field changed.
  • Webhooks describes provider ingress by category and no longer lists its routes one by one: they are for Sempera’s service providers, not for integrators. Web SDK and Sessions and calls describe the realtime media client by category. No route or field changed.
  • Billing boundaries says exactly what the two /v1/compat billing reads may write (the workspace’s default billing record on a first read) and nothing more. API overview’s table of routes that need no credential lists all eight.
shipped after that record
Recorded from the canonical branch’s history. Dates are the commits’. Owner features in the App with no API route are in Owner features in the App and Text messages.

Changed

  • POST /v1/calls for a number on the workspace’s do-not-contact list still answers 501 capability_unavailable, now with reason: do_not_contact and the message “No call was placed: the number is on this workspace’s do-not-contact list.” The refusal is written to the workspace’s audit log with the number’s last four digits only, and the one-call batch is still recorded with that person marked as blocked. It used to answer “outbound call was refused” with no reason and write no audit row (2026-09-30, Sessions and calls, Errors and limits).
  • channels.phone on the Agent (GET /v1/agents, GET /v1/agents/{id}, the Agent returned by create, archive and unarchive, and the MCP list_agents and get_agent tools) is true once a phone number is set up for the agent: a number bound to it, or a line released through its deployments, the same reading as the App’s launch checklist. It was always false (2026-09-29, Agents).
  • DELETE /v1/integrations/{id} (archive, or restore with ?restore=1) takes an optional revision query parameter, the revision the caller read; one that is no longer on file is refused with 409 conflict, reason: stale_connection and the revision on file, and nothing is archived or restored. Without it the call behaves as before (2026-09-29, Tools and integrations).
  • An agent’s greeting (its opening line) no longer replaces the whole opening. The agent always follows the owner’s words by saying it is a virtual assistant and, when calls are recorded, with the recording notice, in the language of the call; an opening line that already asks the caller a question is not followed by a second one. The compiled opening the compatibility reads return as first_sentence carries both. It used to be the owner’s words alone (2026-09-29).
  • identity_style sets only how the agent introduces itself: owned speaks as part of the front desk, by name, and virtual-assistant leads with the title. Every agent says it is an AI virtual assistant at the start of every call, and says plainly that it is an AI when a caller asks whether it is a person, whatever this field holds; owned used to make it answer without saying so. The values are unchanged and an agent that holds owned keeps it (2026-09-29).
  • POST /v1/agents/{id}/preview: the first reply of a typed session starts with the agent’s opening (the AI disclosure and, when calls are recorded, the recording notice) in the language the caller wrote in, as a call’s first words do. It used to be a greeting the model composed (2026-09-29, Agents).
  • PATCH /v1/integrations/{id} refuses a save built on an older revision with 409 conflict, reason: stale_connection and the revision on file. It answered 400 validation_failed with a sentence, against the rule in Errors and limits that a stale revision is a 409 (2026-09-29, Tools and integrations).
  • DELETE /v1/tools/{id} (archive, or restore with ?restore=1) takes an optional revision query parameter, the revision the caller read; one that is no longer on file is refused with 409 conflict, reason: stale_tool and the revision on file, and nothing is archived or restored. Without it the call behaves as before (2026-09-29, Tools and integrations).
  • PATCH /v1/tools/{id} refuses a save built on an older revision with 409 conflict, reason: stale_tool and the revision on file. It answered 400 validation_failed with a sentence, against the rule in Errors and limits that a stale revision is a 409 (2026-09-29, Tools and integrations).
  • /v1/knowledge-sources fills the fields it documents: content_type, added_at and attached were always null, collections was null on an upload and on a source in no collection, and the list’s pdf_supported was always null. An upload now answers what a read of the source answers, raw included: an upload’s raw is the library row a read answers, where it was the store’s own record (it gains the fields a read carries, such as revision and, for text documents, text, and loses mime, extracted and removed_at); if that read fails after the file is saved, the upload answers 201 from the saved record, with collections and attached null. POST .../reparse answers the state the re-read left, where it always said reparsing, and DELETE and POST .../reparse answer an id the agent’s library does not hold 404 not_found, as the read does, where they answered 409 conflict; a source already removed, or a library that cannot be read, still answers 409 conflict with the reason (2026-09-30, Knowledge).
  • PATCH /v1/knowledge/{id}/content takes an optional expect_version_id, the version the content read returned; a save built on an older version is refused with 409 conflict, reason: stale_content and the current version, and nothing is written. Without the field a save behaves as before (2026-09-29).
  • GET /v1/recordings/{recording_id} answers a deleted recording 410 gone (reason: deleted), a recording whose access ended under the workspace’s recording security 410 gone (reason: access_ended), and a caller outside a members-only workspace 403 forbidden (reason: members_only), signing nothing; every link it issues is recorded in the workspace’s audit log. The error vocabulary gains gone (410), which an App-road refusal forwarded through the API used to report as internal_error (2026-09-29, Call recordings, Errors and limits).
  • GET /v1/agents/{id} returns the agent’s own voice (id, name) and language (code, label); both were always null. One go-live check in its readiness block is now named artifact:phone-build, as the App’s Deploy tab names it (2026-09-29, Agents).
  • GET /v1/calls/{call_id} and GET /v1/sessions/{call_id} (and the compatibility call-log reads) return the customer view of a call: each model leg is named by its Sempera model id, and the account id, credential source and upstream model the record held are no longer returned (2026-09-29).
  • A model leg that Sempera’s own cloud model pool served, which has no published Sempera model id, is named sempera-cloud-model on the host cloud in those call and session reads and the compatibility call-log reads. They used to return the engine’s own name, its model and its host (2026-09-29).
  • The result of PATCH and DELETE /v1/agents/{id}/draft names a cloud model pool engine in its runtime block as sempera-cloud-model, and lists every such engine as one option that cannot be chosen for a single agent; a draft that sets runtime_llm_backend to sempera-cloud-model is refused with a sentence saying Sempera chooses the cloud model for the whole organization. Engines on Sempera’s own servers keep their names (2026-09-29).
  • GET /v1/live-calls/stream names each call’s language model in engines.llm by its Sempera model id, or null (2026-09-29, Live calls and live transcripts).
  • A speech recognition or voice engine that a cloud provider hosts for Sempera is named sempera-cloud-speech-recognition or sempera-cloud-voice in GET /v1/sessions/{call_id}, GET /v1/calls/{call_id} and the compatibility call-log reads, and in engines.stt and engines.tts.provider on GET /v1/live-calls/stream. They used to return the engine’s own name and model. Engines on Sempera’s own servers keep their names (2026-09-29, Live calls and live transcripts).
  • The result of PATCH and DELETE /v1/agents/{id}/draft reads such an engine the same way in its hearing and speaking legs: the running engine (effective), its model and the speaking pace’s provider use the neutral name, and each such option’s reason_detail is the same sentence as its reason. An option’s value and the draft’s own values are unchanged, so a draft still names the engine by the value it read (2026-09-29).
  • Two refusal sentences name no engine a cloud provider hosts: a session, or a publish, that picks one where it is not cleared for real calls is refused with “The cloud voice engine …” or “The cloud speech recognition engine …” (the 409 of POST /v1/sessions still carries the engine’s id in engine), and a recognizer that cannot choose the language per call is refused with “Auto-detect is not available with the speech recognition you chose.” (2026-09-29).
  • Why a call’s voice handed over to its backup voice is one fixed sentence in GET /v1/sessions/{call_id} and GET /v1/calls/{call_id}: each handover’s reason in runtime_actual.tts.fallbacks and in sibling.tts_fallbacks reads “The voice engine stopped answering, so the backup voice took over.” It used to be the error’s own text, which could name the address the engine was reached on; the engines and voices of the handover are named as before (2026-09-29).
  • The draft’s summary, which previews each changed value now (before) and in the draft (after), names a voice or speech recognition engine a cloud provider hosts as sempera-cloud-voice or sempera-cloud-speech-recognition, and a cloud model pool engine as sempera-cloud-model, with their models, in the result of PATCH and DELETE /v1/agents/{id}/draft, the summary of GET /v1/agents/{id}/draft and the draft of GET /v1/agents/{id}. It used to preview the engine’s own name and model. The draft’s own fields are unchanged, and engines on Sempera’s own servers keep their names (2026-09-29).
  • POST /v1/sessions names the model of such an engine in voice.model and speech_recognition.model as sempera-cloud-voice or sempera-cloud-speech-recognition. It used to return the engine’s own model. voice.provider and speech_recognition.provider are still the engine ids the session was asked for (2026-09-29, Sessions and calls).
  • GET /v1/personas and GET /v1/personas/{persona_id} report the agent’s own language, time zone and voice in call_config, its role as description, and real dates: when its first and newest versions were built, and the number and build time of the version it runs now, each null when the agent has none. orchestration_prompt and promoted_at are null. They used to be fixed values (en, America/Toronto, an empty voice and description, version 1 and one placeholder date) whatever the agent was configured with (2026-09-29, Personas).
  • GET /v1/recordings/{recording_id} looks the recording up before it signs a link: an id that does not exist and another workspace’s recording both answer 404 not_found, and neither is signed. It used to sign a link for any id it was sent. A link a signed-in member asks for now plays for that member; it was bound to a form of their identity the audio path never matched, so it played for nobody. A link asked for with an API key still plays for nobody (2026-09-29, Call recordings).
  • A trigger’s webhook action survives a restart of the service between its attempts: the run keeps what its next attempt needs, and the next attempt is sent once the service is back, with the same event id and body, to the trigger’s endpoint while it is still enabled, three attempts in all. An attempt a restart cut off counts, marked as not known to have arrived. Such a run used to stay running until it showed as stalled and the owner sent it again (2026-09-29, Webhooks).
  • call.completed is also sent for an interpreted call (Live translation), once per call, with provider sempera and recording_stored false, and the workspace’s Call completed triggers fire for it with direction interpreted; GET /v1/sessions/{call_id} reads it. A session whose interpreter never started sends nothing. Interpreted calls used to send no call.completed and fire no trigger (2026-09-29, Webhooks).
  • call.completed is also sent for a phone call Sempera’s own call service answered, with provider sempera, once per call whichever road sees it first, and never for a test session; GET /v1/calls/{call_id} reads such a call by the id the event carries. It used to be sent only for calls the carrier reported (2026-09-29, Webhooks).
  • A workspace’s Call completed triggers (/v1/automation) also fire for a phone call Sempera’s own call service answered, once per call under the same claim as call.completed, so a call both roads see fires them once; tests never fire them. They used to fire only for calls the carrier reported (2026-09-29, Webhooks).
  • An inbound text on an agent whose texting is off (Send text messages in its phone setup, which starts off) is stored and shown on its thread and not answered; the thread records why. It used to be answered on any bound number (2026-09-29, Text messages).
  • With texting off in an agent’s phone setup (Send text messages, which starts off), the in-call text tool is refused and sends nothing, and the Send SMS voicemail action sends no text and records why. Both used to send whatever the setting said (2026-09-29, Text messages).
  • Why an agent did not answer a text is a fixed sentence for the kind of failure (texting off, the model unavailable, the text not sent, and so on), on the thread, in the conversation drawer and on Needs attention. It used to include the engine’s or the carrier’s own error text, which could name the service it called (2026-09-29, Text messages).
  • agent.published carries the same agent_version object (id, object, agent_id, version_number, created_at, spec_hash, compiled_artifact_id) whichever road published. A publish from the App used to send the version ledger’s own row, with different key names and internal snapshot hashes (Webhooks).
  • A webhook endpoint whose stored signing secret cannot be read is never sent an unsigned request: the attempt fails in the delivery log with a sentence saying so. One such endpoint also no longer stops the workspace’s other endpoints from hearing the same event.
  • Every webhook delivery goes over one guarded road (2026-09-28): the host is resolved and the connection pinned to the address that was checked, an address inside a private network is refused, and a redirect is a failed attempt. A saved endpoint URL with a private address literal, a user name or password, or a bad port is refused with validation_failed.
  • A trigger’s webhook action names one of the workspace’s registered webhook endpoints and is signed with that endpoint’s own secret; a new tool action calls one of the workspace’s API request tools. POST /v1/automation/executions/{id}/retry sends a failed or stalled webhook or tool run again once, with the same event id (2026-09-28).
  • A call’s call.completed and call.outcomes deliveries lose the words read from a call once its transcript is deleted: the stored bodies are emptied, and a later retry of a call.outcomes delivery sends the emptied event (2026-09-26).
  • A version is named the same way everywhere (2026-09-28): the production pin on POST /v1/calls resolves to the version the agent’s current build resolved to, not simply the newest ledger row, and a voice or phone call’s record names the version it started on, not the one current when it ended.
  • Editing a tool or a connection checks its name only inside its own workspace. Another workspace’s tool of the same name no longer refuses the edit or reveals that the name exists elsewhere (2026-09-28).
  • Evaluation criteria are read and scored from the caller’s own workspace only (2026-09-27).
  • openapi.json names no provider: an operation id that carried a platform name is now listCitationSchemasListPath, and a compatibility path whose name carries that platform’s name is still served but no longer listed.
  • openapi.json declares the branch_id path parameter (a UUID) on the four /v2/agents/{uuid}/branches/{branch_id} routes (rebase preview, rebase, merge and delete), which listed only the agent (2026-09-29). The routes themselves did not change.

Added

  • In the App, a text conversation shows every message on its thread in order, who sent each and when, and a texter left without an answer is an urgent item on Needs attention that counts toward the attention alert (2026-09-29, Text messages).
  • Outbound call limits (2026-09-28): an owner can cap the calls a workspace places per UTC hour and per UTC day. POST /v1/calls over a cap answers 429 rate_limited with retry_after_s, and 503 service_unavailable when a cap cannot be read; nobody is called (Errors and limits, Owner features in the App).
  • A real phone call answered by Sempera’s own call service over the carrier’s audio stream (2026-09-27): two provider-ingress routes carry it, public only on a deployment that turned live call audio on (Webhooks).
  • Owner features in the App, described in Owner features in the App: filtered call-data exports built in the background and downloaded by owners; outbound batches with a start time, CSV recipients and a background scheduler; per-tool analytics over 7, 30 or 90 days; an Import from cURL panel on the tool form; writing, testing, publishing and pinning code snippets from the Tools page.
  • Text messages (2026-09-28): the agent’s published version answers inbound texts on a thread it holds, in place of a fixed reply (Text messages).
  • Developer guide pages for snippets, triage, dispositions, alerts, widgets, text messages and branches, verified against the route table, and app-features.md. The guide said POST /v1/calls was not offered and answered 501; it records a one-call outbound batch and answers 201, and Sessions and calls now says so. webhooks.md is rewritten as the stable outbound contract: the events, each payload, signing with a test vector, retries, idempotency, ordering and the delivery log.
  • Developer guide pages for automations, scenarios, secrets and request signing, live calls and live transcripts, recordings, personas, citation schemas and the caller memory schema (2026-09-29), each checked against the route table and its handlers. They record behaviour as it is, including that the live streams answer one snapshot frame and close, that GET /v1/recordings/{recording_id} signs a link an API key cannot fetch, and which persona fields are fixed values.
changes since the cutover
Recorded from the route table and its history between 2026-09-15 and 2026-09-27. openapi.json is the full contract.

Changed

  • GET /v1/tools, GET /v1/integrations and GET /v1/voices (2026-09-21) answer compatibility envelopes instead of the standard list envelope: {"status", "tools", "hasMore", "nextCursor", "data", "errors"}, {"data": {"count", "integrations"}, "errors"} and {"voices": [...]}. The kinds and engines keys those lists carried are gone. This changed /v1 without the notice the policy above promises; this entry is the record of it.
  • session.completed is retired. It was accepted as a subscription but never sent; asking for it is now validation_failed, and endpoints that stored it no longer list it (Webhooks).
  • test.completed is now sent when a test run finishes or crashes (2026-09-26).

Added

  • Agent branches under /v2/agents/{agent_id}/branches (Agent branches).
  • Dispositions and their extractors under /v2/agents/{agent_id}/dispositions; published dispositions run on completed phone calls and their results ride on call.outcomes as dispositions (Dispositions).
  • Triage issues under /v1/triage: issues, flagged calls, files, comments, activity and links between issues (Triage issues).
  • Metric alert rules under /v1/alerts, and the metric_alert.triggered event, delivered only to the endpoints a rule names (Metric alerts).
  • Code snippets under /v1/snippets, and the code tool kind that pins a published snippet version (Code snippets).
  • Website widgets: GET /v1/widgets, GET /v1/widget/threads and /v1/widget/custom-components (Website widgets).
  • Text messages: GET /v1/sms/numbers and GET /v1/sms/conversations (Text messages).
  • Automation triggers and their executions under /v1/automation.
  • Evals: GET /v1/evals/agents, /v1/evals/agent-templates, /v1/evals/test-cases, /v1/evals/user-templates (and POST), and POST /v1/evals/quick-run (Tests and evals).
  • Speech models and saved prompts: GET /v1/models, GET /v2/tts/models, the credential-free GET /v2/models, and /v1/prompts (Sessions and calls).
  • Workspace secrets (/v1/secrets), JWT request signing (/v1/signing, /v1/signing/keys and GET /v1/workspaces/{workspace_id}/.well-known/jwks.json) and GET /v1/release-deployments.
  • Live calls and recordings: GET /v1/live-calls/stream, GET /v1/live-calls/transcript/stream and GET /v1/recordings/{recording_id}.
  • GET /v1/personas, /v1/citation_schemas, GET /v1/agents/{agent_id}/memory/schema, scenarios under /v2/agents/{agent_id}/scenarios, and environment variables under /v2/agents/{agent_id}/environments/{env}/variables.
  • POST /v1/mcp and GET /v1/mcp/clients/{client} for coding agents (Getting started).
  • Further compatibility facades, tagged Compatibility in openapi.json.
the one-API cutover
  • Compatibility /v2 routes for the console agent, disposition, deployment, batch and analytics reads were added behind the same credential and workspace boundary as /v1. They are facades over Sempera-owned stores, not a separate database.
  • api.sempera.org became the single canonical Sempera API, versioned under /v1, with openapi.json, llms.txt and these guide pages served from it.
  • The private server hostname (the retired legacy API hostname, retired 2026-09-14) was migrated behind the canonical API. No customer or developer integration ever targeted it.
  • The existing operational routes that the live salon agent’s tools call (/v1/caller/*, /v1/catalog/*, /v1/center/*, /v1/booking/*, /v1/messages) remain in service unchanged as the runtime tool surface; they are internal to Sempera-built agents, are not part of the public contract, and will be folded into Tools when the salon agent is next re-certified.
  • Credential classes (developer, runtime, CI, service, admin keys; the browser session token) were introduced with explicit scopes.
  • Sempera-signed outbound webhooks were introduced (X-Sempera-Signature).