Sessions
A Session is one conversation with an Agent on any channel:GET /v1/sessions?agent_id=... lists them newest first; GET /v1/sessions/{id} returns the transcript (turns), the tool timeline and captured variables where the runtime recorded them, latency, outcome and the version that spoke.
Live translation
GET /v1/translation-sessions?agent_id=... reads availability and recent sessions with sessions:read. POST /v1/translation-sessions takes agent_id, caller_language and staff_language, requires sessions:write, and returns two room-scoped join tokens. Both routes require workspace activation and access to the agent. No phone call is placed. Tokens must not be logged or shared outside the intended participants.
Dialing pools
These routes require access to the agent and the workspace’s dialing-pool entitlement. Reads needcalls:read; configuration writes need calls:write and permission to send outbound calls.
Rich messaging
These routes require an active workspace and access to the agent. Reads needcalls:read.
Voices
GET /v1/voices lists every voice a session or a draft may name, with production_eligible (may carry a live call) and available (it can speak on this deployment right now). Each voice is named by a Sempera voice id (id, voice_, then letters and digits) and described by name, description, language, source (built_in, library, or the kind of a custom voice) and, for your workspace’s own custom voices, visibility: "private". The id is stable: the same voice has the same id on every read and every deployment, so it is safe to store. No voice payload names the speech engine or the engine’s own id for the voice. Pass the id as voice_id on POST /v1/sessions to override the agent’s published voice for one conversation, or set it as the draft’s voice.id and publish. A raw voice id from before 2026-10-28 is still accepted wherever a voice is written; reads return only Sempera voice ids.
The optional engine filter (also accepted as provider) takes a Sempera engine alias returned by a runtime reading. Treat it as opaque. Older engine values remain accepted for compatibility. An invalid alias returns 400 validation_failed with reason: invalid_voice_id; catalogue refusals use Sempera’s wording and a stable reason, never the engine’s own error text. The MCP list_voices tool lists the same Sempera voice identities without an engine argument.
Spec exports and frozen environment settings name speech engines and models by the same stable Sempera aliases. Existing saved agents, published versions and retained snapshots are not rewritten.
Voice library and studio
A workspace keeps a shortlist of cleared Sempera voices. Every workspace member sees the whole library; sharing only controls a direct link to one entry. Private clones are never added or shared. Writes need theagents:write scope and an Operator role or above; the studio routes need an Owner.
Models
GET /v1/models and GET /v2/tts/models (GET /v2/tts/models/{id} for one) list Sempera’s own published, customer-eligible speech models: scopes: models:read. Values are Sempera’s own model and voice names; no route ever names a supply account or an underlying engine. GET /v2/models is the same speech catalog, reshaped as the model list many client libraries expect ({id, object: "model", created: 0, owned_by: "sempera", name, modality: "text-to-speech", capabilities: {encodings, sample_rates, containers, controls}}, Cache-Control: public, max-age=300), and needs no credential at all.
Stored prompts
GET /v1/prompts lists a workspace’s saved prompts ({"status": "success", "prompts": [...]}); GET /v1/prompts/{id} reads one; POST /v1/prompts with {"prompt": "...", "name": "..."} (name optional) saves a new one, both wrapped the same way ({"status": "success", "prompt": {"id", "prompt", "name", "last_updated"}}). Reads need prompts:read; writes need prompts:write. A saved prompt is reused by hand, most often filling the prompt field on a call’s own settings; nothing here runs at call time.
Minting a Web voice session
{"id", "transport", "url", "token", "expires_at", "voice", "speech_recognition"}, where transport names the realtime media protocol the url and token are for (Web SDK). Hand url and token to the browser; the token is bound to that one room and expires in 30 minutes. Your API key never leaves your server. The request is refused with 409 when the agent has no published version, when the requested voice is not in the catalogue, or when the platform is at capacity (503). When the agent hears or speaks through a published Sempera model, speech_recognition or voice names it by its Sempera model id (model_id, repeated as provider and model), and the session runs that model; if the model cannot carry a call on this deployment, the request is refused with 409 and a sentence saying so, and no other model is used in its place. Otherwise the two legs name no engine and no model: voice carries the voice’s Sempera id (voice_id) and name, and speech_recognition its language. A voice that cannot be used is refused in Sempera’s words, and a voice_id that looks like a Sempera voice id but is not one is refused with 400 and reason: invalid_voice_id.
What ran a session: runtime_identity
GET /v1/sessions/{session_id} carries a runtime_identity block for a session recorded from 2026-10-29 on: one answer to “what heard, thought and spoke, did anything fall back, and was it a customer’s conversation”. Older sessions return null.
Engines and models carry Sempera’s sealed names (
engine_...), or the Sempera model id when a leg ran a published Sempera model. The block never names a supplier, a host, a device or a failure’s own text, and it prices nothing.
Calls
A phone call is a Session withchannel: phone and also a Call: GET /v1/calls?agent_id=..., GET /v1/calls/{call_id}. A Call adds the carrier’s lifecycle: caller and called numbers, direction, duration, recording state and provider correlation ids (internal).
POST /v1/calls (calls:write, owner role, requires Idempotency-Key) places one outbound call through the App’s guarded dispatcher. A missing gateway or unreadable outbound policy refuses the request; it never reports a ledger-only call as queued.
call_data is an object of up to 20 named text values for this conversation only. A key starts with a letter and contains only letters, digits and underscores, at most 40 characters; each value is at most 200 characters. The runtime binds these as call facts without changing the published agent. Do not send credentials in call data. The App’s version picker lists the currently available published phone version and refuses an unavailable selection rather than silently replacing it. Scheduling in the App creates a deferred batch only when the scheduler is enabled; the public single-call API still places calls now.
Send only one spelling of an option, including across settings and top-level aliases. voicemail and precall_dtmf_sequence alias the existing voicemail and keypad settings. webhook selects a registered signed endpoint for this call; see Webhooks. Any other option returns 400 validation_failed naming its field, even when null or false. Recording overrides, transfers, per-call scheduling, retries, model selection and analysis overrides are refused until their runtime paths support them.
Prompt mode
mode: "prompt" places one call from a prompt instead of the agent’s conversation. agent_id is still required. It is the line the call goes out on (number, consent, do-not-contact, calling hours, caps and billing), not the conversation. A prompt is required: task or settings.prompt. first_sentence (alias of message) is the opening line after the automated-call disclosure. voice and language set the voice and language for this call. Omit agent_version, production and environment; sending any of them is 400. prompt_only is not a field. Sending it is 400 and skips no check.
mode does not turn any of them off: the consent sentence, the idempotency key, calling hours, the do-not-contact list, outbound caps, an owned caller id, workspace screening, and billing. A missing prompt places nothing. The answer is 201 with mode: "prompt" and no agent_version_id. The line’s instructions, tools, connections and knowledge are not used.
Corrected transcripts
POST /v1/calls/{call_id}/corrected-transcript (calls:write and calls:read, owner role) imports corrected turns for a completed phone call: source (human_review or recording_analysis) and 1 to 5000 segments, each with ordered start and end seconds, a numeric speaker, a speaker_label (caller, assistant or unknown), text and a 0 to 1 confidence. GET /v1/calls/{call_id}/corrected-transcript (calls:read) reads it back. Every import reads as provenance: owner_import, verified: false and confidence_kind: importer_reported: source is what the importer said, and Sempera does not verify it or re-transcribe the call. The original transcript is unchanged, and the correction shares its retention and deletion. No correction yet is 409 correction_unavailable; a deleted or expired transcript is 410. A call recorded under Exclude from history, a call on an agent this key cannot see and a call that does not exist all answer the same 404. Each import is audited with ids and counts, never the words.
Active call controls
These routes requirecalls:write, the owner supervision permission and a workspace-bound credential. An optional agent_id narrows the credential’s agent scope; it cannot widen it. Every request requires a nonempty reason of at most 200 characters, recorded by the existing supervision audit trail.
POST /v1/calls/{call_id}/stopends one active phone call by its Sempera id.POST /v1/calls/active/transfertakescall_id,to(E.164) andreason. It uses the existing phone transfer path and preserves launch restrictions.POST /v1/calls/active/stopends a snapshot of active phone calls within the authorized scope. Browser sessions and calls arriving afterward are not affected. The response hasdata(one outcome per call),totalandstopped. Failures are reported individually, not hidden by aggregate success. A legacy room without a public call id can still be stopped in bulk; its result hasid: nullrather than exposing an internal room identifier.
404 for missing, ended or out-of-scope calls. An unreadable live roster returns 503, not an empty success. A transport failure can leave the outcome unknown: check active calls before retrying. The console’s Live now section offers owner-confirmed bulk stop for the current agent, alongside its existing single-call stop and transfer controls.
Questions about a finished call
POST /v1/calls/{call_id}/analyze takes {"questions": ["What did the caller request?"]}. It requires calls:read, evaluation permission and a workspace-bound credential. An optional agent_id query parameter narrows lookup. Each request reserves one conversation-evaluation credit unit through the existing credit ledger.
Send 1 to 10 nonempty questions, each at most 500 characters. The call must have a finished phone transcript. Missing or deleted transcripts are refused. Transcripts over 60,000 characters are refused, never silently truncated. Public outbound ids resolve to the call service’s finished record of the call when that record contains the public dispatch correlation; a dispatch checkpoint alone is not a finished transcript.
The response includes id, agent_id, object: call_analysis and answers. Each answer has question, answer, evidence and status. An answered question must include a verbatim transcript quote. Missing information returns answer: null, empty evidence and status: unknown. An unavailable evaluator or invalid evidence returns a service error, never an invented answer. Answers are returned without creating a new stored analysis record. The conversation drawer offers the same questions form to members with evaluation permission and explains the credit use before submission.
Call batches
POST /v2/batches/create requires calls:write, owner outbound permission and Idempotency-Key. Send global with the call’s agent, production/version, consent basis and supported call options, and call_objects containing phone_number, optional metadata template context, timezone and precall_dtmf_sequence. Alternatively supply csv, using the existing recipient CSV columns. Invalid rows reject the request rather than disappearing. Unknown per-recipient options are refused. Optional top-level fields are description, concurrency, max_attempts, retry_after_s, start_at, and status_webhook (an enabled signed endpoint registered in the workspace).
Creation persists and starts the batch, then advances one due pass through the existing consent, do-not-contact, quota and call-cap gates. Future windows and retries remain queued. The response includes the scheduler’s actual state: automatic continuation requires the existing deployment switch SEMPERA_OUTBOUND_SCHEDULER=1; no API changes that switch or raises caps. POST /v2/batches/{batch_id}/run, with an idempotency key, also advances due recipients using that same checkpointed path.
GET /v2/batches/list, GET /v2/batches/{batch_id} and GET /v2/batches/{batch_id}/logs return real persisted records and attempts. Lists and logs support take and skip; logs also accept direction=asc|desc. POST /v2/batches/{batch_id}/stop stops future recipients and retries, not an already connected call. Repeating stop is safe. Every endpoint subscribed to batch.updated receives signed events with batch_id, state and summary after passes and lifecycle changes. A status_webhook must name an endpoint subscribed to batch.updated (otherwise 400), and receives each event once. A failed notification never turns a placed batch into an error. All resources are workspace and agent scoped.
The workspace Outbound list offers recorded attempts and owner-only Start, Resume, Pause and Stop actions using the same guarded lifecycle as the agent’s Outbound page. Each action asks for confirmation; Stop requires a reason. Starting or resuming makes the batch eligible for the scheduler, not proof that a recipient was contacted. The list’s single_call flag identifies one-call checkpoints, which do not offer batch lifecycle actions. Attempts show status and time without recipient numbers or provider messages.
The answer is 201 with a call object: id (call-...), agent_id, channel (phone), the dispatcher’s real status (answered on connection), direction (outbound), caller and to, agent_version_id, agent_version_number, batch_id, attempt and settings. The request is checkpointed as a one-call batch before dialing. GET /v1/calls/{id} reads that dispatch record in the same workspace; this checkpoint is not a completed-call transcript or duration. Internal provider correlation ids never appear in the creation response. Before dialing, the number is checked against the workspace’s do-not-contact list and outbound limits, and nobody is called when either stops it:
- A number on the do-not-contact list is refused with
501 capability_unavailable,reasondo_not_contactin the error and a message that says the number is on the list. The one-call batch is still recorded, with that person marked as blocked, and the refusal is audited. - A reached limit answers
429 rate_limitedwithretry_after_sin the error, and a limit that cannot be read answers503 service_unavailable. Nothing is recorded but the audit row (Owner features in the App).
409 idempotency_key_reused. Concurrent requests and uncertain dispatches are 409 request_in_progress. A timeout or process loss after dispatch begins does not release the reservation or reclaim it after two minutes. Reconcile the saved outbound attempt before making a new intent; do not use a new key to blindly retry an uncertain call.
Phone numbers
GET /v1/phone-numbers?agent_id=... lists the numbers bound to an agent, with the carrier readback that proves where the number really points. POST /v1/phone-numbers with {"agent_id", "number"} binds a number to an agent (owner role, phone_numbers:write). Binding is what makes a real call storable at all: an unbound number’s calls are never stored.
A developer does not need to know which telephony or voice platform Sempera runs to bind a number. Those are internal and may change.