> For the complete documentation index, see [llms.txt](https://docs.concurrence.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.concurrence.com/developer-guide/platform-api/workspaces/services.md).

# Services

Bind agents and context graphs into deployable services, manage version sets, and configure voice behavior for conversations across supported channels.

A service binds an **agent**, a **context graph**, and **version sets** into a deployable unit. Services are the entry point for conversations: when a user calls a phone number or starts a chat, the platform resolves the service to determine which agent persona, conversation flow, and model configuration to use.

{% hint style="info" %}
**Classic API vs Platform API**: These APIs expose separate service resources. Platform services are workspace-scoped and their IDs are used with Platform conversation and call routes. Classic service IDs are organization-scoped and used with Classic conversation routes.
{% endhint %}

## Key Concepts

* **Agent and context graph binding**: every service links to one agent and one context graph.
* **Version sets**: named configurations that pin specific agent and context graph versions and LLM model preferences.
* **Tags**: key-value metadata stored with the service.
* **Channel type**: the separate `channel_type` field declares whether the service is for `voice` or `text` conversations.
* **Tool capacity**: maximum concurrent tool executions per conversation (1-10, default 3).
* **Accepted conversation context**: the `accepted_context` schema declares which string and number values callers may supply when starting a conversation.
* **Platform caller verification**: `platform_caller_verification` (default `true`) decides whether the platform verifies a caller who may match a patient record. With `false`, the platform never asks the service's callers to verify, and the built-in tools that read a patient record refuse; see [Caller Verification](/developer-guide/platform-api/conversations/voice-agent.md#caller-verification).
* **Medical emergency ending**: `medical_emergency_ending` (default `stay_on_line`) decides whether a `gpt_realtime` call ends after its emergency script for a medical emergency described in English. `end_after_script` plays a closing line after the script and ends the call; self-harm keeps the call open in every setting, and the setting doesn't change platform-managed calls. Choose it with the service's clinical owner; see [Realtime Emergency Script](/developer-guide/platform-api/conversations/voice-agent.md#realtime-emergency-script).

## Endpoints

### Create a Service

{% openapi src="<https://platform.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/services" method="post" %}
<https://platform.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

### List Services

Filter by `agent_id` or `context_graph_id` to find the services that run a given agent or Context Graph. Filters apply before pagination.

{% openapi src="<https://platform.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/services" method="get" %}
<https://platform.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

### Get a Service

{% openapi src="<https://platform.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/services/{service\_id}" method="get" %}
<https://platform.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

### Update a Service

Omitted top-level fields and fields sent as `null` leave their stored values unchanged. Explicit non-null values, including an empty description, `is_active: false`, or empty `keyterms` and `tags` lists, are applied when valid for the field.

A supplied `voice_config` object replaces the stored voice configuration. Nested nulls accepted by its schema are preserved, including `forwarding.forward_to: null` for dynamic forwarding. Sending `voice_config: null` leaves the existing configuration unchanged. Read the current configuration first and resend the complete object when changing it.

{% openapi src="<https://platform.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/services/{service\_id}" method="put" %}
<https://platform.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

### Delete a Service

{% openapi src="<https://platform.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/services/{service\_id}" method="delete" %}
<https://platform.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

### Upsert a Version Set

{% openapi src="<https://platform.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/services/{service\_id}/version-sets/{name}" method="put" %}
<https://platform.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

### Process a Voice-Note Turn

Send one recorded voice note with the caller's E.164 `phone_number`, and the operation returns the agent's spoken reply. The note can be OGG Opus, WebM, WAV, MP4, or MP3 audio, up to 20 MB, sent with a matching audio content type. Turns are keyed on the workspace, service, and phone number, so a second note from the same number while one is still running returns `409`. See [WhatsApp Voice Notes](/developer-guide/platform-api/conversations/voice-agent.md#whatsapp-voice-notes) for the reply formats and session behavior.

{% openapi src="<https://platform.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/services/{service\_id}/voice-turn" method="post" %}
<https://platform.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

The note is transcribed in the caller's language when the matched caller record has one, or in the workspace voice language when that is neither English nor `multilingual`. Otherwise, including for a caller or workspace set to `multilingual`, the language the note was spoken in is detected, so a Spanish note on an English workspace is transcribed in Spanish. A `language_providers` entry for that language can pick the reply voice; see [Default TTS Model](/developer-guide/platform-api/workspaces/voice-configuration.md#default-tts-model) for how voice notes choose a voice.

| Status | Detail                                       | What it means                                                                                           |
| ------ | -------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `413`  | Starts with `Audio too large`                | The file is over 20 MB. Send a smaller file.                                                            |
| `415`  | Starts with `Unsupported audio content type` | Send OGG Opus, WebM, WAV, MP4, or MP3 audio with its audio content type.                                |
| `422`  | `Empty audio file`                           | The upload had no audio. Fix the upload rather than resending it.                                       |
| `422`  | `Audio could not be decoded`                 | The recognizer could not read the audio. Fix the recording or its encoding; don't resend the same file. |
| `422`  | `No speech detected in audio`                | The audio has no speech to answer. Don't resend the same file.                                          |
| `502`  | `Speech-to-text failed`                      | Recognition failed and the turn did not run, so resending the note is safe.                             |
| `502`  | `Text-to-speech failed`                      | The turn ran, but its reply could not be spoken. Resending the note runs the turn again.                |
| `504`  | A timeout                                    | The turn may have run. Resending the note can run it again.                                             |

### Text Conversations

Use [Conversations](/developer-guide/platform-api/conversations.md) for application-hosted text and the managed channel integrations for SMS and WhatsApp.

## Accepted Conversation Context

Declare `accepted_context` on the service's create or update request. Each entry specifies a unique `key`, a `type` of `string` or `number`, a `description`, and a `required` flag. Conversation creation validates its parsed `context` map against this schema; unknown keys, missing required keys, and values that fail this validation return `422`. The guide below also covers a boolean-coercion edge case in request parsing.

Update the service with `PUT /v1/{workspace_id}/services/{service_id}`. A supplied `accepted_context` list replaces the entire previous list. Omission or `null` preserves it; `[]` accepts no caller-supplied context keys. Coordinate newly required keys with callers before changing a shared service.

The service declares the schema; the active Context Graph's state-to-tool specification declares `context_bindings`. Follow [Pass Context to Tool Parameters](/developer-guide/guides/conversation-context.md) for a complete setup, supported tool paths, and mapped parameter names and automatic `channel-id` prerequisites.

The same declaration governs values a trusted backend sets on a live voice call. Declare such a key with `required: false`; on voice, a tool bound to it refuses to run until it is set. See [Set Context on a Live Voice Call](/developer-guide/guides/live-call-context.md).

## Version Sets

Creating a service, updating it, and upserting a version set all check the version sets they write. A version set name must match `[A-Za-z0-9_-]{1,40}`, or the request returns `422`. Each pinned agent and Context Graph version must exist for the service's agent and graph, or the request returns `404`. Changing a service's `agent_id` or `context_graph_id` checks its stored pins against the new target.

{% hint style="warning" %}
**Model ID validation.** Creating or updating a service and upserting a version set check every `llm_name` in `version_sets.llm_model_preferences`. A model id the platform cannot route returns `422` naming the version set, role, and id. A retired Claude id returns `422` naming the version set, role, and the current model of the same tier to use instead, such as `claude-sonnet-5-5` for `claude-sonnet-4-6`; [Skills](/developer-guide/platform-api/workspaces/skills.md#execution-configuration) lists each replacement. The check runs only on writes; reading a stored configuration does not re-validate it.
{% endhint %}

Version sets support an optional `llm_model_preferences` map that overrides the default models used for context graph navigation and conversational engagement. Each key identifies a model role: use `nav` for navigation and `engage` for response generation. Each value specifies the preferred model and optional parameters for that role. When omitted, sessions use the platform defaults.

This lets you pin specific agent versions to specific models for controlled rollouts, A/B testing, or compliance requirements that mandate a fixed model version.

### Model preference resolution order

1. **Version set override** - If the active version set has an `llm_model_preferences` entry for the relevant role, that model is used.
2. **Platform default** - If no override is configured, the platform default model is used.

The resolved model is applied consistently across voice sessions, text sessions, and simulation runs.

### Tool Turns and Reasoning Effort

On `gpt-5.4`, `gpt-5.5`, `gpt-5.6`, and `gpt-6` engage models, a turn that offers the model tools is sent at `reasoning_effort` `none`, the only effort those models accept with tools. A turn without tools uses the configured effort.

Because a higher effort would not be honored on tool turns, creating or updating a service, or upserting a version set, returns `422` when an `engage` preference for one of these models sets `reasoning_effort` above `none` in `params` or `experience_controls`. The message names the version set, the preference, and the model. Leave the effort unset or set it to `none`. A stored version set with a higher effort still loads, but its next save fails until the effort is fixed.

`gpt-5.5`, `gpt-5.6`, and `gpt-6` reason above `none` by default. A request to one of them that runs above `none`, such as a turn without tools that leaves the effort unset, does not send the preference's `temperature`, `top_p`, `logprobs`, or `top_logprobs`. Set `reasoning_effort` to `none` when those parameters must apply.

Version sets enable safe iteration, like Git branches for your service configuration:

* **`release`**: the required default version set used when clients do not specify one. Service creation adds an unpinned `release` set when `version_sets` is omitted or empty.
* **Custom sets**: create named sets (for example, `personal-dev`, `test`, or `preview`) for your promotion workflow. Leaving a version number unset resolves the latest available version at runtime.

```mermaid
%%{init: {"theme":"base","themeVariables":{"fontFamily":"Inter, sans-serif","fontSize":"16px","background":"#FAF8F6","primaryColor":"#F1EAE7","primaryTextColor":"#1A1817","primaryBorderColor":"#D7D2D0","secondaryColor":"#FDFCFB","tertiaryColor":"#F6F2F0","lineColor":"#575452","textColor":"#3D3937","clusterBkg":"#FDFCFB","clusterBorder":"#E8E2DF","edgeLabelBackground":"#FAF8F6","actorBkg":"#F1EAE7","actorTextColor":"#1A1817","actorBorder":"#D7D2D0","actorLineColor":"#575452","signalColor":"#575452","signalTextColor":"#3D3937","labelBoxBkgColor":"#F1EAE7","labelBoxBorderColor":"#D7D2D0","labelTextColor":"#3D3937","loopTextColor":"#3D3937","noteBkgColor":"#F6F2F0","noteBorderColor":"#D7D2D0","noteTextColor":"#3D3937","activationBkgColor":"#F1EAE7","activationBorderColor":"#D7D2D0","altSectionBkgColor":"#FAF8F6","altSectionColor":"#3D3937"},"fontFamily":"Inter, sans-serif"}}%%
flowchart TB
    A[personal-dev] -->|validate| B[test]
    B -->|run simulations| C[preview]
    C -->|UAT + promote| D[release]
```

The recommended promotion path: `personal-dev → test → preview → release`

Manage Platform version sets through service updates or the single-version upsert endpoint. Agent Forge also provides `forge platform version-set` commands for listing, inspecting, upserting, comparing, promoting, and rolling back sets. Mutating commands are dry-run by default and require `--apply` to execute.

{% hint style="warning" %}
When creating a service with a non-empty `version_sets` map, or replacing that map during an update, include `release`. The single-version upsert can create or replace `release` or a custom set. It validates pinned agent and context graph versions. Names must match `^[A-Za-z0-9_-]+$` (max 40 characters).
{% endhint %}

## Structured Actions

Context Graph action states use a **structured Action model**. Each action and exit condition can carry a `filler_hint`, a semantic direction that guides the voice pipeline's filler generation alongside emotional context and latency state.

```json
{
  "actions": [
    { "description": "Look up available appointment slots", "filler_hint": "Let me check what's available..." },
    { "description": "Confirm the appointment with the user", "filler_hint": null }
  ],
  "exit_conditions": [
    { "description": "Appointment confirmed", "next_state": "wrap_up", "filler_hint": "Great, I've got that booked." }
  ]
}
```

{% hint style="info" %}
**Filler hints** are not literal audio fillers. They are semantic directions; the voice pipeline uses them alongside real-time emotional and latency context to produce natural-sounding filler speech.
{% endhint %}

## Voice Configuration

### Session Provider

The `voice_config.session_provider` field controls which voice session runtime drives calls for a service. Supported values:

| Value          | Description                                                          |
| -------------- | -------------------------------------------------------------------- |
| `amigo`        | Concurrence's own voice pipeline (STT, reasoning, TTS). The default. |
| `gpt_realtime` | GPT realtime speech-to-speech model family                           |

When the service field is unset, it inherits the agent-level provider, and with neither set the call runs on `amigo`. The session provider can also be set on the service's page (**Agents** in the Console).

The platform resolves the session provider with the following precedence: service > agent > `amigo`.

`gpt_realtime` runs the whole call from one action state, so it needs a Context Graph version that is exactly one action state. A write that would run a version set on `gpt_realtime` with any other graph returns `422` naming the version set and the graph. That covers creating or updating a service, upserting a version set, creating a Context Graph version that a `gpt_realtime` version set follows at its latest version, and creating an agent version that selects `gpt_realtime` for a service that leaves the runtime to its agent and runs the agent's latest version. Each version set whose runtime or graph a write changes is checked. Bind a graph version that is exactly one action state, or set `voice_config.session_provider` to `amigo`.

`voice_config` is an optional field on the service model that controls how the voice pipeline behaves for that service. It covers latency tuning, filler behavior, response length, barge-in sensitivity, and tool access. If omitted, the service has no service-level overrides. See [Voice Configuration](/developer-guide/platform-api/workspaces/voice-configuration.md) for field-level inheritance and defaults.

### Fields

**Latency**

| Field                 | Type                 | Default | Description                                                                                                                                                                                                                                                                                |
| --------------------- | -------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `max_buffer_delay_ms` | `int` (200 to 1000)  | `500`   | How long the pipeline buffers text before sending to TTS. Lower values feel snappier but may produce choppier speech.                                                                                                                                                                      |
| `eager_eot_threshold` | `float` (0.0 to 1.0) | -       | Confidence at which the recognizer reports an early end of turn. The agent replies at the final end of turn, so this value does not change when it responds. Applies on the platform-managed runtime, on recognizers that accept it; an English call on the default recognizer ignores it. |
| `eot_timeout_ms`      | `int`                | -       | Hard timeout for end-of-turn detection in milliseconds. Applies on the platform-managed runtime, on recognizers that accept it; an English call on the default recognizer ignores it.                                                                                                      |

**Fillers**

| Field                  | Type                                       | Default         | Description                                                                                                                                                                                                                                                                                      |
| ---------------------- | ------------------------------------------ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `filler_style`         | `"backchannel"`, `"phrase"`, or `"silent"` | `"backchannel"` | Only `silent` changes runtime behavior: it mutes the agent's autonomous backchannels. `backchannel` and `phrase` are advisory, and Context Graph progress phrases still play under `silent`. See [Voice Configuration](/developer-guide/platform-api/workspaces/voice-configuration.md#fillers). |
| `filler_vocabulary`    | `list[str]`                                | -               | Custom words for the short acknowledgment played when navigation is skipped. Tool-wait phrases come from `progress_vocabulary` or Context Graph progress phrases.                                                                                                                                |
| `backchannel_delay_ms` | `int`                                      | `400`           | Delay before emitting a backchannel filler when navigation is skipped. Controls how quickly the agent acknowledges the caller.                                                                                                                                                                   |

**Response**

| Field                    | Type  | Default | Description                                                                        |
| ------------------------ | ----- | ------- | ---------------------------------------------------------------------------------- |
| `max_response_sentences` | `int` | -       | Streamed-response sentence cap. Counting is approximate across streamed fragments. |
| `max_response_words`     | `int` | -       | Streamed-response word cap, enforced while consuming generated text.               |

**Barge-in**

| Field                   | Type    | Default | Description                                                                                                                                                                                                                                                                                                                                                                               |
| ----------------------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `barge_in_min_speech_s` | `float` | `0.5`   | Minimum duration of speech still in progress before it can cut the agent off. On the platform-managed runtime the speech also needs recognized words, and a finished utterance with recognized words needs no minimum; on `gpt_realtime` it applies to all speech the realtime session detects. See [Barge-in](/developer-guide/platform-api/workspaces/voice-configuration.md#barge-in). |
| `barge_in_cooldown_s`   | `float` | `1.5`   | Time after the caller last cut the agent off before speech still in progress can do so again. Prevents rapid back-and-forth interruptions. It applies only on the platform-managed runtime.                                                                                                                                                                                               |

**Speed**

| Field           | Type                 | Default | Description                                                                                                                                                                                                                                                                                                                                  |
| --------------- | -------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `min_tts_speed` | `float` (0.5 to 1.0) | -       | Minimum TTS speed multiplier. When the emotion engine reduces speech rate in response to caller distress, it cannot go below this value. The empathy formula produces speeds in the 0.85-0.97 range; set this somewhere in that band if the agent sounds unnaturally slow after emotional turns. Omit to use the default unclamped behavior. |

### Call Forwarding

Configure forwarding in `voice_config.forwarding`. Omitting that block or setting it to null leaves the forwarding tool unavailable. When present, choose warm or cold transfer explicitly and configure either a fixed destination or dynamic destination mode. There is no workspace-number fallback.

Dynamic mode (`forward_to: null`) dials only the numbers listed in `approved_destinations`, a map of E.164 number to label with up to 50 entries. A dynamic block with no approved destinations offers no `forward_call` tool, and `approved_destinations` with a fixed `forward_to` returns `422`. A transfer that can't connect returns the caller to the agent. See [Call Forwarding](/developer-guide/platform-api/workspaces/voice-configuration.md#call-forwarding) for an example and the full flow.

The `forwarding` block is the enablement control; there is no separate `forward_call_enabled` flag. The embedded service schema is authoritative for the forwarding object. On the platform-managed runtime, active turn policy can further restrict forwarding. See [Call Forwarding](/developer-guide/platform-api/conversations/voice-agent.md#call-forwarding) for the hold and answer-handling sequence.

### Presets

Agent Forge provides four named presets:

| Preset              | Buffer | Filler Style  | Backchannel Delay | Response Cap |
| ------------------- | ------ | ------------- | ----------------- | ------------ |
| `ultra_low_latency` | 200ms  | `backchannel` | 400ms             | 1 sentence   |
| `balanced`          | 500ms  | defaults      | defaults          | -            |
| `quality`           | 500ms  | `phrase`      | defaults          | -            |
| `gpt_realtime`      | n/a    | n/a           | n/a               | n/a          |

Presets do not select a Cartesia model, and a sent service `tts_model` value is ignored. Pin a model in `tts_config.model`; see [Default TTS Model](/developer-guide/platform-api/workspaces/voice-configuration.md#default-tts-model).

### Example

Include `voice_config` when creating or updating a service:

```json
{
  "name": "Intake Line",
  "agent_id": "abc123",
  "context_graph_id": "def456",
  "voice_config": {
    "max_buffer_delay_ms": 200,
    "filler_style": "backchannel",
    "backchannel_delay_ms": 400,
    "max_response_sentences": 1,
    "barge_in_min_speech_s": 0.25,
    "min_tts_speed": 0.92,
    "forwarding": null
  }
}
```

### CLI

Apply a preset:

```bash
forge platform service voice-config <service_id> --preset ultra_low_latency
```

Set config fields (the body replaces the stored voice config as a whole - read the current config first and include every field you want to keep):

```bash
forge platform service voice-config <service_id> --body '{"filler_style": "silent", "max_response_sentences": 1}'
```

Read current config:

```bash
forge platform service voice-config <service_id> --get
```

### API

Use [Update a Service](#update-a-service) with the `voice_config` field in the request body. The field replaces the stored voice config as a whole. Omitted fields resolve through their field-specific defaults or inheritance rules. Read the current config first (`--get` or [Get a Service](#get-a-service)) and resend the full object with your changes.

## Turn-Level Emotion

On each turn, the voice pipeline selects an emotional tone alongside the state transition and applies it to the entire utterance - filler and main response share the same tone, so the whole turn has consistent prosody with no mid-sentence shifts. See [Voice Configuration](/developer-guide/platform-api/workspaces/voice-configuration.md#turn-level-emotion-selection) for the available emotions and how the empathy system can override the selection, and [Voice Agent](/developer-guide/platform-api/conversations/voice-agent.md) for the emotional adaptation behavior.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.concurrence.com/developer-guide/platform-api/workspaces/services.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
