# Platform conventions Every Simwood service shares the same gateway model, versioning scheme, and error conventions. They are documented here once and inherited by every service; a service's own pages cover only what is specific to it. ## Gateway model All services sit behind a single gateway at `api.simwood.com` and are reached at: ```plaintext https://api.simwood.com/{service}/{version}/... ``` plaintext The gateway handles authentication and routing; each service owns its own contract behind it. Because the host, authentication scheme, and error shapes are identical across services, integrating an additional service means learning only its endpoints — not a new client. ## Versioning The version is part of the URL path (`/v1`, `/v2`), and each service versions independently. * **Breaking changes** ship under a new path version (`/v2`). * **Additive changes** — new endpoints, new optional fields — ship within the current version and do not break existing integrations. ## Errors All services return a consistent JSON error shape: ```json { "error": "Short error description", "detail": "Optional longer explanation" } ``` json Standard status codes used across the platform: | Status | Meaning | | ------ | --------------------------------------------- | | `400` | Malformed request — missing or invalid fields | | `401` | Missing or invalid API key | | `403` | Valid key, but it lacks the required scope | | `404` | Resource not found | | `409` | Conflict — resource already exists | | `422` | Request is valid but cannot be processed | | `429` | Rate limit exceeded | | `500` | Internal server error | Individual services may document additional, service-specific status codes in their own reference. # Authentication Every request to a Simwood service must be authenticated. Machine-to-machine integrations authenticate with an **API key** sent as a Bearer token. The same key and mechanism work across every service on the platform. ## Get an API key API keys are issued from the [Customer Portal](https://portal.simwood.com). Keys look like: ```plaintext sk_live_<48 hex characters> ``` plaintext ![](phosphor/regular/warning) The full key is shown **once**, at the moment it is created. Store it securely — Simwood only keeps a hash and cannot show it to you again. If a key is lost or leaked, revoke it and issue a new one. Keys do not expire and have no refresh flow. Revocation is immediate. ## Authenticate a request Send the key in the `Authorization` header as a Bearer token: ```bash curl https://api.simwood.com/{service}/{version}/... \ -H "Authorization: Bearer sk_live_abc123..." ``` bash You can also paste a key into the **Test Request** panel in any service's API reference to try endpoints live from your browser. ## Scopes Authentication proves _who_ you are; **scopes** control _what_ you can do. Each key is granted a set of scopes following the pattern `resource:action`: | Action | Grants | | ------------------ | --------------------------------------------- | | `:read` | Read operations — list and retrieve resources | | `:write` | Create and modify resources | | `:delete` | Delete resources | | `:admin` | Administrative operations | Each service defines its own scopes under its own name — for example, the Conversation Intelligence service uses `intelligence:read`, `intelligence:write`, and so on. See a service's pages for the exact scopes it defines. A request made with a key that lacks the required scope is rejected with `403 Forbidden`. Request the scopes you need when you create a key in the portal. For the JSON error shape returned on authentication and authorization failures, see [Platform conventions](/conventions#errors). # Wholesale API (v3) The **Wholesale API** is the original Simwood API, served at `https://api.simwood.com/v3`. It has long been the single API through which the Simwood platform is consumed. We are gradually migrating from the single `/v3` surface to **independent, versioned service APIs** — such as [Conversation Intelligence](/services/intelligence/overview) — each with its own contract and versioning. The platform isn't changing; the way you consume it is. ## What this means for you * **The Wholesale API (v3) remains available and supported** — existing integrations keep working, nothing stops responding overnight — but it is feature-frozen. * **New capabilities arrive in the relevant service APIs**, not in `/v3`. * **Existing capabilities move out of `/v3` over time**, each becoming its own versioned service. As a capability is re-expressed as a service, the corresponding part of `/v3` is deprecated and signalled through the standard [deprecation policy](/deprecation), giving you a window to migrate. * **For new integrations**, build against the individual service APIs rather than `/v3`. ## Documentation The Wholesale API keeps its own dedicated documentation, separate from this site. For the full v3 reference — endpoints, parameters, and examples — see: ![](phosphor/regular/info) **[Wholesale API (v3) documentation →](https://developer.simwood.com/docs/wholesale/api/v3)** `https://developer.simwood.com/docs/wholesale/api/v3` # Introduction The Simwood Carrier Services platform consists of a set of independent, versioned APIs that sit behind a single gateway at `api.simwood.com`. Each service owns its own contract and versioning, so you can adopt new capabilities without breaking existing integrations. ![](phosphor/regular/info) Every service is reached at `https://api.simwood.com/{service}/{version}/...` — for example, `https://api.simwood.com/intelligence/v1` for v1 of the Conversation Intelligence service. All services share the same authentication, versioning, and error conventions, documented once in the platform guides below. Each service then documents only what is specific to it. ## Available services | Service | Base path | Status | Reference | | ---------------------------------------------------------------- | ------------------ | ------ | -------------------------------------------------- | | **[Conversational AI](/services/conversational-ai/overview)** | `/convai/v1` | Beta | [Reference](/services/conversational-ai/reference) | | **[Conversation Intelligence](/services/intelligence/overview)** | `/intelligence/v1` | Beta | [Reference](/services/intelligence/reference) | More services are coming online and will be published here as they launch. The original [Wholesale API (v3)](/wholesale-api) remains available while its capabilities move into these versioned services. ## New here? Start with [Getting started](/getting-started) to make your first authenticated request, then explore whichever services you need. [Getting startedGet an API key and make your first request.](/getting-started)[Authentication & authorizationAPI keys, Bearer tokens, and scopes.](/authentication)[Platform conventionsGateway model, versioning, and error shapes shared by every service.](/conventions) # Getting started This guide takes you from zero to your first authenticated request against any Simwood service. ## 1. Get an API key Generate a key in the [Customer Portal](https://portal.simwood.com). Each key carries a set of scopes that control what it can do — see [Authentication & authorization](/authentication) for the full details. ![](phosphor/regular/warning) The full key is shown **once**, at creation. Store it securely — Simwood keeps only a hash and cannot show it to you again. ## 2. Make your first request Send your key as a Bearer token in the `Authorization` header. Every service is reached at the same gateway, so the only things that change between services are the path and the payload: ```bash # Replace {service}/{version} with the service you're calling, e.g. intelligence/v1 curl https://api.simwood.com/{service}/{version}/... \ -H "Authorization: Bearer sk_live_..." ``` bash ## 3. Explore a service Each service documents its own endpoints, concepts, and workflows. Browse the [available services](/) and open the one you need to see its overview and API reference. # Deprecation policy When an API version is being retired, Simwood signals it programmatically so you can detect and migrate ahead of time. This policy applies uniformly across every service. ## Deprecation headers A deprecated version includes these response headers: ```plaintext Deprecation: true Sunset: 2027-06-01 Link: ; rel="successor-version" ``` plaintext * `Deprecation` — the version is deprecated. * `Sunset` — the date after which the version may stop responding. * `Link` with `rel="successor-version"` — where to migrate. Watch for these headers in your client so you can surface deprecation warnings automatically rather than discovering a sunset by outage. ## Support window A deprecated version is supported for a **minimum of 12 months** after its successor is published, giving you time to migrate. # Overview Conversational AI lets you deploy AI voice agents that hold natural conversations over the phone. An agent answers calls, speaks with callers using synthesised speech, understands their intent, and can carry out actions — all without human involvement. This reference covers the Conversational AI API — the HTTP interface you use to create and configure agents, manage the knowledge they draw on, start text-only sessions, and receive conversation events. ![](phosphor/regular/info) Base path: `https://api.simwood.com/convai/v1`. Authentication, versioning, and error shapes follow the shared [platform conventions](/conventions). ## When to use it Reach for Conversational AI when you need AI-driven telephony: an agent that handles inbound calls on your SIP infrastructure, responds naturally to callers, and produces a full transcript and analysis on every call. You can also embed a text-only agent in a web or mobile application using the session endpoint. ## Core concepts The service is built from a small set of primitives. The [Concepts](/services/conversational-ai/concepts) page explains each one on its own terms; the guides and API reference link back here rather than re-explaining them. * **Agents** — the core entity: a voice and a prompt that define how the agent speaks and behaves. * **Voices** — the catalogue of speech synthesis voices an agent can use. * **Knowledge base** — documents the agent can retrieve from during a conversation. * **Webhooks** — HTTP callbacks that fire when a conversation starts or ends. * **SIP UAC** — the SIP registration that connects an agent to your telephony infrastructure. * **Conversations** — the record of what was said, by whom, and when, plus any analysis. [ConceptsThe building blocks of the Conversational AI API.](/services/conversational-ai/concepts)[Create your first agentA step-by-step walkthrough: agent → SIP registration → live call.](/services/conversational-ai/guides/create-your-first-agent)[API referenceEndpoints, parameters, and schemas.](/services/conversational-ai/reference) # Create your first agent This guide walks the fastest path to a working voice agent: create an agent with a prompt and a voice, configure its SIP registration, then make a test call and retrieve the transcript. The examples use the Conversational AI base path `https://api.simwood.com/convai/v1` and a Bearer API key. See [Authentication](/authentication) for how keys and scopes work; the scopes used below are `convai:read` and `convai:write`. ![](phosphor/regular/info) You'll need an API key with `convai:read` and `convai:write`, and access to a SIP-capable PBX or SIP proxy that can route calls to the agent's registered address of record. ## 1. Choose a voice Browse the voice catalogue to find a voice that fits your agent's persona. Listen to the preview URL before committing. ```bash curl https://api.simwood.com/convai/v1/voices \ -H "Authorization: Bearer sk_live_..." ``` bash Pick a voice from the response and note its `id` — you'll use it in the next step. ```bash # Inspect a specific voice curl https://api.simwood.com/convai/v1/voices/ \ -H "Authorization: Bearer sk_live_..." ``` bash ## 2. Create the agent Create an agent with a minimal configuration: a prompt, a voice, and sensible defaults for the other settings. You can refine the configuration later with `PUT /agents/:id`. ```bash curl -X POST https://api.simwood.com/convai/v1/agents \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "name": "My first agent", "configuration": { "asr": { "keywords": [] }, "turn": { "timeout_seconds": 10, "silence_timeout_seconds": 3 }, "tts": { "voice_id": "", "settings": { "stability": 0.5, "similarity_boost": 0.75, "speed": 1.0 } }, "agent": { "first_message": "Hello, thanks for calling. How can I help you today?", "language": "en", "prompt": { "text": "You are a helpful assistant. Answer questions clearly and concisely. If you cannot help, say so politely and offer to transfer the caller.", "temperature": 0.5, "model": "gpt-4o" }, "knowledge_base": [], "tools": [] }, "conversation": { "max_duration_seconds": 600 } } }' ``` bash The response includes the agent's `id` — save it for the following steps. ## 3. Configure SIP registration Register the agent to your PBX so it can receive calls. The `PUT /agents/:id/sip-uac` operation is an upsert — it creates or replaces the registration in one call. ```bash curl -X PUT https://api.simwood.com/convai/v1/agents//sip-uac \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "sip_username": "agent-001", "sip_domain": "pbx.example.com", "sip_password": "your-sip-password" }' ``` bash The agent registers immediately. Confirm it is registered by checking your PBX's registration list — the agent's address of record will appear as `agent-001@pbx.example.com` (or whatever your `sip_username@sip_domain` resolves to). ![](phosphor/regular/info) If your registrar address differs from `sip_domain`, pass it explicitly as `sip_registrar`. See [SIP UAC](/services/conversational-ai/concepts/sip-uac) for the full set of fields. ## 4. Make a test call Route a call to the agent's registered address from your PBX. The agent answers, says its `first_message`, and begins the conversation. End the call when you're done. ## 5. Retrieve the transcript Once the call ends, the conversation record is available. List the agent's conversations to find it: ```bash curl https://api.simwood.com/convai/v1/agents//conversations \ -H "Authorization: Bearer sk_live_..." ``` bash Retrieve the full transcript and analysis from the most recent conversation: ```bash curl https://api.simwood.com/convai/v1/agents//conversations/ \ -H "Authorization: Bearer sk_live_..." ``` bash The `transcript` array contains each turn — who said what and when. If you defined `analysis.data_collection` on the agent, the extracted values appear in `analysis.data_collection_results`. ## Next steps [Manage a knowledge baseGive your agent access to documents it can retrieve from during conversations.](/services/conversational-ai/guides/manage-knowledge-base)[Receive conversation eventsConfigure a webhook to receive transcripts and analysis as soon as calls end.](/services/conversational-ai/guides/receive-conversation-events)[AgentsThe full agent configuration reference.](/services/conversational-ai/concepts/agents) # Receive conversation events This guide walks through configuring a webhook and handling the two event types the platform delivers: `conversation.initiating` (when a call is answered) and `conversation.completed` (when a call ends). ![](phosphor/regular/info) You'll need an API key with `convai:read` and `convai:write`, and a publicly accessible HTTPS endpoint to receive webhook deliveries. ## 1. Configure a webhook Create a webhook by posting to `/webhooks`. The platform will POST to your `url` whenever the specified event fires. Use `request_headers` to pass a shared secret so your endpoint can verify deliveries. ```bash curl -X POST https://api.simwood.com/convai/v1/webhooks \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "event_type": "conversation.completed", "url": "https://example.com/hooks/calls", "request_headers": { "X-Webhook-Secret": "your-shared-secret" } }' ``` bash To also receive initiating events, create a second webhook: ```bash curl -X POST https://api.simwood.com/convai/v1/webhooks \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "event_type": "conversation.initiating", "url": "https://example.com/hooks/calls", "request_headers": { "X-Webhook-Secret": "your-shared-secret" } }' ``` bash These webhooks fire for every conversation on your account. To scope them to a single agent, add `"agent_id": ""` to either request body. ## 2. Handle `conversation.initiating` The `initiating` event fires when an inbound call arrives, **before** it is answered. The caller hears ringing while the platform waits for your response. You have up to the webhook's configured `timeout_ms` (default: 5 seconds) to reply — use this window to look up caller-specific data and return it as overrides for this call. Your endpoint receives: ```json { "type": "conversation.initiating", "timestamp": 1719820800, "data": { "conversation_id": "conv-uuid-...", "agent_id": "agent-uuid-..." } } ``` json Respond with any combination of the following fields to override the agent's default configuration for this call: ```json { "dynamic_variables": { "customer_name": "Jane Smith", "account_status": "premium", "outstanding_balance": "£42.00" }, "conversation_config_override": { "text_only": false } } ``` json | Field | Type | Description | | ------------------------------ | ------ | --------------------------------------------------------------------------------- | | `dynamic_variables` | object | Key-value pairs injected into the agent's prompt as `{{dynamic_variables.}}` | | `conversation_config_override` | object | Overrides to the agent's `conversation` configuration for this call | Omit a field to use the agent's default. An empty `200` response is valid if you have no overrides. ![](phosphor/regular/info) If your endpoint does not respond within `timeout_ms`, the agent answers with its default configuration. Keep your handler fast — a simple database lookup or cache read is ideal; avoid anything that may block. ## 3. Handle `conversation.completed` The `completed` event fires when the call ends and the transcript is ready. Your endpoint receives the full conversation record: ```json { "type": "conversation.completed", "timestamp": 1719820943, "data": { "conversation_id": "conv-uuid-...", "agent_id": "agent-uuid-...", "status": "done", "metadata": { "start_time_unix_secs": 1719820800, "call_duration_secs": 143 }, "transcript": [ { "role": "agent", "message": "Hello, thanks for calling. How can I help you today?", "time_in_call_secs": 0.0 }, { "role": "user", "message": "I'd like to check my account balance.", "time_in_call_secs": 3.4 }, { "role": "agent", "message": "Your current balance is £42.00.", "time_in_call_secs": 6.1 } ], "analysis": { "data_collection_results": { "issue_resolved": true, "customer_intent": "check account balance" }, "call_successful": "success", "transcript_summary": "Caller asked for their account balance. Agent provided it." } } } ``` json Respond with `2xx` to acknowledge. The platform retries on non-`2xx` responses with exponential backoff. ## 4. Check webhook health After a few calls, confirm deliveries are landing: ```bash curl https://api.simwood.com/convai/v1/webhooks/ \ -H "Authorization: Bearer sk_live_..." ``` bash The response includes a `health` field (`healthy`, `degraded`, or `failing`) and `consecutive_failures`. A `failing` webhook has its deliveries suspended until it recovers — investigate your endpoint if the health degrades. [WebhooksWebhook scope, delivery, retry behaviour, and the full event payload reference.](/services/conversational-ai/concepts/webhooks)[ConversationsThe full conversation record and what each field means.](/services/conversational-ai/concepts/conversations) # Agents An agent is the core entity in Conversational AI. It defines what your AI says, how it sounds, what it knows, and how conversations are governed. Once an agent is created and its [SIP UAC](/services/conversational-ai/concepts/sip-uac) is configured, it registers to your telephony platform and begins handling calls. ## Configuration Every agent has a `configuration` object with five top-level sections: ### `asr` — speech recognition ```json "asr": { "keywords": ["booking", "cancellation", "account number"] } ``` json `keywords` hints the speech recognition engine toward domain-specific terms that might otherwise be misheard. ### `turn` — turn timing ```json "turn": { "timeout_seconds": 10, "silence_timeout_seconds": 3 } ``` json `timeout_seconds` is the maximum time the agent waits for a caller to respond before moving on. `silence_timeout_seconds` is how long a pause in the caller's speech is treated as them finishing their turn. ### `tts` — text to speech ```json "tts": { "voice_id": "a1b2c3d4-...", "settings": { "stability": 0.5, "similarity_boost": 0.75, "speed": 1.0 } } ``` json `voice_id` is the ID of a [voice](/services/conversational-ai/concepts/voices) from the voice catalogue. `settings` controls how the voice sounds: `stability` (0–1, lower is more expressive), `similarity_boost` (0–1, how closely the synthesised output matches the target voice), and `speed` (relative playback speed). ### `agent` — language model behaviour ```json "agent": { "first_message": "Hello, thanks for calling. How can I help you today?", "language": "en", "prompt": { "text": "You are a helpful customer service agent for Acme Corp...", "temperature": 0.5, "model": "gpt-4o" }, "knowledge_base": [ { "type": "url", "id": "doc-uuid-...", "source": "https://..." } ], "tools": [] } ``` json `first_message` is what the agent says when it answers the call. `prompt.text` is the system prompt that governs the agent's behaviour, tone, and scope. `knowledge_base` lists the [knowledge base documents](/services/conversational-ai/concepts/knowledge-base) the agent can retrieve from — reference them by their ID. ### `conversation` — limits ```json "conversation": { "max_duration_seconds": 600 } ``` json `max_duration_seconds` is a hard ceiling on how long a single conversation can run. ### `analysis` — data collection (optional) ```json "analysis": { "data_collection": { "customer_intent": { "type": "string", "description": "What the caller wanted to achieve" }, "issue_resolved": { "type": "boolean", "description": "Whether the caller's issue was resolved" } } } ``` json When `analysis.data_collection` is defined, the agent extracts the named fields from the conversation and includes them in the [conversation record](/services/conversational-ai/concepts/conversations) and `conversation.completed` [webhook](/services/conversational-ai/concepts/webhooks) payloads. ## Agent generation Rather than composing a configuration from scratch, you can use `POST /agents/generate` to create an agent automatically from a high-level description. Pass `industry`, `use_case`, and `goal` to receive a fully-formed agent with a prompt appropriate for that context. `GET /agents/generate-options` returns the list of supported `industry` and `use_case` values. ## Properties | Property | Type | Description | | --------------- | ----------------- | ------------------------------------ | | `id` | string (UUID) | Stable identifier for this agent | | `name` | string | Display name | | `configuration` | object | Full agent configuration (see above) | | `created_at` | string (ISO 8601) | When the agent was created | | `updated_at` | string (ISO 8601) | When the agent was last modified | ## Lifecycle An agent is created with `POST /agents` and updated with `PUT /agents/:id`. Updates take effect on new calls — calls already in progress are not affected. An agent can be deleted with `DELETE /agents/:id`; this terminates any active SIP registration. [VoicesHow to browse and select a voice for your agent.](/services/conversational-ai/concepts/voices)[Knowledge baseHow to upload documents and reference them in an agent configuration.](/services/conversational-ai/concepts/knowledge-base)[SIP UACConnecting an agent to your telephony platform.](/services/conversational-ai/concepts/sip-uac)[Create your first agentStep-by-step: agent → SIP registration → live call.](/services/conversational-ai/guides/create-your-first-agent) # Knowledge base Knowledge base documents give an agent access to information it can retrieve during a conversation. When a caller asks a question, the agent searches the knowledge base and uses what it finds to construct an accurate answer — rather than relying solely on what its prompt describes. Typical documents: product catalogues, FAQs, support articles, pricing sheets, terms and conditions. ## Document types | Type | How to create | When to use | | ------ | --------------------------- | ---------------------------------------------------------- | | `url` | `POST /knowledge-base/url` | Public web pages the platform fetches and indexes | | `text` | `POST /knowledge-base/text` | Raw text you supply directly | | `file` | `POST /knowledge-base/file` | Uploaded files (`.txt`, `.pdf`, `.docx`, `.epub`, `.html`) | ## Adding documents ### From a URL ```bash curl -X POST https://api.simwood.com/convai/v1/knowledge-base/url \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "name": "Support FAQ", "url": "https://example.com/support/faq" }' ``` bash ### From text ```bash curl -X POST https://api.simwood.com/convai/v1/knowledge-base/text \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "name": "Cancellation policy", "text": "Customers may cancel any subscription within 30 days of purchase..." }' ``` bash ### From a file ```bash curl -X POST https://api.simwood.com/convai/v1/knowledge-base/file \ -H "Authorization: Bearer sk_live_..." \ -F "name=Product catalogue" \ -F "file=@catalogue.pdf" ``` bash The response includes the document's `id` — save it to reference from agent configurations. ## Referencing documents in an agent Documents are added to an agent's `configuration.agent.knowledge_base` array. Each entry references a document by its `id` and the same `type` it was created with: ```json "knowledge_base": [ { "type": "url", "id": "doc-uuid-1", "source": "https://example.com/support/faq" }, { "type": "text", "id": "doc-uuid-2", "source": "Cancellation policy" }, { "type": "file", "id": "doc-uuid-3", "source": "catalogue.pdf" } ] ``` json An agent can reference multiple documents. During a conversation it searches across all of them. ## Managing documents ```bash # List all documents on your account curl https://api.simwood.com/convai/v1/knowledge-base \ -H "Authorization: Bearer sk_live_..." # Retrieve a specific document curl https://api.simwood.com/convai/v1/knowledge-base/ \ -H "Authorization: Bearer sk_live_..." # Delete a document curl -X DELETE https://api.simwood.com/convai/v1/knowledge-base/ \ -H "Authorization: Bearer sk_live_..." ``` bash ![](phosphor/regular/info) Deleting a document does not automatically update agents that reference it. Remove the entry from `configuration.agent.knowledge_base` on any affected agent with `PUT /agents/:id` before or after deletion. [Manage a knowledge baseStep-by-step: add documents, reference them in an agent, and remove them when no longer needed.](/services/conversational-ai/guides/manage-knowledge-base)[AgentsHow knowledge base documents fit into agent configuration.](/services/conversational-ai/concepts/agents) # Webhooks Webhooks deliver conversation events to your infrastructure as HTTP POST requests. Configure a webhook once and the platform calls your endpoint automatically whenever the event fires — no polling required. ## Event types | Event type | When it fires | | ------------------------- | -------------------------------------------------------------------------- | | `conversation.initiating` | A call has been answered and the agent is about to speak its first message | | `conversation.completed` | The call has ended and the full transcript and analysis are available | ## Webhook scope A webhook can be scoped to your entire account or to a single agent: * **Account-wide**: fires for every conversation on your account, regardless of which agent handled it. Create without an `agent_id`. * **Agent-specific**: fires only for conversations handled by the specified agent. Create with an `agent_id`. If both apply to a conversation, both fire independently. ## Creating a webhook ```bash curl -X POST https://api.simwood.com/convai/v1/webhooks \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "event_type": "conversation.completed", "url": "https://example.com/hooks/calls", "request_headers": { "X-Webhook-Secret": "your-secret-value" } }' ``` bash To scope to a specific agent, add `"agent_id": ""` to the body. ### Request headers `request_headers` are included verbatim in every delivery to your endpoint. Use them to pass a shared secret or authentication token so you can verify that requests come from the platform. ![](phosphor/regular/info) One webhook per event type per scope is allowed. Attempting to create a second `conversation.completed` webhook at the account level returns `409 Conflict`. Create an agent-scoped webhook if you need different behaviour per agent. ## Event payloads ### `conversation.initiating` Fires when an inbound call arrives, before it is answered. The caller hears ringing while the platform waits for your response — up to the webhook's `timeout_ms` (default: 5 seconds). Respond within that window to inject per-call context; if your endpoint does not respond in time, the agent answers with its default configuration after the full timeout has elapsed. Return an empty `200` immediately if you have no overrides — this avoids making the caller wait unnecessarily. ```json { "type": "conversation.initiating", "timestamp": 1719820800, "data": { "conversation_id": "conv-uuid-...", "agent_id": "agent-uuid-..." } } ``` json Your response body may include any of the following fields to override the agent's default configuration for this call: | Field | Type | Description | | ------------------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `dynamic_variables` | object | Key-value pairs available as `{{dynamic_variables.}}` in the agent's prompt — use to personalise the conversation with caller-specific data | | `conversation_config_override` | object | Overrides to the agent's `conversation` configuration for this call (e.g. `text_only`) | Omit a field entirely to use the agent's default. An empty `200` response is valid if you have no overrides. ### `conversation.completed` Fires when the call ends. The full transcript and any analysis results are included. ```json { "type": "conversation.completed", "timestamp": 1719820943, "data": { "conversation_id": "conv-uuid-...", "agent_id": "agent-uuid-...", "status": "done", "metadata": { "start_time_unix_secs": 1719820800, "call_duration_secs": 143 }, "transcript": [ { "role": "agent", "message": "Hello, thanks for calling.", "time_in_call_secs": 0.0 }, { "role": "user", "message": "I'd like to cancel my subscription.", "time_in_call_secs": 3.4 } ], "analysis": { "data_collection_results": { "issue_resolved": true }, "call_successful": "success", "transcript_summary": "Caller cancelled their subscription." } } } ``` json ## Delivery and retry The platform delivers each event with a short timeout. If your endpoint does not respond with a `2xx` status, delivery is retried with exponential backoff. The platform tracks consecutive failures per webhook; a webhook that fails repeatedly is marked `failing` and delivery is suspended until it recovers. Check the health of a webhook with `GET /webhooks/:id`. ## Managing webhooks ```bash # List all webhooks on the account curl https://api.simwood.com/convai/v1/webhooks \ -H "Authorization: Bearer sk_live_..." # Filter by agent curl "https://api.simwood.com/convai/v1/webhooks?agent_id=" \ -H "Authorization: Bearer sk_live_..." # Filter by event type curl "https://api.simwood.com/convai/v1/webhooks?event_type=conversation.completed" \ -H "Authorization: Bearer sk_live_..." # Delete a webhook curl -X DELETE https://api.simwood.com/convai/v1/webhooks/ \ -H "Authorization: Bearer sk_live_..." ``` bash [Receive conversation eventsEnd-to-end walkthrough: configure a webhook and handle each event type.](/services/conversational-ai/guides/receive-conversation-events)[ConversationsThe full conversation record delivered in the completed event.](/services/conversational-ai/concepts/conversations) # Overview Conversation Intelligence is built from a small set of primitives. Each one is explained here on its own terms; the workflow guides and the [API reference](/services/intelligence/reference) link back to these pages rather than re-explaining them. The primitives fall into three groups: what you define to analyse a conversation, the policy that composes those definitions, and what runs and what you get back. ## Define what to analyse [OperatorsA single, reusable unit of analysis: a prompt paired with a typed output. Detects, classifies, scores, extracts, or tags one thing about a conversation.](/services/intelligence/concepts/operators)[TemplatesPlatform-maintained, read-only starting points for a configuration, pre-populated for a use case such as fraud detection or contact centre.](/services/intelligence/concepts/templates) ## Compose a policy [ConfigurationsThe policy a conversation is analysed under: which operators run and how, and the actions that fire on their results. The unit you select per call.](/services/intelligence/concepts/configurations)[ActionsDelivery instructions defined within a configuration: webhooks and whispers that fire when operator results meet a condition.](/services/intelligence/concepts/actions) ## Run and retrieve [BindingsMake a configuration the default for a point of ingress — a voice trunk or number — so every conversation arriving there is analysed automatically, with no per-call API call.](/services/intelligence/concepts/bindings)[SessionsAttach a configuration to a single live call and run it in realtime — turn by turn as the conversation unfolds, and once when it ends.](/services/intelligence/concepts/sessions)[ConversationsThe durable record of everything that happened across one or more sessions on the same call — the unit you query and report on afterwards.](/services/intelligence/concepts/conversations) # Configurations A **configuration** is the policy a conversation is analysed under. It composes a set of [operators](/services/intelligence/concepts/operators) — each with its own granularity and context settings — together with the [actions](/services/intelligence/concepts/actions) that fire when their results meet a condition. The configuration is the unit you select at run time: attach one to a trunk so it applies to every call, or supply one per call when you start a [session](/services/intelligence/concepts/sessions). Configurations are owned by your account and addressed by a stable `uuid` that survives every edit. ## Granularity Granularity decides _when_ each operator runs. It is set on the operator reference inside the configuration, not on the operator itself — so the same operator can run at different cadences in different configurations, or even more than once within one configuration. | Setting | Behaviour | | ------------------------------- | ------------------------------------------------------------- | | `on: turn` | Run on each conversational turn | | `on: turn`, `every: N` | Run every N turns | | `on: turn`, `until_match: true` | Run each turn, then stop once a non-null result is produced | | `on: conversation_end` | Run once when the conversation ends, over the full transcript | A `turn` operator produces live signals as the conversation unfolds; a `conversation_end` operator produces a single post-conversation result. A configuration can mix both freely. ## Context settings Each operator reference can widen the context the operator reasons over: * **`context_window`** — how many prior turns to include in the operator's input. * **`include_prior_results`** / **`prior_results_window`** — inject the operator's own recent results back into its context, for cumulative reasoning over a running assessment. The window defaults to a small number of recent results; set it to `1` for accumulating `multi_tag` operators, where the latest result already holds the complete set. * **`include_operator_results`** / **`operator_results_window`** — inject the recent results of _other_ named operators in the same configuration, so one operator can reason over another's findings (for example, a guidance operator reading a protocol-adherence result). * **`parameters`** — values for the operator's declared [parameters](/services/intelligence/concepts/operators#parameters), keyed by parameter name. ## Operator references and versioning Each operator reference pins to a specific operator version. Editing an operator never silently changes a configuration that references it — the reference holds its pinned version until you upgrade it explicitly. Omit `version` when adding an operator to bind to its current version. When a newer version later exists, the resolved reference reports `current_version` and sets `upgrade_available`, and `POST /configurations/{uuid}/upgrade-operators` repoints references to the current version in one step. ## Identity and lifecycle * **`uuid`** — the stable identifier. It never changes, so long-lived bindings (a trunk, a session request) keep resolving to the same configuration. * **`version`** — a generation counter bumped on each write. It records that the configuration changed; it is not an addressing axis, and there is no per-version history. * **`is_active`** — whether the configuration is selectable for new sessions. * **`sync_state`** — `live` once the running pipeline reflects your latest edit, or `pending` until the next sync cycle picks it up. An edit is durable immediately; it takes effect on new sessions once `sync_state` is `live`. ## Validating before you save `POST /configurations/validate` runs the same checks as a real write without persisting anything, returning `valid` plus any human-readable `errors`. Use it as a dry run before creating or replacing a configuration. ## Creating a configuration A configuration can be built from scratch, or derived from a [template](/services/intelligence/concepts/templates) — a pre-populated starting point you complete and save as your own. Either way the result is a configuration you own, identified by its own `uuid`. [API reference — ConfigurationsCreate, validate, replace, patch, and upgrade configurations.](/services/intelligence/reference) # Templates A **template** is a platform-maintained, read-only starting point for a [configuration](/services/intelligence/concepts/configurations), pre-populated for a use case. Templates show the kind of analysis a use case calls for, so you can adopt a working setup and adjust it rather than assembling one from scratch. ## What a template contains A template's `definition` is a complete configuration document — the [operators](/services/intelligence/concepts/operators) to run, their granularities and context settings, and the [actions](/services/intelligence/concepts/actions) to fire — with one deliberate gap: actions are defined without their delivery destinations. The granularities reflect a suggested setup for the use case, not a fixed one. Templates are maintained by Simwood and available to every account; you cannot edit a template itself. ## Applying a template You apply a template on your side, in three steps: 1. **Fetch** the template and read its `definition`. 2. **Complete** the fields it leaves open — at minimum each action's `webhook_url` — and adjust operator selection, granularity, and context to suit. 3. **Create** a configuration from the result. The configuration you create is your own, owned by your account and identified by its own `uuid`. You can record where it came from by passing `template_id`, which is kept for lineage only and never merges anything from the template back in — later changes to the template never affect a configuration you have already created. [API reference — TemplatesList and retrieve configuration templates.](/services/intelligence/reference) # Sessions A **session** attaches a [configuration](/services/intelligence/concepts/configurations) to a single live call and runs it in realtime. While the call is connected, the session taps the audio and runs the configured [operators](/services/intelligence/concepts/operators) — turn by turn as the conversation unfolds, and once over the full transcript when the call ends. ## Starting a session You create a session with the call to analyse and the configuration to apply: * **`call_id`** — the live call to analyse. It must already be known to the platform. * **`configuration_id`** — the `uuid` of the configuration to apply. It must be one of your account's active configurations. * **`customer_metadata`** — an optional opaque object stored with the session and echoed back, verbatim, on every webhook the session fires. Use it to carry your own correlation keys. * **`transcript_events`** — when true, transcript turns are emitted on the session's event stream alongside operator results. The response returns a `session_id`; use it on every other session endpoint. You don't have to start sessions yourself: assign a [configuration](/services/intelligence/concepts/configurations) to a trunk and the platform starts a session automatically for every call over it. Creating a session explicitly is the per-call alternative, for when you choose the configuration dynamically or attach per-call metadata. ## Lifecycle A session runs for the life of the call and **ends automatically when the call ends** — you don't tear it down yourself. As it ends, any `conversation_end` operators run over the full transcript. Within one session a configuration can blend both cadences: `turn` operators give you live signals during the call, while `conversation_end` operators produce the post-call analysis. A session moves through a `status` over its life — `established` while running, then `ending`, `ended`, `failed`, or `timed_out`. ## Retrieving results A live session exposes its results two ways: * **Stream** — call `GET /sessions/{session_id}/stream-token` for a short-lived token, then connect to `GET /sessions/{session_id}/events` to receive operator results over Server-Sent Events as they are produced. See [Session events](/services/intelligence/events/session-events) for the event types and payload shapes. * **Snapshot** — call `GET /sessions/{session_id}/operator-results` for the latest result per operator without holding a stream open. Results can also be **pushed** to your systems as [webhooks](/services/intelligence/events/webhooks) when they meet a condition, without retrieving them yourself. ## Changing or ending a session early `PATCH /sessions/{session_id}` swaps the configuration in force from that point in the conversation onward, or toggles transcript streaming for the rest of the session — so you can adapt the analysis mid-call. `DELETE /sessions/{session_id}` ends a session _before_ the call ends, when you want to stop analysing early; in the normal case the session ends on its own when the call does. ![](phosphor/regular/info) A session is live and ephemeral. What persists after the call is the [conversation](/services/intelligence/concepts/conversations) — the durable record you query afterwards. [API reference — SessionsCreate, update, stream, and end sessions.](/services/intelligence/reference) # Analyse a live call This guide walks the full path: compose a [configuration](/services/intelligence/concepts/configurations), attach it to a live call as a [session](/services/intelligence/concepts/sessions), receive results as the call unfolds, and retrieve the durable [conversation](/services/intelligence/concepts/conversations) afterwards. Each step links back to the concept it builds on rather than re-explaining it. The examples use the Conversation Intelligence base path `https://api.simwood.com/intelligence/v1` and a Bearer API key. See [Authentication](/authentication) for how keys and scopes work; the scopes used below are `intelligence:write` and `intelligence:read`. ![](phosphor/regular/info) You'll need an API key with `intelligence:write` and `intelligence:read`, and a live call already known to the platform (its `call_id`). ## 1. Choose your operators An [operator](/services/intelligence/concepts/operators) is one unit of analysis. The platform maintains a catalogue you can use as-is — list them to see what's available: ```bash curl https://api.simwood.com/intelligence/v1/operators \ -H "Authorization: Bearer sk_live_..." ``` bash This guide uses two platform operators: `fraud-confidence` (a `score`) and `fraud-indicators` (a `multi_tag`). If you need analysis the catalogue doesn't cover, [create a custom operator](/services/intelligence/concepts/operators#platform-and-custom-operators) — the rest of the flow is identical. ## 2. Create a configuration A configuration names the operators to run, the [granularity](/services/intelligence/concepts/configurations#granularity) each runs at, and the [actions](/services/intelligence/concepts/actions) that fire on their results. Here, both operators run every five turns, and a `webhook` action posts to your endpoint when fraud confidence crosses 80 with high confidence: ```bash curl -X POST https://api.simwood.com/intelligence/v1/configurations \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "name": "fraud-detection", "operators": [ { "name": "fraud-confidence", "granularity": { "on": "turn", "every": 5 }, "include_prior_results": true }, { "name": "fraud-indicators", "granularity": { "on": "turn", "every": 5 } } ], "actions": [ { "type": "webhook", "webhook_url": "https://example.com/hooks/fraud", "once_per_session": true, "condition": { "operator": "fraud-confidence", "expression": "result.value > 80 AND confidence > 0.75" } } ] }' ``` bash The response includes the configuration's `uuid` — its stable identifier — and a `sync_state`. Wait until `sync_state` is `live` before starting a session, so the running pipeline reflects your new configuration. ![](phosphor/regular/info) To check a configuration before saving it, post the same body to `POST /configurations/validate` for a dry run that returns `valid` plus any `errors` without persisting anything. ## 3. Start a session on the live call A session attaches the configuration to one live call and runs it in realtime. Pass the `call_id` and the configuration's `uuid`; the optional `customer_metadata` is echoed back, verbatim, on every webhook this session fires: ```bash curl -X POST https://api.simwood.com/intelligence/v1/sessions \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "call_id": "call-abc-123", "configuration_id": "5f1c…", "customer_metadata": { "your_ref": "ticket-9981" } }' ``` bash The response returns a `session_id`. Use it on every other session endpoint. The session then runs for the life of the call — it ends automatically when the call ends, so you don't tear it down yourself. ![](phosphor/regular/info) **You don't have to start sessions yourself.** Assign a configuration to a trunk in your trunk settings, and every call over that trunk is analysed automatically — the platform starts a session for each call and applies the configuration, with no call to `POST /sessions`. Starting a session explicitly, as above, is the per-call alternative: reach for it when you choose the configuration dynamically or attach per-call `customer_metadata`. ## 4. Receive results as the call unfolds You have three ways to consume results, and they work together: **Webhooks fire automatically.** The action you defined in step 2 posts to your endpoint whenever its condition is met. Your `customer_metadata` and a summary of the operators the condition tested arrive in the [webhook envelope](/services/intelligence/events/webhooks) — no polling required. **Stream results live.** Fetch a short-lived stream token, then open a Server-Sent Events connection to receive every operator result as it is produced. See [Session events](/services/intelligence/events/session-events) for the full list of event types and their payloads: ```bash # 1. Get a stream token curl https://api.simwood.com/intelligence/v1/sessions//stream-token \ -H "Authorization: Bearer sk_live_..." # 2. Connect to the event stream — authenticated by the token alone, no API key curl -N "https://api.simwood.com/intelligence/v1/sessions//events?token=" ``` bash **Snapshot the latest results.** When you only need the current state, fetch the latest result per operator without holding a stream open: ```bash curl https://api.simwood.com/intelligence/v1/sessions//operator-results \ -H "Authorization: Bearer sk_live_..." ``` bash ![](phosphor/regular/info) To adapt the analysis mid-call, `PATCH /sessions/` swaps the configuration in force from that point onward. To stop analysis _before_ the call ends, `DELETE /sessions/`. ## 5. Retrieve the conversation afterwards When the call ends, the session ends on its own and any `conversation_end` operators run over the full transcript. The session was live and ephemeral; the [conversation](/services/intelligence/concepts/conversations) is the durable record. Find it by the call's `transport_key`, then read its operator results and transcript: ```bash # Find the conversation for this call curl "https://api.simwood.com/intelligence/v1/conversations?transport_key=call-abc-123" \ -H "Authorization: Bearer sk_live_..." # Read its operator results curl https://api.simwood.com/intelligence/v1/conversations//operator-results \ -H "Authorization: Bearer sk_live_..." # Page through its transcript curl https://api.simwood.com/intelligence/v1/conversations//transcript \ -H "Authorization: Bearer sk_live_..." ``` bash From here you can list and query conversations across all of your calls — by call, end user, medium, status, or time range — rather than auditing a sample. ## Next steps [Start from a templateAdopt a pre-built configuration for a use case and complete it, instead of composing one from scratch.](/services/intelligence/concepts/templates)[API referenceEvery endpoint, parameter, and schema used in this guide.](/services/intelligence/reference) # Create a custom operator The platform [operator](/services/intelligence/concepts/operators) catalogue covers common analysis, but you can define your own for anything specific to your business. A custom operator is the same shape as a platform one — a prompt paired with a typed output — and is used in [configurations](/services/intelligence/concepts/configurations) exactly the same way. This guide creates one, then references it in a configuration. The examples use the base path `https://api.simwood.com/intelligence/v1` and a Bearer API key — see [Authentication](/authentication). You'll need `intelligence:write`. ## 1. Choose an output type Decide what shape the result should take, from the [output type vocabulary](/services/intelligence/concepts/operators#output-types). The type fixes the result shape and what you can query and gate actions on: * a `classification` for one value from a set, * a `boolean` for detected-or-not, * a `score` for a numeric assessment, * a `multi_tag` for zero or more values from a set, * an `extraction` for a specific piece of information, * `text` for free-form generated output. This guide builds a `classification` operator that grades complaint severity. ## 2. Write the prompt and output config The `prompt` is natural-language instruction to the model describing what to look for and how to reason about it. The `output_config` constrains the result — for a `classification`, the full set of allowed values: ```bash curl -X POST https://api.simwood.com/intelligence/v1/operators \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "name": "complaint-severity", "description": "Grades how serious a customer complaint is.", "prompt": "Assess the severity of the customer'\''s complaint in this conversation. Respond with one of: low, medium, high.", "output_type": "classification", "output_config": { "values": ["low", "medium", "high"] }, "model_tier": "standard", "include_reasoning": true }' ``` bash A few rules: * **`name`** is kebab-case, unique within your account, and must not collide with a platform operator's name. * **`model_tier`** is `fast` for high-frequency, latency-sensitive analysis or `standard` for quality over speed. * **`include_reasoning`** asks the model to justify each result; the justification arrives in the `reasoning` field. Note the operator declares _what_ to analyse and _how_ — not _when_. Granularity is set later, on the [operator reference](/services/intelligence/concepts/configurations#granularity) inside a configuration. ## 3. Optional — parameterise it If the same operator should run against different inputs across configurations, declare a [parameter](/services/intelligence/concepts/operators#parameters) and reference it in the prompt as `{{parameters.}}`. A `kb_document` parameter resolves to a knowledge base document whose text is substituted into the prompt at session start: ```json { "name": "protocol-adherence", "description": "Assesses whether the handler followed the protocol.", "prompt": "The protocol for this call is:\n\n{{parameters.protocol-document}}\n\nAssess adherence. Respond with one of: on_track, minor_deviation, major_deviation.", "output_type": "classification", "output_config": { "values": ["on_track", "minor_deviation", "major_deviation"] }, "model_tier": "standard", "parameters": [ { "name": "protocol-document", "type": "kb_document", "description": "The protocol document to assess against." } ] } ``` json The parameter's value — the document id — is supplied where the operator is referenced in a configuration, so one operator serves many protocols. ## 4. Reference it in a configuration A custom operator is referenced like any other — by name, with its granularity and any parameter values: ```json { "name": "complaint-severity", "granularity": { "on": "conversation_end" } } ``` json Add it to a configuration's `operators` list and it runs on every session that configuration drives. From there, the [Analyse a live call](/services/intelligence/guides/analyse-a-live-call) flow is identical. ## Versioning Editing a custom operator creates a **new version** rather than changing the existing one, and configurations stay pinned to the version they reference — so an edit never silently changes running analysis. `GET /operators/{name}/versions` lists the history, and a configuration moves to a newer version only when you [upgrade its references](/services/intelligence/concepts/configurations#operator-references-and-versioning) explicitly. ## Next steps [OperatorsOutput types, confidence and reasoning, model tiers, parameters, and versioning in depth.](/services/intelligence/concepts/operators)[Analyse a live callPut your operator to work in a configuration on a live call.](/services/intelligence/guides/analyse-a-live-call) # Session events A [session](/services/intelligence/concepts/sessions) publishes a realtime stream of events over **Server-Sent Events (SSE)** for the life of the call. You receive operator results as they are produced, plus session lifecycle markers and — when enabled — transcript utterances. ## Connecting The events endpoint is authenticated by a short-lived **stream token** rather than your API key, so it can be opened directly from a browser `EventSource`: 1. `GET /sessions/{session_id}/stream-token` — returns a `token`. 2. Connect to `GET /sessions/{session_id}/events?token={token}`. The stream stays open until the call ends or you disconnect. Each SSE message carries the JSON event in its `data` field and a unique event id in its `id` field. The [Analyse a live call](/services/intelligence/guides/analyse-a-live-call) guide walks through a full connect-and-consume example. ![](phosphor/regular/info) Payloads document a stable set of fields but may also carry additional internal or usage fields that are **not part of the contract**. Read the fields you need and ignore the rest. ## Event types Every event has an `event_type` discriminator. Switch on it to decide how to read the payload. | `event_type` | When it fires | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `operator_result` | An operator produced a result. | | `operator_error` | An operator failed to produce a result. | | `transcript_utterance` | A transcript result was produced — interim or final. Only when the session was created (or patched) with `transcript_events: true`. | | `session_started` | The session established and analysis began. | | `session_ended` | The session ended (normally when the call ends). | ### operator\_result ```json { "event_id": "e2b1…", "event_type": "operator_result", "session_id": "abc-123", "call_id": "call-789", "conversation_id": "conv-456", "configuration_id": "5f1c…", "timestamp_utc": "2026-06-17T10:30:00.000Z", "call_elapsed_seconds": 42.0, "operator": { "name": "fraud-confidence", "version": 3, "granularity": { "on": "turn", "every": 1, "until_match": null } }, "output_type": "score", "result": { "value": 84 }, "confidence": 0.91, "reasoning": "Caller requested an urgent transfer to a new payee…", "speaker": "caller", "trigger_text": "I need to move the money today.", "turn_index": 12 } ``` json * **`result`** — the typed result; its shape depends on `output_type` (see [below](#result-shapes)). * **`confidence`** — the model's self-assessed confidence, `0.0`–`1.0`. * **`reasoning`** — a short natural-language justification, or `null`. * **`operator.granularity`** — the granularity that produced this result (see [Configurations](/services/intelligence/concepts/configurations)). When the same operator runs at more than one granularity, this is how you tell the deployments apart. * **`speaker`**, **`trigger_text`**, **`turn_index`** — the turn that triggered the result; `null` for `conversation_end` results that run over the whole transcript. ### operator\_error Emitted when an operator fails to produce a result. It carries no `result`, `confidence`, or `reasoning`. ```json { "event_id": "9c4d…", "event_type": "operator_error", "session_id": "abc-123", "call_id": "call-789", "conversation_id": "conv-456", "configuration_id": "5f1c…", "timestamp_utc": "2026-06-17T10:30:01.000Z", "operator": { "name": "fraud-confidence", "version": 3, "granularity": { "on": "turn", "every": 1, "until_match": null } }, "turn_index": 12 } ``` json ### transcript\_utterance Emitted only when the session has `transcript_events: true`. The transcriber emits results **incrementally**: you receive interim hypotheses as someone is still speaking (`is_final: false`), each refining the last, then a settled result for the utterance (`is_final: true`). ```json { "event_type": "transcript_utterance", "session_id": "abc-123", "call_id": "call-789", "speaker": "caller", "text": "I need to move the money today.", "start": 41.2, "utterance_index": 12, "is_final": true, "confidence": 0.97, "duration": 1.8 } ``` json * **`is_final`** — `false` for an interim, still-changing hypothesis; `true` for the settled text. If you only want stable transcript, **ignore interim events and keep those with `is_final: true`**. * **`utterance_index`** — a per-speaker counter. It stays the same across the interim updates of one utterance and only advances after that utterance is finalised, so the interims and the final for the same utterance share an index. * **`speaker`** — `caller` or `recipient`. * **`start`** — offset in seconds from the start of the session. * **`duration`** — length of the utterance in seconds. * **`confidence`** — transcription confidence, `0.0`–`1.0`. Lower and less stable on interim results. ### session\_started ```json { "event_type": "session_started", "session_id": "abc-123", "call_id": "call-789", "conversation_id": "conv-456", "started_at": "2026-06-17T10:29:18.000Z" } ``` json ### session\_ended ```json { "event_type": "session_ended", "session_id": "abc-123", "call_id": "call-789", "conversation_id": "conv-456", "configuration_id": "5f1c…", "started_at": "2026-06-17T10:29:18.000Z", "ended_at": "2026-06-17T10:34:02.000Z", "duration_seconds": 284 } ``` json A session normally ends when the call ends; `duration_seconds` is the analysed duration. After it ends, the durable record is the [conversation](/services/intelligence/concepts/conversations). ## Result shapes The `result` field on an `operator_result` is shaped by the operator's `output_type`: | `output_type` | `result` shape | Example | | ---------------- | ---------------------------------------------- | ------------------------------------------- | | `classification` | `{ "value": "" }` | `{ "value": "billing" }` | | `boolean` | `{ "value": }` | `{ "value": true }` | | `score` | `{ "value": }` | `{ "value": 84 }` | | `multi_tag` | `{ "tags": [, …] }` | `{ "tags": ["refund", "vip"] }` | | `extraction` | `{ "value": , "found": }` | `{ "value": "AB12 3CD", "found": true }` | | `text` | `{ "value": "" }` | `{ "value": "Customer wants a callback." }` | See [Operators](/services/intelligence/concepts/operators) for how output types are defined. [SessionsThe session lifecycle, starting a session, and retrieving results.](/services/intelligence/concepts/sessions)[WebhooksPush the same operator results to your endpoint when a condition is met.](/services/intelligence/events/webhooks) # Voices Voices are the speech synthesis identities your agents speak with. The platform maintains a catalogue of voices; you select one by setting `configuration.tts.voice_id` on an [agent](/services/conversational-ai/concepts/agents). ## The voice catalogue List available voices: ```bash curl https://api.simwood.com/convai/v1/voices \ -H "Authorization: Bearer sk_live_..." ``` bash Each voice has: | Property | Type | Description | | ------------- | ------------- | --------------------------------------------------------------------- | | `id` | string (UUID) | The ID to use in `configuration.tts.voice_id` | | `name` | string | Display name | | `category` | string | Voice category (e.g. `premade`, `professional`) | | `description` | string | A short description of the voice's character | | `preview_url` | string | URL to an audio sample | | `labels` | object | Key-value metadata (e.g. `{"accent": "british", "gender": "female"}`) | | `tags` | string\[] | Searchable tags | | `status` | string | `active` or `inactive` | By default only `active` voices are returned. Pass `?include_inactive=true` to see inactive voices — useful when an existing agent references a voice that has been retired. ## Selecting a voice 1. List voices and browse `name`, `description`, and `labels` to narrow down candidates. 2. Use `preview_url` to listen to a sample. 3. Copy the `id` of your chosen voice. 4. Set it as `configuration.tts.voice_id` when creating or updating an agent. ```bash # Retrieve a specific voice curl https://api.simwood.com/convai/v1/voices/ \ -H "Authorization: Bearer sk_live_..." ``` bash ## Voice settings In addition to selecting a voice, you can tune how it sounds with `configuration.tts.settings`: | Setting | Range | Effect | | ------------------ | ------- | ----------------------------------------------------------------------------------------------------- | | `stability` | 0–1 | Lower values produce more expressive, variable speech; higher values are more consistent and measured | | `similarity_boost` | 0–1 | How closely the output resembles the target voice; lower values add more variety | | `speed` | 0.5–2.0 | Relative playback speed; `1.0` is natural pace | ![](phosphor/regular/info) Voice settings interact with each other. A starting point of `stability: 0.5`, `similarity_boost: 0.75`, `speed: 1.0` works well for most use cases — adjust from there based on listening tests. [AgentsHow voice\_id and TTS settings fit into the full agent configuration.](/services/conversational-ai/concepts/agents) # SIP UAC The SIP UAC resource configures an agent's SIP registration. Once set, the agent registers to the specified PBX and behaves as a fully capable SIP user agent for as long as the registration is active. ## What the agent can do once registered * **Receive inbound calls** — the PBX routes calls to the agent's address of record (AOR); the agent answers and begins the conversation * **Initiate outbound calls** — the agent dials a specified number through the PBX * **Transfer calls** — both cold (unattended) transfer, where the call is handed off immediately, and warm (attended) transfer, where the agent introduces the caller before completing the transfer No further API interaction is required once the agent is registered. Calls arrive and are handled automatically. ## Configuration SIP UAC is configured per agent with `PUT /agents/:id/sip-uac`. The operation is an upsert — it creates the registration if one doesn't exist, or replaces it entirely if it does. ```bash curl -X PUT https://api.simwood.com/convai/v1/agents//sip-uac \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "sip_username": "agent-001", "sip_domain": "pbx.example.com", "sip_password": "secret", "sip_registrar": "sip.example.com" }' ``` bash | Field | Required | Description | | --------------- | -------- | ----------------------------------------------------------------- | | `sip_username` | Yes | The SIP username the agent registers with | | `sip_domain` | Yes | The SIP domain | | `sip_password` | No | SIP registration password | | `sip_registrar` | No | Explicit registrar address; defaults to `sip_domain` if omitted | | `aor` | No | Address of record override; defaults to `sip_username@sip_domain` | ## Retrieving the current configuration ```bash curl https://api.simwood.com/convai/v1/agents//sip-uac \ -H "Authorization: Bearer sk_live_..." ``` bash The response includes the `aor` the agent is registered under. The `sip_password` is not returned. ## Deregistering Deleting the SIP UAC configuration deregisters the agent and stops it from receiving or initiating calls: ```bash curl -X DELETE https://api.simwood.com/convai/v1/agents//sip-uac \ -H "Authorization: Bearer sk_live_..." ``` bash ![](phosphor/regular/info) Deleting an agent (`DELETE /agents/:id`) also removes its SIP UAC configuration and deregisters it. [ConversationsWhat is captured during a voice call and how to retrieve it.](/services/conversational-ai/concepts/conversations)[Create your first agentEnd-to-end walkthrough including SIP UAC setup.](/services/conversational-ai/guides/create-your-first-agent) # Operators An **operator** is a single, reusable unit of analysis: a natural-language prompt paired with a typed output. Each operator detects, classifies, scores, extracts, or tags exactly one thing about a conversation. An operator describes _what_ to analyse and _how_ — not _when_, which is decided by the [configuration](/services/intelligence/concepts/configurations) that references it. ## Output types Every operator declares one **output type** from a fixed vocabulary. The type fixes the shape of the `result` object on every event the operator emits, so any consumer can handle a result generically once it knows the type — including results from operators you define yourself. | Output type | Result shape | Use for | | ---------------- | ----------------------------------------------- | ---------------------------------------------------------- | | `classification` | `{ "value": "string" }` | One value from a declared set | | `boolean` | `{ "value": true \| false }` | Detected or not | | `score` | `{ "value": 0–100 }` | A numeric assessment within a range | | `multi_tag` | `{ "tags": ["a", "b"] }` | Zero or more values from a declared set | | `extraction` | `{ "value": "string", "found": true \| false }` | A specific piece of information stated in the conversation | | `text` | `{ "value": "string" }` | Free-form generated text, for human consumption | Each type carries type-specific `output_config` that both constrains the model and tells consumers what to expect before any results arrive: * `classification` — `{ "values": ["a", "b", "c"] }`, the full set of possible values * `score` — `{ "min": 0, "max": 100 }`, the declared range * `multi_tag` — `{ "tags": ["a", "b", "c"] }`, the full tag set * `extraction` — `{ "description": "what to look for" }`, an optional hint * `boolean` and `text` need no configuration ## Confidence and reasoning Every result also carries a `confidence` value between `0.0` and `1.0` — the model's self-assessed confidence. This is the universal field you can gate [actions](/services/intelligence/concepts/actions) on, alongside the typed result. When an operator sets `include_reasoning`, the model is also asked to justify each result, and a short `reasoning` string is populated on the event. The instruction is applied consistently across every operator that enables it, so the justification reads the same way whether the operator is platform-maintained or your own. ## Model tier Each operator runs on a declared `model_tier`: * **`fast`** — for high-frequency, latency-sensitive analysis, such as operators that run on every turn. * **`standard`** — for quality over speed, such as analysis that runs once over the full transcript. The operator declares only the tier; which model backs each tier is resolved by the platform. ## Parameters Parameters let an operator's prompt reference a value supplied where the operator is configured, rather than hard-coding it into the prompt. The same operator can then be deployed against different inputs across different configurations. A parameter is declared on the operator and referenced in the prompt as `{{parameters.}}`. Its value is supplied in the configuration's operator reference and resolved once, at session start, before any analysis runs. The `kb_document` parameter type resolves to a knowledge base document whose extracted text is substituted into the prompt — useful for running one operator, such as a protocol-adherence check, against each account's own protocol document. ## Platform and custom operators **Platform operators** are authored and maintained by Simwood and available to every account read-only; **custom operators** are defined by your account. Both share the same definition structure. A custom operator's name must be kebab-case, unique within your account, and must not collide with a platform operator's name. ## Versioning Operators are versioned. Editing a custom operator creates a new version rather than mutating the existing one, and a configuration's reference stays pinned to the version it was bound to — so a configuration's behaviour is stable across operator edits. Moving a configuration onto a newer operator version is always an explicit step. See [Configurations](/services/intelligence/concepts/configurations#operator-references-and-versioning) for how references pin and upgrade. [API reference — OperatorsList, create, version, and retrieve operators.](/services/intelligence/reference) # Bindings A **binding** enables intelligence on a **point of ingress** — a voice **trunk** or **number**. Give it a [configuration](/services/intelligence/concepts/configurations) and every conversation arriving there is analysed automatically under that configuration, with no per-conversation API call. Leave the configuration off and the calls are simply _tracked_, so you can start a session on demand for any of them. A binding with a configuration is the standing counterpart to a [session](/services/intelligence/concepts/sessions): where a session applies a configuration to one live conversation you start yourself, a binding applies one to everything arriving at a trunk or number, and the platform starts the session for you. A binding resolves to its configuration by the configuration's stable `uuid`, so editing the configuration takes effect everywhere it is bound without touching the binding. ## Tracking vs automatic analysis A call can only be analysed — automatically or on demand — if it is being **tracked**, and a binding is what turns tracking on for a target: * **With a `configuration_id`** — every conversation at the target is tracked _and_ analysed automatically under that configuration, with no per-conversation API call. * **Without a `configuration_id`** — conversations at the target are tracked only. Nothing is analysed until you [start a session](/services/intelligence/concepts/sessions) for a specific call, which you can do on demand for any live call there. This is the lightest-touch option. There is no account-wide switch — tracking is opted into one trunk or number at a time, so a call is never tracked unless you have bound its ingress point. ## Targets A binding's `target` is the ingress point it applies to: | Field | Meaning | | -------- | ----------------------------------------------------------------------------------------- | | `medium` | The ingress plane. `voice` today — every call, whether over PSTN or WhatsApp, is `voice`. | | `type` | The ingress kind: `trunk` (every call over the trunk) or `number` (a single number/DDI). | | `key` | The identifier — the trunk id, or the number in E.164 for a `number` target. | A target can hold only one binding; binding a configuration to a target that is already bound returns **409**. Park or delete the existing binding to rebind the target. ![](phosphor/regular/info) Because a WhatsApp call flows through the same voice platform as a PSTN call, a `voice` binding on a trunk or number covers both automatically — you do not bind them separately. ## Precedence A single call can match more than one binding — a `number` binding for the dialled number and a `trunk` binding for the trunk it arrived on. The **most specific wins**: a `number` binding takes precedence over a `trunk` binding. If you also start a [session](/services/intelligence/concepts/sessions) explicitly for a call, that session is what runs — an explicit session is the per-call override of the standing binding. ## Lifecycle * **`enabled`** — whether the binding is in force. Set it `false` to park a binding without deleting it. * **`target_status`** — `active` while the target trunk/number is present and owned by your account; `missing` if it has been removed on the carrier side. A `missing` binding stops applying, and is removed after a grace period — so bindings do not accumulate against ingress points that no longer exist. * **`sync_state`** — `live` once the running platform reflects the binding, or `pending` until the next sync cycle. A change is durable immediately; it takes effect on new conversations once `sync_state` is `live`. ## Finding what a configuration governs Before you edit or retire a configuration, list bindings filtered by `configuration_id` to see every ingress point it currently governs — the blast radius of the change. Deleting a configuration removes its bindings. Parking one (`is_active: false`) leaves its bindings in place but stops the automatic analysis: those targets stay tracked, so on-demand sessions still work, and automatic analysis resumes when the configuration is active again. [API reference — BindingsCreate, list, get, and delete bindings.](/services/intelligence/reference) # Start from a template A [template](/services/intelligence/concepts/templates) is a platform-maintained, read-only [configuration](/services/intelligence/concepts/configurations) pre-built for a use case. Starting from one means you adopt a working set of operators and actions and complete the gaps, rather than composing a configuration from scratch. This guide fetches a template, completes it, and saves it as your own configuration. The examples use the base path `https://api.simwood.com/intelligence/v1` and a Bearer API key — see [Authentication](/authentication). You'll need `intelligence:read` and `intelligence:write`. ## 1. Browse the templates List the available templates to find one tuned for your use case: ```bash curl https://api.simwood.com/intelligence/v1/configuration-templates \ -H "Authorization: Bearer sk_live_..." ``` bash Each entry has a numeric `id`, a `name` such as `fraud-detection`, and a `description` of what it's tuned for. ## 2. Fetch the template definition Retrieve the full template to read its `definition` — the pre-populated configuration document, with operators, granularities, and actions already in place: ```bash curl https://api.simwood.com/intelligence/v1/configuration-templates/ \ -H "Authorization: Bearer sk_live_..." ``` bash The `definition` is complete except for one deliberate gap: actions are defined **without their delivery destinations**. The granularities reflect a suggested setup for the use case, not a fixed one. ## 3. Complete the definition Working from the `definition`, fill in what the template leaves open and adjust anything you want to change: * **Add each action's `webhook_url`** (and any `webhook_headers`) — these are always left blank. * **Adjust operator selection, granularity, and context** to suit your call patterns. * **Supply any operator [parameters](/services/intelligence/concepts/operators#parameters)** the template's operators declare — for example, a `kb_document` value pointing at your own protocol document. ## 4. Create your configuration Post the completed document to create a configuration you own. Pass `template_id` to record where it came from — this is kept for lineage only and never merges anything from the template back in: ```bash curl -X POST https://api.simwood.com/intelligence/v1/configurations \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "name": "acme-fraud-detection", "template_id": 3, "operators": [ /* completed from the template definition */ ], "actions": [ { "type": "webhook", "webhook_url": "https://example.com/hooks/fraud", "once_per_session": true, "condition": { "operator": "fraud-confidence", "expression": "result.value > 80" } } ] }' ``` bash The response returns your configuration's own `uuid`. Later changes to the template never affect a configuration you've already created. ![](phosphor/regular/info) To check the completed document before saving, post it to `POST /configurations/validate` for a dry run that returns `valid` plus any `errors` without persisting anything. ## Next steps [Analyse a live callAttach your new configuration to a live call and handle the results.](/services/intelligence/guides/analyse-a-live-call)[ConfigurationsHow operators, granularity, and actions compose into a configuration.](/services/intelligence/concepts/configurations) # Webhooks A **webhook** is a delivery [action](/services/intelligence/concepts/actions) defined within a [configuration](/services/intelligence/concepts/configurations). When an operator produces a result that meets the action's condition, the platform POSTs a JSON envelope to the action's `webhook_url`. Unlike the [session event stream](/services/intelligence/events/session-events) — which delivers everything — webhooks deliver only the results your actions select. You define a webhook action with a `webhook_url`, an optional set of `webhook_headers`, and an optional `condition`. See [Actions](/services/intelligence/concepts/actions) for conditions and `once_per_session`. ## The envelope Every delivery has the same shape: ```json { "session_id": "abc-123", "configuration_id": "5f1c…", "customer_metadata": { "your_ref": "xyz" }, "fired_at": "2026-06-17T10:30:00.000Z", "type": "operator_result", "action": { "index": 2, "once_per_session": true }, "data": { }, "evaluated": { "fraud-confidence": { "result": { "value": 84 }, "confidence": 0.91, "call_elapsed_seconds": 42.0 } } } ``` json * **`type`** — the kind of operator event that triggered delivery: `operator_result` or `operator_error`. * **`data`** — the full operator event that triggered the action. This is the same payload the session stream carries: see the [`operator_result`](/services/intelligence/events/session-events#operator_result) and [`operator_error`](/services/intelligence/events/session-events#operator_error) shapes. * **`evaluated`** — a per-operator summary (`result`, `confidence`, `call_elapsed_seconds`) of every operator the condition tested, so you can see exactly what satisfied it. `null` for an unconditional action. * **`customer_metadata`** — echoed verbatim from the value you supplied when the session was created; `null` if none was set. * **`action.index`** — the action's position in the configuration. When several actions post to the same URL, use it together with `session_id` to tell deliveries apart. * **`fired_at`** — the `timestamp_utc` of the triggering operator event. ![](phosphor/regular/info) Unconditional actions also fire on `operator_error`, where `data` has no `result`. Conditions that test `result` never match an error, so a conditional action only delivers `operator_result`. Read `type` before reading `data`. ## Delivery Each delivery is a single HTTP `POST` with a JSON body (`Content-Type: application/json`) and any `webhook_headers` you configured on the action. Your endpoint should return a **2xx** status promptly — do slow work asynchronously after acknowledging. The platform waits up to **10 seconds** for a response. ### Retries | Outcome | Behaviour | | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | **2xx / 3xx** | Treated as delivered. | | **4xx** | Permanent failure — **not retried** (a client error won't be fixed by retrying). Check your endpoint and headers. | | **5xx, timeout, or network error** | Retried with backoff. | Retried deliveries are attempted up to **3 times**: the initial attempt, then again after roughly **5 seconds**, then after roughly **30 seconds**. If all three fail, the delivery is abandoned and logged. ![](phosphor/regular/warning) Delivery is **at-least-once**. A retry can deliver the same event more than once, and an endpoint that is slow to acknowledge (but eventually succeeds) may still be retried. Make your handler **idempotent** — deduplicate on the triggering event id (`data.event_id`) together with `action.index`. For at-most-once-per-call business logic, set [`once_per_session`](/services/intelligence/concepts/actions) on the action. ## Securing your endpoint Deliveries are not signed. To verify a request genuinely came from the platform: * Set a secret on the action's **`webhook_headers`** — for example `{ "Authorization": "Bearer " }` — and check it on every request. * Serve your endpoint over **HTTPS**. * Don't trust the payload's identifiers for authorisation on their own; gate on the shared secret first. [ActionsDefine webhook actions, conditions, and once\_per\_session within a configuration.](/services/intelligence/concepts/actions)[Session eventsThe realtime stream that carries the same operator results, unconditionally.](/services/intelligence/events/session-events) # Concepts The Conversational AI API is built from a small set of primitives. Each concept page explains one of them — what it is, what properties it has, and how it relates to the others. The primitives fall into three groups: what you define to create an agent, how you connect it to your infrastructure, and what you get back from every call. ## Build [AgentsThe core entity: a voice, a prompt, and configuration that define how the agent behaves during a call.](/services/conversational-ai/concepts/agents)[VoicesThe catalogue of speech synthesis voices an agent can speak with.](/services/conversational-ai/concepts/voices)[Knowledge baseDocuments the agent retrieves from during a conversation to answer questions accurately.](/services/conversational-ai/concepts/knowledge-base) ## Integrate [WebhooksHTTP callbacks that deliver conversation events to your infrastructure — when a call starts and when it ends.](/services/conversational-ai/concepts/webhooks)[SIP UACThe SIP registration that connects an agent to your telephony platform so it can receive and make calls.](/services/conversational-ai/concepts/sip-uac) ## Analyse [ConversationsThe record of every call: transcript, analysis, and metadata, available as soon as the call ends.](/services/conversational-ai/concepts/conversations) # Manage a knowledge base This guide walks through adding documents to your knowledge base, referencing them from an agent, and removing them when they are no longer needed. ![](phosphor/regular/info) You'll need an API key with `convai:read` and `convai:write`. ## 1. List existing documents Before adding a document, check what is already there: ```bash curl https://api.simwood.com/convai/v1/knowledge-base \ -H "Authorization: Bearer sk_live_..." ``` bash The response is an array of document records, each with an `id`, `name`, `type`, and `size_bytes`. ## 2. Add a document Choose the ingestion method that matches your content. ### From a URL The platform fetches and indexes the page at the URL. Pass a `name` to identify it in the list. ```bash curl -X POST https://api.simwood.com/convai/v1/knowledge-base/url \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "name": "Support FAQ", "url": "https://example.com/support/faq" }' ``` bash ### From text Pass the content directly as a string. Suitable for policies, scripts, or any content you maintain in-house. ```bash curl -X POST https://api.simwood.com/convai/v1/knowledge-base/text \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "name": "Cancellation policy", "text": "Customers may cancel any subscription within 30 days of purchase for a full refund..." }' ``` bash ### From a file Upload a file. Supported formats: `.txt`, `.pdf`, `.docx`, `.epub`, `.html`. ```bash curl -X POST https://api.simwood.com/convai/v1/knowledge-base/file \ -H "Authorization: Bearer sk_live_..." \ -F "name=Product catalogue" \ -F "file=@catalogue.pdf" ``` bash All three endpoints return the same document schema. Note the `id` from the response — you need it to reference the document from an agent. ## 3. Reference the document from an agent Add the document to the agent's `configuration.agent.knowledge_base` array. If the agent already exists, use `PUT /agents/:id` with the full updated configuration: ```bash curl -X PUT https://api.simwood.com/convai/v1/agents/ \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "name": "My agent", "configuration": { ... "agent": { ... "knowledge_base": [ { "type": "url", "id": "", "source": "https://example.com/support/faq" }, { "type": "text", "id": "", "source": "Cancellation policy" } ] } } }' ``` bash The agent begins drawing on the new documents immediately for new conversations. ![](phosphor/regular/info) Calls already in progress are not affected by agent configuration changes — only new conversations pick up the updated knowledge base. ## 4. Remove a document To stop using a document, first remove its entry from `configuration.agent.knowledge_base` on any agent that references it (using `PUT /agents/:id`), then delete the document itself: ```bash curl -X DELETE https://api.simwood.com/convai/v1/knowledge-base/ \ -H "Authorization: Bearer sk_live_..." ``` bash ![](phosphor/regular/info) Deleting a document does not automatically update agents. If you delete without removing the reference first, the agent will have a stale reference — update the agent configuration to remove it after deletion. [Knowledge baseDocument types, formats, and how the agent uses retrieved content.](/services/conversational-ai/concepts/knowledge-base)[AgentsThe full agent configuration reference, including the knowledge\_base field.](/services/conversational-ai/concepts/agents) # Actions **Actions** are delivery instructions defined within a [configuration](/services/intelligence/concepts/configurations). Each action specifies what to do when an [operator](/services/intelligence/concepts/operators) produces a result, and fires independently — a configuration can hold any number of actions, each with its own target and optional condition. ## Action types * **`webhook`** — deliver an HTTP POST to one of your endpoints. * **`whisper`** — synthesise the output of a `text` operator and inject it into the live call audio. ## Conditions By default an action fires on every operator result. A **condition** narrows it. The single-clause form names one operator and evaluates an expression against the result it produced: ```json { "operator": "fraud-confidence", "expression": "result.value > 80 AND confidence > 0.75" } ``` json Expressions read the typed `result` fields (`result.value`, `result.tags`, `result.found`) and the universal `confidence` field. Compound conditions combine clauses with `all` (every clause must be met) or `any` (any clause): ```json { "all": [ { "operator": "fraud-confidence", "expression": "result.value > 80" }, { "operator": "fraud-indicators", "expression": "'safe_account_request' IN result.tags" } ] } ``` json When the same operator is deployed at more than one granularity, an action fires for results from either deployment; each result carries its granularity so you can tell them apart. ## Suppressing repeats By default an action fires every time its condition is met. Set `once_per_session` to fire at most once per [session](/services/intelligence/concepts/sessions), suppressing later matches — useful for an alert you only want raised once per call. ## Webhook delivery A `webhook` action posts a consistent envelope to its `webhook_url`, with optional `webhook_headers` (for example, an authorization token). The envelope is the same shape for every delivery: ```json { "session_id": "abc-123", "configuration_id": "5f1c…", "customer_metadata": { "your_ref": "xyz" }, "fired_at": "2026-06-17T10:30:00.000Z", "type": "operator_result", "action": { "index": 2, "once_per_session": true }, "data": { }, "evaluated": { "fraud-confidence": { "result": { "value": 84 }, "confidence": 0.91, "call_elapsed_seconds": 42 } } } ``` json * **`data`** — the full operator result that triggered the action. * **`evaluated`** — a per-operator summary of every operator the condition tested; `null` for an unconditional action. * **`customer_metadata`** — echoed verbatim from the value you supplied when the session was created. * **`action.index`** — the action's position in the configuration, so you can correlate deliveries when several actions share one URL. For the full envelope, delivery and retry behaviour, and how to secure your endpoint, see [Webhooks](/services/intelligence/events/webhooks). ## Whisper delivery A `whisper` action references a `text` operator already in the configuration via `content` — `{ "operator": "" }` — and a `whisper_channel` of `caller` or `recipient`. That operator's prompt and granularity determine what is generated; the action controls when it is voiced and into which side of the call. The same `text` operator result can be delivered as both a whisper and a webhook by defining both actions. [ConfigurationsWhere actions are defined, alongside the operators they fire on.](/services/intelligence/concepts/configurations) # Events While a [session](/services/intelligence/concepts/sessions) runs, its results reach your systems two ways. Both carry the same operator results — pick whichever fits how you want to consume them, or use both. * **[Session events](/services/intelligence/events/session-events)** — a realtime **stream** you hold open for the life of the call. You receive every operator result (and, optionally, transcript turns) as it is produced. Best for live dashboards and in-call UX. * **[Webhooks](/services/intelligence/events/webhooks)** — HTTP callbacks the platform **pushes** to your endpoint when a result meets a condition you defined in the configuration. Best for triggering downstream systems without holding a connection open. The stream delivers everything; webhooks deliver only what your [actions](/services/intelligence/concepts/actions) select. ![](phosphor/regular/info) **Ignore fields you don't recognise.** Event and webhook payloads document a stable set of fields, but a payload may also carry additional internal or usage fields that are **not part of the contract** and may change or disappear without notice. Read the fields you need by name and ignore the rest. [Session eventsThe realtime SSE stream: event types, payload shapes, and how to connect.](/services/intelligence/events/session-events)[WebhooksThe webhook envelope, delivery semantics, retries, and securing your endpoint.](/services/intelligence/events/webhooks) # Conversations A conversation is the record of everything that happened during an agent call: who said what, when, the outcome of any data collection the agent performed, and metadata about the call itself. Every completed call produces a conversation record that can be retrieved and queried afterwards. ## Two ways to have a conversation Agents support two conversation modes: voice and text-only. Both produce the same conversation record on completion. ### Voice Voice is the primary mode. An agent with a [SIP UAC](/services/conversational-ai/concepts/sip-uac) configured registers to your telephony platform as a SIP user agent. Calls arrive over SIP — no API call is needed to start them. The agent answers, speaks with the caller, and ends the call when the conversation concludes. From the caller's perspective it is a normal phone call. The agent can: * Receive inbound calls routed to it by the PBX * Initiate outbound calls to specified numbers * Transfer calls to another extension or number — both cold (unattended) and warm (attended) transfers ### Text-only Text-only sessions are created via the API and are suited for chat-style integrations — a web widget, a mobile app, or a testing environment. Create a session to receive a token and connection URL for the real-time SDK: ```bash curl -X POST https://api.simwood.com/convai/v1/agents//conversations \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "config": { "dynamic_variables": { "customer_name": "Jane Smith" } } }' ``` bash The response includes: ```json { "id": "conv-uuid-...", "agent_id": "agent-uuid-...", "room_name": "room-...", "ws": { "url": "wss://...", "token": "eyJ...", "text_only": true } } ``` json Connect with the real-time SDK using `ws.url` and `ws.token`. The session ends when the SDK disconnects. ## Conversation record Once a conversation ends — whether voice or text-only — the record is available via the conversations API: ```bash # List conversations for an agent curl https://api.simwood.com/convai/v1/agents//conversations \ -H "Authorization: Bearer sk_live_..." # Retrieve a specific conversation curl https://api.simwood.com/convai/v1/agents//conversations/ \ -H "Authorization: Bearer sk_live_..." ``` bash ### Transcript The `transcript` array is the turn-by-turn record of the conversation: ```json "transcript": [ { "role": "agent", "message": "Hello, thanks for calling. How can I help you today?", "time_in_call_secs": 0.0 }, { "role": "user", "message": "I'd like to cancel my subscription.", "time_in_call_secs": 3.4 } ] ``` json Each turn has `role` (`agent` or `user`), `message`, `time_in_call_secs`, and — when applicable — `tool_calls` and `tool_results` from any tools the agent invoked. ### Analysis If the agent was configured with `analysis.data_collection`, the `analysis` field on the conversation record contains the extracted values: ```json "analysis": { "data_collection_results": { "customer_intent": "cancel subscription", "issue_resolved": true }, "call_successful": "success", "transcript_summary": "Caller wanted to cancel their subscription. Agent confirmed cancellation." } ``` json ### Metadata ```json "metadata": { "start_time_unix_secs": 1719820800, "accepted_time_unix_secs": 1719820802, "call_duration_secs": 143 } ``` json `status` reflects the outcome: `done`, `failed`, or `in-progress` for a call that is still active. ## Deleting a conversation ```bash curl -X DELETE https://api.simwood.com/convai/v1/agents//conversations/ \ -H "Authorization: Bearer sk_live_..." ``` bash [SIP UACConfiguring the SIP registration that enables voice conversations.](/services/conversational-ai/concepts/sip-uac)[WebhooksReceiving conversation events via HTTP callback.](/services/conversational-ai/concepts/webhooks)[Receive conversation eventsConfigure a webhook and handle the event payloads.](/services/conversational-ai/guides/receive-conversation-events) # Conversations A **conversation** is the durable record of everything that happened across one or more [sessions](/services/intelligence/concepts/sessions) on the same call: the transcript turns, the [operator](/services/intelligence/concepts/operators) results, and rolled-up usage, all threaded by a single `conversation_id`. Where a session is live and ephemeral, the conversation is what persists — the unit you retrieve, query, and report on after the fact. ## What a conversation holds * **`conversation_id`** — the stable identifier for the conversation. * **`medium`** — the communication medium, such as `voice`. * **`status`** — `open` while a session is live, `closed` once the last session ends. * **`transport_key`** — the medium-native key; the telephony `call_id` for voice. * **`source_sessions`** and **`configuration_ids`** — the sessions that contributed turns, and the distinct configurations active over the conversation. * **`turn_count`** and **`usage`** — the number of transcript turns, and rolled-up `audio_seconds` and `operator_results_emitted`. * **`started_at`** / **`ended_at`** — when the earliest session started and the last session ended; `ended_at` is absent while the conversation is open. A single conversation can span more than one session — for example a call analysed under one configuration, then another after a mid-call swap — and its `configuration_ids` records each one. ## Retrieving and querying * **`GET /conversations`** lists your conversations; **`GET /conversations/{conversation_id}`** retrieves one. * **`GET /conversations/{conversation_id}/operator-results`** returns the operator results, optionally split out `per_session`. * **`GET /conversations/{conversation_id}/transcript`** returns the transcript, paged: each turn carries a `seq`, the diarised `speaker`, the `text`, and when it `occurred_at`, and you page forward with `after_seq`. ## Today and tomorrow Today, conversations are **synchronous**: they come from live calls, each backed by one or more sessions. The model is medium-agnostic by design — support for **asynchronous** conversations such as messaging and email, which have no live session and are analysed as each message arrives, is planned. A conversation's `medium` distinguishes the two. [API reference — ConversationsList, retrieve, and query conversations and their transcripts.](/services/intelligence/reference) # Overview Conversation Intelligence turns conversations into structured, actionable data. It analyses calls as they happen — so you can act while a call is still live — and records every result, so you can query what happened across all of your calls rather than a sample. This reference covers the Conversation Intelligence API — the HTTP interface you use to define analysis, run it on live calls, and retrieve the results. ![](phosphor/regular/info) Base path: `https://api.simwood.com/intelligence/v1`. Authentication, versioning, and error shapes follow the shared [platform conventions](/conventions). ## When to use it Reach for Conversation Intelligence when you want to extract insight from conversations — intent, sentiment, fraud signals, summaries, and your own custom analysis — and act on it automatically: fire a webhook the moment a fraud score crosses a threshold, surface guidance to a handler mid-call, or report across every call afterwards instead of auditing a fraction. ## Core concepts The service is built from a small set of primitives. The [Concepts](/services/intelligence/concepts) page explains each one on its own terms; the workflow guides and the API reference link back here rather than re-explaining them. * **Operators** — a single, reusable unit of analysis: a prompt paired with a typed output. * **Configurations** — the policy a conversation is analysed under: which operators run, and the actions that fire on their results. * **Actions** — delivery instructions in a configuration: webhooks and whispers that fire when results meet a condition. * **Templates** — platform-maintained, read-only starting points for a configuration. * **Bindings** — make a configuration the default for a point of ingress (a trunk or number) so every call there is analysed automatically. * **Sessions** — attach a configuration to a single live call and run it in realtime. * **Conversations** — the durable record of everything that happened across one or more sessions on the same call. [ConceptsThe building blocks of the Intelligence API.](/services/intelligence/concepts)[Analyse a live callA step-by-step walkthrough: configuration → session → results.](/services/intelligence/guides/analyse-a-live-call)[API referenceEndpoints, parameters, and schemas.](/services/intelligence/reference)