> 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/api-reference/change-logs/amigo-api/amigo-api-history-05.md).

# API History: Archive 5

Retained API history, archive 5, with original entry headings and links back to current integration guidance.

Historical entries retain their original release/source context. Use [Current Upgrade Guidance](/api-reference/change-logs/upgrade-guide.md) for current integration boundaries and [API](/api-reference/change-logs/amigo-api.md) for the recent history and archive index. An older entry does not establish current availability.

<details>

<summary>Platform API: Durable Dataset Updates and Cloud-Storage File Export (July 2026)</summary>

#### Durable Dataset Updates and Cloud-Storage File Export <a href="#durable-dataset-updates-and-cloud-storage-file-export" id="durable-dataset-updates-and-cloud-storage-file-export"></a>

Customer data intake now has a single asynchronous update workflow and broader support for connected storage content.

**What changed:**

* **New endpoint: `POST /v1/{workspace_id}/intake/datasets/{dataset}/update`.** Starts an asynchronous dataset update that checks mapped storage folders, prepares new files, and publishes immediately when no preparation is required. The endpoint returns `202 Accepted` with an update-run record.
* **New endpoint: `GET /v1/{workspace_id}/intake/update-runs/{run_id}`.** Returns the current update status, source and batch identifiers, timestamps, and an error message when the run fails.
* **Online documents exported during intake.** Documents that are not directly downloadable are exported to a supported file representation before preparation.
* **Shortcuts resolved.** Intake follows storage shortcuts to their target files while preserving the mapped source workflow.

**What you need to do:**

* **Poll the update run.** Treat the initial response as asynchronous and wait for a terminal status before assuming the dataset is published.

</details>

<details>

<summary>Platform API: Classic Conversation History Import (July 2026)</summary>

#### Classic Conversation History Import <a href="#classic-conversation-history-import" id="classic-conversation-history-import"></a>

Workspace administrators can now import historical Classic conversation metadata so migrated users retain accurate conversation history and dates in Platform API run listings.

**What changed:**

* **New endpoint: `POST /v1/{workspace_id}/world/migration/conversations`.** Imports 1-500 completed conversation records per request with the person entity, stable source conversation ID, channel, start and end timestamps, and optional turn count.
* **Safe repeat imports.** Repeating a source conversation updates its existing imported record instead of creating a duplicate. The response reports imported, created, and updated counts.
* **Workspace validation.** Each `entity_id` must identify a person in the workspace. Timestamps must include a time zone, be in the past, and place `started_at` no later than `ended_at`.
* **Metadata-only scope.** The endpoint imports conversation records, not transcript turns.

**What you need to do:**

* **Import historical metadata in bounded batches.** Use a stable `source_conversation_id` so a corrected batch can be submitted again safely.
* **Plan transcript migration separately.** Imported conversation records can appear in history without transcript content.

For the request schema, see [Import Classic Conversation Metadata](https://docs.concurrence.com/developer-guide/platform-api/data-world-model#import-classic-conversation-metadata).

</details>

<details>

<summary>Platform API: Unified Run Detail, Trajectory, and Non-Voice Authored Turns (July 2026)</summary>

#### Unified Run Detail, Trajectory, and Non-Voice Authored Turns <a href="#unified-run-detail-trajectory-and-non-voice-authored-turns" id="unified-run-detail-trajectory-and-non-voice-authored-turns"></a>

The unified Runs API now covers single-run reads, framework trajectories, and operator-authored replies on non-voice channels.

**What changed:**

* **New endpoint: `GET /v1/{workspace_id}/runs/{run_id}`.** Returns one conversation or framework run at any status through its channel-neutral run ID.
* **New endpoint: `GET /v1/{workspace_id}/runs/{run_id}/trajectory`.** Returns ordered structural steps for a framework run. Conversation runs return a conflict because their turn detail remains on the conversation endpoint.
* **New endpoint: `POST /v1/{workspace_id}/runs/{run_id}/authored-turn`.** Stages the next operator-authored reply on a text, SMS, email, or web run under takeover. Each staged turn is sent once.
* **Multi-value run filters.** Run list filters accept multiple kind, channel, and status values. Summary filters accept multiple kind and channel values. Both apply OR within an axis and AND across axes.
* **Legacy list endpoints retired.** The framework-only `GET /agent-runs`, conversation-only `GET /conversations`, and active-call intelligence list are removed. Create, detail, and turn endpoints remain available on their resource-specific surfaces.

**What you need to do:**

* **Move run listings to `GET /runs`.** Filter the unified result by kind or channel instead of calling the retired list endpoints.
* **Use conversation detail for transcripts.** The unified run object and framework trajectory do not replace channel-specific transcript detail.

</details>

<details>

<summary>Platform API: API-Key Permission Catalog (July 2026)</summary>

#### API-Key Permission Catalog <a href="#api-key-permission-catalog" id="api-key-permission-catalog"></a>

Clients can now build API-key creation forms from the server's role and permission model instead of maintaining a separate matrix.

**What changed:**

* **New endpoint: `GET /v1/{workspace_id}/api-keys/permission-catalog`.** Returns every API-key role, the default permissions allowed for that role, and the complete permission universe.
* **Workspace authorization.** The endpoint requires API-key view permission and is scoped to the requested workspace.
* **Server-owned compatibility.** When role defaults or available permissions change, clients receive the current model without a frontend or SDK release.

**What you need to do:**

* **Replace hard-coded permission matrices.** Load the catalog before presenting role and permission choices for a new API key.

</details>

<details>

<summary>Platform API: Per-Question (Turn-Level) Metering Attribution (July 2026)</summary>

#### Per-Question (Turn-Level) Metering Attribution <a href="#per-question-turn-level-metering-attribution" id="per-question-turn-level-metering-attribution"></a>

Token usage billing events now carry a per-question turn index, enabling usage breakdowns at the individual user-question level within a conversation.

**What changed:**

* **Turn-level attribution.** Each token-usage billing event now includes a turn index that identifies which user question (turn) the model call served. This enables per-question cost attribution in addition to the existing per-conversation and per-workspace rollups.
* **Consistent within a turn.** The turn index is snapshotted once at the start of each user turn, so all model calls that serve the same turn - including navigation and response generation - share the same index.
* **Off-turn usage.** Model calls that occur outside a user turn (for example, companion processing or background completions) carry no turn index. These calls still roll up at the conversation grain but are not attributed to any single question.
* **Batch usage unchanged.** Batch and non-conversation usage continues to carry no conversation identifier or turn index and rolls up at the workspace grain only.

**What you need to do:**

* **No action required.** This is an additive change to billing event data. Existing per-conversation and per-workspace rollups are unchanged. If you consume raw billing events for custom cost attribution, the new turn index field is available for per-question breakdowns.

</details>

<details>

<summary>Platform API: Universal Metering - All Workspaces Metered (July 2026)</summary>

#### Universal Metering - All Workspaces Metered <a href="#universal-metering-all-workspaces-metered" id="universal-metering-all-workspaces-metered"></a>

The metering pipeline now meters usage across all workspaces universally, not just billing-registered workspaces. Billing remains scoped to billable customers only - this change adds visibility into non-billable and unregistered workspace usage for cost attribution.

**What changed:**

* **Universal metering.** Every workspace's usage events now flow through the metering pipeline regardless of whether the workspace is a registered billable customer. Previously, only billable workspaces were metered. Non-billable and unregistered workspace usage is now measured and available for cost attribution rather than silently dropped.
* **Per-workspace meter rollup.** A new per-workspace meter rollup captures usage for all workspaces. This is the superset view - it includes both billable and non-billable workspaces. The customer identifier is nullable for workspaces that are not mapped to a registered customer.
* **Billing subset unchanged.** The existing per-customer billing rollup continues to include only registered billable customers. Billing outputs are byte-for-byte identical to the previous behavior - no billing amounts, invoices, or usage reports change as a result of this update.
* **Cost attribution improvement.** The universal metering data enables cost attribution for overhead and internal workspaces. Usage that was previously invisible (because it came from non-billable workspaces) is now captured and can be routed to the appropriate cost center.

**What you need to do:**

* **No action required.** This is a backend metering infrastructure change. Billing behavior, API responses, and usage reports for billable customers are unchanged. If you consume metering data for custom cost attribution, the universal per-workspace rollup now provides a complete picture of all workspace usage.

</details>

<details>

<summary>Scribe API: Interactive API Documentation (ReDoc) (July 2026)</summary>

#### Interactive API Documentation (ReDoc) <a href="#interactive-api-documentation-redoc" id="interactive-api-documentation-redoc"></a>

The Scribe API now serves interactive API documentation through a public docs endpoint, making it easier for developers to explore available endpoints, request and response shapes, and authentication requirements.

**What changed:**

* **Interactive docs endpoint.** The Scribe API now serves a ReDoc-powered interactive documentation page. The page is publicly accessible (no authentication required) and renders the same OpenAPI schema that was already available at the public schema endpoint.
* **Security headers updated.** The documentation page is served with appropriate security headers that allow the interactive UI to render correctly.

**What you need to do:**

* **No action required.** This is an additive change. Existing API behavior and endpoints are unchanged.

</details>

<details>

<summary>Platform API: Google Drive Intake - Native File Export and Shortcut Resolution (July 2026)</summary>

#### Google Drive Intake - Native File Export and Shortcut Resolution <a href="#google-drive-intake-native-file-export-and-shortcut-resolution" id="google-drive-intake-native-file-export-and-shortcut-resolution"></a>

Google Drive intake sources now automatically export Google-native files and resolve Drive shortcuts during sync, so knowledge base datasets connected to Google Drive folders ingest the actual content of Docs, Sheets, Slides, Drawings, and shortcut targets without manual conversion.

**What changed:**

* **Google-native file export.** Google Docs, Sheets, Slides, and Drawings have no downloadable binary content. The platform now exports them to an ingestible format during folder sync - Docs are exported as Markdown, and Sheets, Slides, and Drawings are exported as PDF. The exported filename carries the target format's extension (for example, a Google Doc named "Onboarding Guide" becomes "Onboarding Guide.md") so the dataset's accepted-file-type check validates against the exported format. Google-native types that have no useful export (Forms, Sites, and similar) are skipped.
* **Drive shortcut resolution.** Drive shortcuts are now resolved to their target at fetch time. A shortcut to a regular file fetches the target's content (applying export rules if the target is a Google-native file). A shortcut to a folder is traversed as a real subfolder with cycle detection. Previously, shortcuts were listed but could not be fetched.
* **Unsupported native types skipped gracefully.** Native file types without a supported export (Forms, Sites, Jamboard, and similar) are skipped during sync rather than causing an error. The sync continues with the remaining files in the folder.

**What you need to do:**

* **No action required.** This is automatic for all Google Drive intake sources. On the next sync, Google-native files in your connected folders will be exported and ingested alongside regular files. If your dataset's accepted file types do not include Markdown (.md) or PDF (.pdf), the exported files will be rejected by the file-type filter - update your accepted file types if needed.

</details>

<details>

<summary>Platform API: Optional Domain-Wide Delegation for Google Drive Intake Sources (July 2026)</summary>

#### Optional Domain-Wide Delegation for Google Drive Intake Sources <a href="#optional-domain-wide-delegation-for-google-drive-intake-sources" id="optional-domain-wide-delegation-for-google-drive-intake-sources"></a>

Google Drive intake sources now support an optional domain-wide delegation (DWD) impersonation mode as an alternative to the default direct folder-share model.

**What changed:**

* **New `impersonate_subject` field on source registration.** When registering a Google Drive intake source, you can now provide an optional `impersonate_subject` (a Workspace user email, max 320 characters). When set, the connector authenticates as this user via domain-wide delegation instead of authenticating as the service account directly.
* **Returned on source detail.** The `impersonate_subject` field is included in intake source responses when set, so callers can see which authentication model a source uses.
* **Two authentication models.** Sources without `impersonate_subject` continue to use the existing direct folder-share model (the service account authenticates as itself and the folder is shared directly with it). Sources with `impersonate_subject` use domain-wide delegation, where the service account's client ID is authorized for delegation in the customer's Workspace admin console and the connector mints tokens as the specified user.

**When to use DWD mode:**

* Use DWD when your organization's Workspace policy forbids sharing folders directly with external service account emails. Instead, authorize the service account's client ID for domain-wide delegation in your Workspace admin console and provide a Workspace user email that has Viewer access on the target folder.
* If your organization can share folders directly with the service account email, leave `impersonate_subject` unset. The existing default behavior is unchanged.

**What you need to do:**

* **No action required for existing sources.** Existing sources without `impersonate_subject` continue to work exactly as before.
* **To use DWD**, authorize the service account's client ID for domain-wide delegation in your Workspace admin console, then register or update the source with `impersonate_subject` set to a Workspace user who has Viewer access on the target folder.

</details>

<details>

<summary>Scribe API: AI-Powered Clinical Note Generation and Finalization (July 2026)</summary>

#### AI-Powered Clinical Note Generation and Finalization <a href="#ai-powered-clinical-note-generation-and-finalization" id="ai-powered-clinical-note-generation-and-finalization"></a>

The Scribe Sessions API now supports generating clinical notes from session transcripts and finalizing them with a provider signature.

**What changed:**

* **New `POST /v1/{workspace_id}/sessions/{session_id}/note` endpoint.** Generates a clinical note from the session's transcript. Accepts an optional `note_type` (full, medical, SOAP, DAP, BIRP, and several specialty-specific formats - defaults to medical) and optional free-text `instructions` (up to 1,200 characters). Returns the generated note in draft status alongside generation metadata (generation ID, model provider, model name, prompt version, and generation timestamp).
* **New `POST /v1/{workspace_id}/sessions/{session_id}/note/finalize` endpoint.** Finalizes the most recent draft or submitted note for a session, transitioning it to submitted status and recording a signature timestamp. This is the provider's sign-off on the generated content.
* **Strict transcript grounding.** Generated notes are grounded exclusively in the session transcript. The model never invents facts, diagnoses, medications, or plans not present in the transcript. Unsupported sections are omitted rather than fabricated.
* **Circuit-breaker resilience.** The generation pipeline uses circuit-breaker protection so repeated upstream failures trigger fast-fail responses rather than cascading timeouts. The circuit breaker recovers automatically after a short cooldown.
* **Input and output bounds.** Transcript content is bounded (segment count and per-segment length) before generation, and generated output is length-capped, preventing unbounded resource consumption.
* **Nine note types supported.** `full`, `medical`, `soap`, `dap`, `birp`, `amd-psych-intake`, `amd-psych-progress`, `amd-therapy-intake`, `amd-therapy-progress` - each producing notes with clinically appropriate section headings.

**New error responses:**

* `409 Conflict` - returned when the session's transcript is empty and generation cannot proceed.
* `503 Service Unavailable` - returned when clinical generation is not configured or the upstream model is experiencing failures.

**What you need to do:**

* **No action required for existing integrations.** These are new additive endpoints. Existing session and artifact endpoints are unchanged.
* **To generate notes**, call the generate endpoint with an optional note type and instructions after a session has a non-empty transcript. Review the draft note and call the finalize endpoint to sign and submit it.
* Both endpoints require the `scribe:notes:rw_own` scope.

</details>

<details>

<summary>Platform API: Configurable Output Voice for Real-Time Speech-to-Speech (July 2026)</summary>

#### Configurable Output Voice for Real-Time Speech-to-Speech <a href="#configurable-output-voice-for-real-time-speech-to-speech" id="configurable-output-voice-for-real-time-speech-to-speech"></a>

Services using the real-time speech-to-speech voice family can now select an output voice from a set of built-in voices, controlling how the agent sounds to the caller.

**What changed:**

* **New `realtime_voice` field on service voice configuration.** When the voice model family is set to real-time speech-to-speech, you can now specify an output voice. Supported voices: `alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, `shimmer`, `verse`, `marin`, `cedar`.
* **Validation enforced at write time.** Setting `realtime_voice` when the voice model family is not real-time speech-to-speech is rejected with a validation error. The field is optional - when omitted, the default voice for the backing model is used.
* **Propagated to call sessions.** The configured voice is carried through to the call session so the caller hears the selected voice for the duration of the call.

**What you need to do:**

* **No action required for existing services.** Services without a `realtime_voice` setting continue to use the default voice.
* **To select a voice**, set `realtime_voice` on the service voice configuration alongside `session_provider: gpt_realtime`. The change takes effect on the next call without a redeploy.

</details>

<details>

<summary>Platform API: Default Realtime Voice Model Updated (July 2026)</summary>

#### Default Realtime Voice Model Updated <a href="#default-realtime-voice-model-updated" id="default-realtime-voice-model-updated"></a>

The default backing model for real-time speech-to-speech voice agents has been updated to a newer version with improved tool calling, interruption handling, and alphanumeric recognition.

**What changed:**

* **New default realtime model.** The real-time speech-to-speech voice family now defaults to an updated model version that fires tools reliably and handles interruptions more naturally. The previous default remains in the recognized allowlist and can be restored via environment configuration without a redeploy.
* **No behavior change for explicitly configured workspaces.** If your workspace already sets a specific realtime model through environment configuration, that setting is unchanged. The new default applies only to workspaces using the default.
* **Allowlist expanded.** Two additional model tiers (an improved version and a fast/cheap distilled tier) are now selectable through environment configuration for A/B testing. The distilled tier is A/B-ready - it shares the same reasoning guards as the full-size model and can be trialed for cost and latency improvements. It has not yet completed live tool-depth verification, so teams should run tool-depth validation before promoting it to the default.

**What you need to do:**

* **No action required for most users.** The new default is validated for production tool-calling workloads. If you experience unexpected behavior, you can revert to the previous model via environment configuration (no redeploy needed).
* **To trial the distilled tier**, switch the realtime model configuration to the fast/cheap tier and monitor tool-calling behavior. Run tool-depth validation on the distilled tier before promoting it to your default.

</details>

<details>

<summary>Platform API: Per-Conversation Token Attribution for Text Channels (July 2026)</summary>

#### Per-Conversation Token Attribution for Text Channels <a href="#per-conversation-token-attribution-for-text-channels" id="per-conversation-token-attribution-for-text-channels"></a>

Token-usage billing events on text channels now carry the durable conversation identifier, enabling per-conversation cost rollups.

**What changed:**

* **Conversation-grain attribution on text token events.** Every token-usage billing event emitted during a text conversation now includes the conversation identifier. This allows usage reporting and analytics to roll up token consumption per conversation in addition to per session or per workspace.
* **No change for non-conversation usage.** Batch and non-conversation token events carry no conversation identifier and continue to roll up at the workspace grain only.

**What you need to do:**

* **No action required.** This is an automatic enhancement to billing event data. If you consume raw token-usage events for custom analytics, the conversation identifier is now available as an additional dimension for grouping.

</details>

<details>

<summary>Platform API: Bulk Import Endpoint for Conversation Migration (July 2026)</summary>

#### Bulk Import Endpoint for Conversation Migration <a href="#bulk-import-endpoint-for-conversation-migration" id="bulk-import-endpoint-for-conversation-migration"></a>

A new endpoint enables self-serve migration of historical conversation data from external systems into the platform's world model.

**What changed:**

* **New `POST /{workspace_id}/world/migration/conversations` endpoint.** Accepts a batch of conversation envelopes and upserts them into the world model under a target entity. Each envelope carries channel, direction, timing, participants, and message content. Envelopes are validated independently - malformed or incomplete envelopes are rejected individually without blocking the rest of the batch. The response includes per-envelope success or failure status, plus aggregate imported and failed counts.
* **Per-envelope error reporting.** Each result in the response identifies the envelope by its zero-based index, its status (`success` or `failed`), and a descriptive error message on failure, so callers can identify and retry specific failures without re-submitting the entire batch.
* **Designed for v1 migration.** The endpoint is purpose-built for importing historical conversation records (such as v1 conversation data) into the platform so they appear alongside native platform conversations in entity timelines.

**What you need to do:**

* **No action required for existing integrations.** This is an additive endpoint.
* **To migrate conversation history**, submit batches of conversation envelopes to the new endpoint. Use the per-envelope results to retry only failed items.
* **Permissions:** Requires workspace-scoped API key or session token with write permissions.

</details>

<details>

<summary>Platform API: Channel-Agnostic Eval Quality Analytics Endpoint (July 2026)</summary>

#### Channel-Agnostic Eval Quality Analytics Endpoint <a href="#channel-agnostic-eval-quality-analytics-endpoint" id="channel-agnostic-eval-quality-analytics-endpoint"></a>

A new analytics endpoint aggregates production-eval verdicts into pass-rate and score trends across all conversation channels - voice, text, SMS, email, and web.

**What changed:**

* **New `GET /v1/{workspace_id}/analytics/eval-quality` endpoint.** Returns production-eval verdict aggregates for the workspace over a configurable time window. The response includes an overall summary (total evaluations, judged count, pass rate, average score), a per-eval-key breakdown ordered by volume, and a time-bucketed trend series at the requested interval (`1h`, `1d`, or `1w`). Supports optional `service_id` filtering.
* **Channel-agnostic quality signal.** Unlike the existing call quality analytics (which derive from voice-shaped call intelligence data), eval quality is keyed on conversations regardless of channel. Voice calls, text sessions, SMS, email, and web conversations all contribute equally to the aggregates.
* **Safe empty-state behavior.** When no verdicts exist for the workspace or time window - for example, before automatic post-conversation evaluation is enabled - the endpoint returns zeroed shapes (empty per-key and trend arrays, null rates in the summary) so consumers can render an empty state rather than receiving an error.

**What you need to do:**

* **No action required.** This is an additive endpoint. Integrate it into your analytics workflows or dashboards when you want a channel-agnostic view of production evaluation quality.
* **Permissions:** Workspace viewer or above (same as other analytics endpoints).

</details>

<details>

<summary>Platform API: Durable Background Reply Delivery, SMS Opt-In, Turn Idempotency, and Conversation Close (July 2026)</summary>

#### Durable Background Reply Delivery, SMS Opt-In, Turn Idempotency, and Conversation Close <a href="#durable-background-reply-delivery-sms-opt-in-turn-idempotency-and-conversation-close" id="durable-background-reply-delivery-sms-opt-in-turn-idempotency-and-conversation-close"></a>

Web text conversations now use a receipt-backed delivery protocol for background tool replies, SMS channels record opt-in consent before each reply, message turns support idempotency keys, and conversations can be explicitly closed through the API.

**What changed:**

* **Durable background reply delivery (web text).** When a background tool completes during a web text session, the platform commits the reply to a durable outbox. The caller claims it via polling, renders the message, and acknowledges receipt. This ensures background answers survive disconnects and restarts. The turn-done event now includes `background_pending` (true when a final answer must be collected from the delivery outbox) and `delivery_protocol_version` (the protocol version supported by the serving agent) so clients can adapt their polling strategy.
* **Receipt-backed poll endpoint.** The existing poll request now accepts an optional `poll_request_id` parameter. When provided, the poll atomically claims at most one committed delivery (or a typed safe failure message) from the conversation's outbox. The response includes `delivery_id` and `delivery_receipt` fields that the caller passes to the new acknowledgement endpoint to confirm receipt.
* **Delivery acknowledgement endpoint.** A new internal endpoint accepts the `delivery_id` and `delivery_receipt` from a claimed delivery and advances the outbox. Acknowledged deliveries are not returned again.
* **Conversation close endpoint.** A new endpoint explicitly closes a web text conversation. Closing tears down the delivery state, cancels in-flight background tasks, and prevents further turns. The next inbound message from the contact starts a fresh conversation.
* **Turn idempotency.** The text interact endpoint now accepts an optional `turn_request_id` parameter. When a client retries a turn with the same idempotency key, the platform returns the cached reply from the original turn instead of re-processing the message. Reuse of the same key with a different message body is rejected with a 409.
* **Agent message delivery ID.** The `text.agent_message` event now carries an optional `delivery_id` field linking the SSE event to the delivery outbox entry.
* **SMS opt-in before reply.** On SMS channels, the platform records the recipient's consent before sending each reply. If the recipient has actively opted out (a prior STOP), the reply is suppressed and a structured error is returned. Channels that do not require carrier-level consent proceed without an opt-in gate.

**What you need to do:**

* **Web text clients** should check `background_pending` and `delivery_protocol_version` on turn-done events. When `background_pending` is true and the protocol version is present, use receipt-backed polling with `poll_request_id` to collect background replies, then acknowledge each delivery.
* **SMS integrations** require no changes. Opt-in is handled automatically by the platform before each reply.
* **Clients that retry turns** can pass a `turn_request_id` to get idempotent behavior. Omitting the field preserves existing behavior.

</details>

<details>

<summary>Platform API: Analytics Dashboard KPI Endpoint Typed and Operator Performance Aggregation Fixed (July 2026)</summary>

#### Analytics Dashboard KPI Endpoint Typed and Operator Performance Aggregation Fixed <a href="#analytics-dashboard-kpi-endpoint-typed-and-operator-performance-aggregation-fixed" id="analytics-dashboard-kpi-endpoint-typed-and-operator-performance-aggregation-fixed"></a>

The composite analytics dashboard endpoint now returns a fully typed response with six named KPIs instead of an untyped dictionary, and the operator performance summary uses a corrected aggregation method.

**What changed:**

* **Typed dashboard response.** The `GET /v1/{workspace_id}/analytics/dashboard` endpoint now returns a structured response with six named KPI objects (`call_volume`, `avg_quality`, `avg_ttfb_ms`, `escalation_rate`, `tool_success_rate`, `avg_duration_s`), each carrying a `value` (current-period value, or null when no data) and a `delta_pct` (signed percent change versus the previous equal-length period, or null when a prior-period comparison is unavailable), plus a `period_days` field indicating the reporting period length.
* **KPI polarity guidance.** Higher is better for `call_volume`, `avg_quality`, and `tool_success_rate`. Lower is better for `escalation_rate`, `avg_ttfb_ms`, and `avg_duration_s`. Consumers should color deltas by each KPI's polarity rather than by delta sign alone.
* **Operator performance aggregation fix.** The operator performance summary endpoint now computes average handle time as a call-weighted mean (weighted by escalations handled per operator) instead of an unweighted mean of per-operator averages. The aggregation also pages through all operators in the workspace rather than truncating at a fixed limit, so workspaces with many operators get accurate totals.
* **Insight metric sentiment field.** Insight block metrics now support a `sentiment` field (`good`, `bad`, or `neutral`) that is independent of the `trend` field (`up`, `down`, `flat`). Trend indicates the direction the value moved (drives the arrow icon). Sentiment indicates whether that movement is good or bad for the business (drives the color). For "higher is worse" metrics - such as escalation rate or latency - a rising value has trend `up` but sentiment `bad`. Consumers should use sentiment for color and trend for direction.

**What you need to do:**

* **Update any integrations** that consume the analytics dashboard endpoint. The response is now a structured object with named fields instead of an untyped dictionary. The field names and value shapes are documented in the OpenAPI spec.
* **Review any code** that interprets operator performance summary averages. The average handle time value may differ from previous responses due to the corrected weighted-mean calculation.
* **Update insight rendering** if you consume insight blocks. Use the `sentiment` field for color (green/red/grey) and the `trend` field for arrow direction. Do not assume that `trend: "up"` means good.

</details>

<details>

<summary>Platform API: Legacy Read Endpoints Removed Behind Unified Runs Surface (July 2026)</summary>

#### Legacy Read Endpoints Removed Behind Unified Runs Surface <a href="#legacy-read-endpoints-removed-behind-unified-runs-surface" id="legacy-read-endpoints-removed-behind-unified-runs-surface"></a>

Several legacy list and read endpoints that were superseded by the unified Runs surface have been removed from the API. These endpoints were no longer serving traffic - all reads now go through the unified runs list, run detail, and conversation detail endpoints.

**What changed:**

* **Conversation list endpoint removed.** The `GET /v1/{workspace_id}/conversations` endpoint that returned a paginated list of text and voice conversations has been removed. Use the unified runs list endpoint (`GET /v1/{workspace_id}/runs`) with channel filters to list conversations across all channels.
* **Agent runs list endpoint removed.** The standalone `GET /v1/{workspace_id}/agent-runs` list endpoint for framework agent runs has been removed. Use the unified runs list endpoint with the framework kind filter instead.
* **Active calls intelligence endpoint removed.** The `GET /v1/{workspace_id}/calls/active/intelligence` endpoint that returned active calls with live intelligence overlay data has been removed. Active call data is available through the unified runs list with the live status filter.

**What you need to do:**

* **Migrate any integrations** that call the removed endpoints to use the unified runs list endpoint (`GET /v1/{workspace_id}/runs`). The unified endpoint supports filtering by kind (framework or conversation), channel (voice, text, SMS, email, web), and status (including a "live" filter for active runs).
* **Update any SDK or automation code** that referenced the standalone conversation list, agent runs list, or active calls intelligence endpoints.

</details>

<details>

<summary>Voice Agent: Per-Call Media Isolation Now Universal (July 2026)</summary>

#### Per-Call Media Isolation Now Universal <a href="#per-call-media-isolation-now-universal" id="per-call-media-isolation-now-universal"></a>

Per-call voice isolation is now permanent and universal for every call. The graduated rollout controls (workspace allowlist and environment toggle) have been removed - every call allocates an isolated media server with no legacy bypass.

**What changed:**

* **Universal isolation.** Every voice call now takes the per-call isolation path. The platform allocates an isolated media server for every call and pins the routing for the duration of the call. There is no longer a legacy shared-path fallback.
* **Workspace allowlist removed.** The optional workspace allowlist that previously narrowed per-call isolation to specific workspaces has been removed. All workspaces are on the isolation path.
* **Environment toggle removed.** The environment-level toggle that enabled or disabled per-call isolation has been removed. Isolation is always active for in-cluster calls.
* **Busy redirect on pool exhaustion.** If the isolation pool is exhausted or the allocation fails, the platform redirects the caller to a busy message and ends the call rather than leaving them in dead air. There is no fallback to a shared path.
* **Contour transport gating simplified.** The choice between media transports is now gated purely by a per-workspace feature flag. The transport suffix is provisioned in every cluster, so the flag alone decides which transport a call uses.

**What you need to do:**

* **No action required.** This is a backend change. All voice calls already on the per-call isolation path are unaffected. Workspaces that were previously on the legacy path are now automatically on the isolation path.
* **Remove any references** to the workspace allowlist or environment toggle in operational runbooks or configuration management - these settings no longer exist.

</details>

<details>

<summary>Platform API: API-Key Role and Permission Catalog Endpoint (July 2026)</summary>

#### API-Key Role and Permission Catalog Endpoint <a href="#api-key-role-and-permission-catalog-endpoint" id="api-key-role-and-permission-catalog-endpoint"></a>

A new read-only endpoint returns the authoritative role-to-permission model for API-key creation, so clients can build key-creation forms from the server-side source of truth instead of hard-coding the permission matrix.

**What changed:**

* **Permission catalog endpoint.** A new `GET /v1/{workspace_id}/api-keys/permission-catalog` endpoint returns the complete role and permission catalog. The response includes each role's name, priority, description, and default permission set (what an API key of that role may carry), plus the full permission universe with each permission's full name, namespace, and action.
* **Authorization.** The endpoint requires the `ApiKey.view` permission - the same gate used for listing API keys. A caller that can inspect keys can also read the catalog needed to configure them.
* **Workspace-scoped.** The endpoint is served under the workspace scope so clients fetch it with the same credential used for other API-key operations. The payload is workspace-independent today but is scoped for forward compatibility.

**Why this matters:**

Previously, clients (the Developer Console, SDKs, custom tooling) maintained their own copy of the role-to-permission matrix. This copy could drift from the server-side definitions - for example, listing a permission as a role default that the server did not accept - causing key-creation requests to fail with a 422 error. The catalog endpoint eliminates this drift by providing a single source of truth.

**What you need to do:**

* **No action required.** This is an additive endpoint. Existing API-key creation, listing, deletion, and rotation endpoints are unchanged.
* **SDK and console integrations** can use the catalog endpoint to dynamically populate role and permission selectors in key-creation forms instead of maintaining a hardcoded permission list.

</details>

<details>

<summary>Platform API: Framework Run Trajectory Endpoint (July 2026)</summary>

#### Framework Run Trajectory Endpoint <a href="#framework-run-trajectory-endpoint" id="framework-run-trajectory-endpoint"></a>

A new endpoint returns the step-by-step structural trajectory of a framework run, so the console and API consumers can inspect the ordered steps a framework agent took during a run.

**What changed:**

* **Trajectory endpoint.** A new `GET /v1/{workspace_id}/runs/{run_id}/trajectory` endpoint returns the ordered structural steps of a framework run. Each step includes structural metadata - step kind, sequence number, actor, state, tool name with input and result summaries, tool success indicator, and decision state transitions. Verbatim transcript text and raw model reasoning are not included.
* **Truncation handling.** The response includes a `truncated` flag. When a run has more steps than the server-side bound, the response contains the first N steps and sets `truncated` to `true`, so consumers can indicate that the trajectory is partial.
* **Conversation run guard.** Requesting the trajectory for a conversation run returns HTTP 409 with a message directing callers to the conversation detail endpoint for per-turn data.
* **Workspace-scoped authorization.** The endpoint is gated on workspace tenant isolation - a run identifier from another workspace or an unknown identifier returns 404 with no tenant leak.

**What you need to do:**

* **No action required.** This is an additive endpoint. Existing run list, run detail, and run-scoped operation endpoints are unchanged.
* **Console integrations** can use the trajectory endpoint to render step-by-step framework run detail in place of the previous "trajectory rendering is a follow-up" placeholder.

</details>

<details>

<summary>Platform API: Intake Dataset Update Runs - One-Click Sync, Prepare, and Publish (July 2026)</summary>

#### Intake Dataset Update Runs - One-Click Sync, Prepare, and Publish <a href="#intake-dataset-update-runs-one-click-sync-prepare-and-publish" id="intake-dataset-update-runs-one-click-sync-prepare-and-publish"></a>

A new dataset update flow orchestrates the full sync-prepare-publish cycle for a dataset in a single action, giving the console a durable status to poll instead of stitching together independent resources.

**What changed:**

* **Dataset update endpoint.** A new `POST /intake/datasets/{dataset}/update` endpoint triggers a one-click update for a dataset. The platform identifies all active cloud storage sources mapped to the dataset, syncs their folders for new or changed files, prepares any newly landed files, and publishes the results. The endpoint returns immediately with a durable update run that can be polled for progress.
* **Durable update run status.** Each update run tracks its progress through a defined set of statuses: checking the cloud storage source, preparing files, publishing, completed, needs review, or failed. The console polls a single resource for the current state rather than inferring status from independent batches and materializer runs.
* **Automatic advancement.** If file preparation is asynchronous, the update run advances automatically as files complete processing. When all files are ready, the run triggers publishing. If any files need review before publishing, the run surfaces a "needs review" status rather than proceeding.
* **At-most-one active run per dataset.** Only one update run per dataset is active at a time. Requesting an update while one is already in progress returns the existing run's current status.
* **Update run detail endpoint.** A new `GET /intake/update-runs/{run_id}` endpoint returns the current state of an update run, refreshing its status against upstream progress on each read.
* **Heartbeat during long syncs.** Long-running cloud storage syncs periodically update the run's timestamp so the run does not appear stale while files are still being discovered and downloaded.
* **Error handling.** If any stage of the update fails (cloud storage authentication, file sync, processing dispatch, or publishing), the run captures a descriptive error and moves to a failed status rather than leaving the run in an indeterminate state.

**What you need to do:**

* **No action required for existing integrations.** The existing source-level sync, batch processing, and materialize endpoints continue to work as before. The dataset update endpoint is an additional orchestration layer.
* **Console integrations** can use the new dataset update endpoint to replace multi-step sync-process-publish workflows with a single call and a poll loop on the returned run.

</details>

<details>

<summary>Platform API: EHR Appointment Reschedule Reliability - Patient Identity and Edit Verification (July 2026)</summary>

#### EHR Appointment Reschedule Reliability - Patient Identity and Edit Verification <a href="#ehr-appointment-reschedule-reliability-patient-identity-and-edit-verification" id="ehr-appointment-reschedule-reliability-patient-identity-and-edit-verification"></a>

Appointment reschedules through the EHR connector are now more reliable: the platform carries the patient identity forward on edits and verifies that reschedules actually applied before reporting success.

**What changed:**

* **Patient identity carried forward on reschedule.** When rescheduling an existing appointment, the platform now reads the appointment's patient identity from the live appointment detail and includes it in the edit submission. The EHR's appointment edit form requires a patient identity - previously, reschedules omitted it, causing the EHR to silently reject the edit with no error. Creates were unaffected (they always sent the patient identity).
* **Edit verification by calendar read-back.** After submitting a reschedule, the platform now verifies the edit applied by reading the provider's calendar and confirming the appointment appears at the new time with the correct appointment identifier. If neither the EHR's response nor the calendar read-back confirms the edit, the endpoint returns a `verify_failed` status (HTTP 502) with a descriptive reason rather than falsely reporting the appointment as rescheduled.
* **Eventual-consistency retry on read-back.** The calendar read-back retries across a short window to accommodate EHR systems where a just-written appointment takes a few seconds to appear in calendar queries. This retry applies only to post-write verification - pre-write idempotency checks do not retry.
* **Multi-day calendar query window.** Calendar queries for conflict checking, post-write read-back, and appointment-id resolution now query a multi-day window around the target date instead of a single day. This works around EHR calendar APIs that return empty results for single-day queries, which previously caused silent read-back failures and missed conflict detection.
* **Unresolvable patient on reschedule returns 422.** If the platform cannot resolve the appointment's patient identity from its live detail (for example, a group or couples appointment where no single patient is identified), the reschedule is refused with a `verify_failed` status (HTTP 422) and a descriptive reason rather than submitting a knowingly invalid edit.
* **Improved error logging for unparseable EHR responses.** When the EHR returns a response that the platform cannot parse into an appointment identifier, the platform now logs non-PHI metadata (status code, content type, response length) for diagnostics. Patient data is never included in log entries.

**What you need to do:**

* **No action required for most integrations.** Appointment reschedules are now more reliable automatically. Previously silent failures will now surface as `verify_failed` responses so callers can detect and handle them.
* **If your integration handles the `verify_failed` status**, note that reschedule failures may now return HTTP 422 (unresolvable patient) in addition to the existing HTTP 502 (write or verification failure). Both carry a descriptive `reason` field.

</details>

<details>

<summary>Platform API: Durable EHR Appointment Sync, Delete Detection, and Faster Detail Refresh (July 2026)</summary>

#### Durable EHR Appointment Sync, Delete Detection, and Faster Detail Refresh <a href="#durable-ehr-appointment-sync-delete-detection-and-faster-detail-refresh" id="durable-ehr-appointment-sync-delete-detection-and-faster-detail-refresh"></a>

The EHR connector's appointment sync is now more reliable, detects deleted appointments, and refreshes detail-only edits faster.

**What changed:**

* **Durable event delivery for all EHR sync paths.** Appointment, practitioner, availability, and patient sync events are now confirmed delivered before the dedup marker advances. If delivery is not confirmed, the event is automatically retried on the next poll rather than silently dropped. This closes a window where an unconfirmed emit could be marked as sent, causing the change to never surface in the world model.
* **Deleted appointment detection.** When an appointment is deleted in the source EHR, the platform now detects its absence by comparing the current upcoming-appointment window against prior state and emits a cancellation record that supersedes the last observation. Previously, deleted appointments lingered as active records indefinitely because the sync only ever upserted appointments present in each fetch.
* **Safety guards against mass cancellation.** Two guards prevent a degraded or partial EHR fetch from incorrectly cancelling a large number of appointments. Small appointment sets skip the ratio guard entirely (the blast radius is inherently bounded). If a fetch's upcoming set shrinks below a configurable fraction of the prior window, the platform treats the fetch as degraded and skips delete detection for that cycle rather than acting on incomplete data.
* **Faster detail-only refresh.** Appointment detail re-hydration - the periodic forced refresh that catches edits visible only in per-appointment detail (such as no-show status, telehealth toggles, room changes, service or billing code updates, duration changes, or provider reassignments that do not move the calendar slot) - now runs on a faster cadence. Detail-only edits surface within roughly one to two hours instead of most of a day.
* **Tunable re-hydration cadence.** The detail re-hydration frequency can be adjusted per connection configuration without a code change, so practices with different appointment volumes can balance freshness against fetch cost.

**What you need to do:**

* **No action required.** These changes improve sync reliability and freshness automatically. Deleted appointments will now be reflected as cancelled in the world model. Detail-only edits will appear faster.
* **If you have a large practice with high appointment volume**, you can tune the detail re-hydration cadence through connection configuration to balance freshness against API cost.

</details>

<details>

<summary>Platform API: EHR Patient Creation No Longer Auto-Assigns Intake Packet Cohort (July 2026)</summary>

#### EHR Patient Creation No Longer Auto-Assigns Intake Packet Cohort <a href="#ehr-patient-creation-no-longer-auto-assigns-intake-packet-cohort" id="ehr-patient-creation-no-longer-auto-assigns-intake-packet-cohort"></a>

When creating a patient through the EHR connector, the platform no longer automatically assigns the intake-packet cohort. This prevents the EHR from emailing intake packets to agent-created patients.

**What changed:**

* **No default cohort assignment on patient create.** Previously, when no cohort IDs were specified in a patient creation request, the platform defaulted to assigning the intake-packet cohort. The EHR treats cohort assignment as a trigger to email the associated packet to the patient - a side effect that is unwanted for patients created by the agent. The platform now assigns only the cohorts the caller explicitly passes.
* **Explicit cohort assignment still supported.** Callers that want the intake packet sent can still include the cohort ID in their request. The behavior is identical to before when cohort IDs are explicitly provided.
* **No change to existing patients.** Patients already assigned to the intake-packet cohort are unaffected. This change applies only to new patient creation requests going forward.

**What you need to do:**

* **If you rely on the intake packet being sent automatically at patient creation**, update your integration to explicitly pass the intake-packet cohort ID in the `cohort_ids` field when creating a patient.
* **If you create patients through the agent and do not want intake packets sent**, no action is required - this is now the default behavior.

</details>

<details>

<summary>Platform API: Streaming Intake File Downloads (July 2026)</summary>

#### Streaming Intake File Downloads <a href="#streaming-intake-file-downloads" id="streaming-intake-file-downloads"></a>

Intake file download endpoints now stream file bytes incrementally instead of buffering the entire file in memory before responding.

**What changed:**

* **Streaming proxy for intake downloads.** Both the intake catalog download endpoint and the intake link upload download endpoint now stream file bytes in fixed-size chunks directly from storage to the caller. Previously, the platform read the entire file into memory before sending the response, which could cause memory pressure for large uploads.
* **Bounded-memory proxying.** The response is sent incrementally as bytes arrive from the storage backend. The platform never holds the full file in memory, so download size is no longer constrained by available service memory.
* **Content-Length when available.** When the storage backend supplies a content length, the response includes a `Content-Length` header so callers can display download progress. If the storage backend omits the length, the header is absent and the response uses chunked transfer encoding.
* **Mid-stream error logging.** If the storage connection fails partway through a download, the error is logged with correlation metadata (upload ID, link ID, workspace ID, error type, and upstream status when available) for diagnostics. No PHI is included in the log entry.
* **No change to upload behavior.** File uploads, content-type validation, and virus scanning are unaffected.

**What you need to do:**

* **No action required for most callers.** The response content type, filename, and disposition headers are unchanged. Callers that stream the response body (the recommended approach) work without modification.
* **Callers that rely on Content-Length** should handle the case where the header is absent, as it is now conditional on the storage backend providing it.

</details>

<details>

<summary>Platform API: Single-Run Detail Endpoint (July 2026)</summary>

#### Single-Run Detail Endpoint <a href="#single-run-detail-endpoint" id="single-run-detail-endpoint"></a>

A new endpoint returns a single run by its channel-neutral run identifier at any status, closing the gap where the unified runs list had no per-run detail counterpart.

**What changed:**

* **New endpoint: `GET /{workspace_id}/runs/{run_id}`.** Resolve a single run by its channel-neutral `run_id` and return the canonical `Run` object at any status - live, completed, failed, or timed out. The endpoint federates framework runs, conversation runs, and the live voice registry, matched by `run_id` and scoped to the workspace. Use the returned `kind`, `channel`, and source provenance fields to open the channel-appropriate detail view (voice call, text conversation, framework trajectory).
* **Workspace-scoped with no tenant leak.** The run identifier is resolved within the authenticated workspace only. A `run_id` from another workspace or an unknown identifier returns 404 - no information about other workspaces is disclosed.
* **Live voice overlay.** An in-flight voice call is resolvable before its terminal record is written. Once the terminal record exists, it is authoritative and the live entry is dropped, consistent with the list endpoint's deduplication behavior.
* **Fail-open federation.** All sources (framework, conversation, live voice) are queried concurrently. A transient failure in any single source degrades to "no match from that source" rather than failing the request, so a single source outage never blocks a run lookup.
* **Same response shape as the list.** The returned `Run` object carries the same fields as a row from the list endpoint, including optional enrichment fields (entity name, service name, caller identity, contact number, direction, turn count, completion reason).

**What you need to do:**

* **No action required for existing integrations.** This is an additive endpoint. Existing list, summary, takeover, and guidance endpoints are unaffected.
* **Use this endpoint for run detail views.** Instead of resolving a run's source record through channel-specific endpoints, call `GET /{workspace_id}/runs/{run_id}` to get the canonical run and then use its `kind` and `channel` fields to compose the appropriate detail view.

</details>

<details>

<summary>Platform API: Non-Voice Takeover Route Dispatch and Authored-Turn Endpoint (July 2026)</summary>

#### Non-Voice Takeover Route Dispatch and Authored-Turn Endpoint <a href="#non-voice-takeover-route-dispatch-and-authored-turn-endpoint" id="non-voice-takeover-route-dispatch-and-authored-turn-endpoint"></a>

The takeover and handback endpoints now fully dispatch for non-voice channels (text, SMS, email, web), and a new authored-turn endpoint lets operators stage replies on a taken-over non-voice run.

**What changed:**

* **Takeover and handback dispatch for non-voice channels.** The `POST /{workspace_id}/runs/{run_id}/takeover` and `POST /{workspace_id}/runs/{run_id}/handback` endpoints no longer return 409 for non-voice runs. Taking over a non-voice run suspends the agent (marks the run paused); handing back clears the suspension so the agent resumes. Both transitions are idempotent - a repeat takeover on an already-paused run or a repeat handback on an already-active run is a no-op success. The non-voice takeover response carries `mode: "takeover"` only (listen is not offered because there is no live audio to monitor).
* **New endpoint: `POST /{workspace_id}/runs/{run_id}/authored-turn`.** Stage an operator-authored reply for a non-voice run under takeover. The platform substitutes it for the agent's next outbound turn (exactly-once), so the caller receives the operator's words while the agent stays suspended. Repeatable - each call stages the next turn. Requires `admin` role (Operator:Update) and own-identity enforcement (no impersonation). Returns 404 if the run is not live; returns 409 for voice runs (voice takeover drives the live audio leg directly). Request body: `operator_id` (UUID, required), `text` (string, 1-10,000 characters, required). Response: `run_id` (UUID), `staged` (boolean, always true on 200).
* **Takeover eligibility updated for non-voice channels.** The server-computed `takeover_eligibility` object on run responses now reports non-voice conversation runs as eligible with `mode_options: ["takeover"]` (no listen mode). Previously these channels were reported as ineligible. Voice runs continue to report `mode_options: ["listen", "takeover"]`.
* **Authored-turn attempts are audit-logged.** Each authored-turn call emits an audit event with operator and run identifiers. The turn text is never included in the audit record.

**What you need to do:**

* **No action required for existing integrations.** This is an additive change. Voice takeover workflows are unaffected. Callers that previously received 409 for non-voice takeover will now receive a successful response.
* **Use the new authored-turn endpoint to compose replies.** After taking over a non-voice run, POST to `/{workspace_id}/runs/{run_id}/authored-turn` with the operator's reply text. The agent's next outbound step will send the operator's text instead of its own.

</details>

<details>

<summary>Platform API: Non-Voice Operator Takeover - Authored-Turn Send-Gate (July 2026)</summary>

#### Non-Voice Operator Takeover - Authored-Turn Send-Gate <a href="#non-voice-operator-takeover-authored-turn-send-gate" id="non-voice-operator-takeover-authored-turn-send-gate"></a>

Operators can now take over non-voice conversation runs (text, SMS, email, web) by composing a reply that the platform sends instead of the agent's next outbound turn.

**What changed:**

* **Authored-turn substitution for non-voice channels.** When an operator takes over a non-voice run, the operator's composed reply is durably staged and consumed exactly once by the agent's next outbound step. The agent's own outbound text is replaced with the operator's authored turn, so the caller receives the operator's reply. This is the non-voice counterpart to voice takeover's live audio seize.
* **Decoupled compose and send.** The operator's authored turn survives pod restarts and rolling deploys between compose and send. The durable staging ensures the operator's reply is never silently dropped.
* **Send-gate on agent outbound.** Before sending each outbound turn (both mid-conversation replies and terminal closing messages), the agent checks for a pending operator-authored turn. If present, it atomically consumes and substitutes it. If absent, the agent's own turn proceeds with zero behavior change.
* **Tenant isolation.** An operator in one workspace cannot plant an authored turn for another workspace's agent. The authored turn is scoped to both the workspace and the run.
* **Distinct from integration-write approval.** The authored-turn mechanism is a separate obligation from the integration approval gate. Integration approval resolves a parked integration write by conversation; authored-turn takeover substitutes the agent's outbound text by run. The two mechanisms operate independently.
* **Fail-open consumption.** If the durable store is transiently unavailable when the agent checks for an authored turn, the agent proceeds with its own outbound rather than blocking. The authored turn remains staged for the next attempt.

**What you need to do:**

* **No action required for existing integrations.** This is an additive capability. Non-voice takeover uses the same run-scoped takeover endpoint already documented for voice runs. Existing voice takeover workflows are unaffected.
* **Non-voice runs now support takeover eligibility.** The server-computed takeover eligibility object on run responses will reflect takeover availability for non-voice channels where previously it returned ineligible.

</details>

<details>

<summary>Platform API: Multi-Value OR Filters on Runs List and Summary (July 2026)</summary>

#### Multi-Value OR Filters on Runs List and Summary <a href="#multi-value-or-filters-on-runs-list-and-summary" id="multi-value-or-filters-on-runs-list-and-summary"></a>

The `status`, `kind`, and `channel` query parameters on the runs list and summary endpoints now accept multiple values, acting as OR filters within each axis.

**What changed:**

* **`status`, `kind`, and `channel` accept repeated query parameters.** Each filter axis now accepts multiple values as repeated query parameters (e.g. `?status=failed&status=timed_out`). Values within an axis are unioned (OR), and different axes are combined (AND). For example, `?channel=voice&status=running` returns only live voice runs, while `?status=failed&status=timed_out` returns runs matching either status.
* **Applies to both list and summary endpoints.** The `GET /{workspace_id}/runs` list endpoint and the `GET /{workspace_id}/runs/summary` summary endpoint both support the same multi-value filter shape.
* **Backward compatible.** A single filter value (e.g. `?status=running`) continues to work exactly as before - the multi-value format is a strict superset of the previous single-value format. Omitting a filter axis entirely means no filter on that axis, matching the previous behavior.
* **`kind` filter routing.** When `kind` includes `framework`, framework runs are included; when it includes `conversation`, conversation runs are included. An empty `kind` (or both values) returns both sources. A `channel` filter still suppresses framework runs (which carry no channel), consistent with the previous behavior.

**What you need to do:**

* **No action required for existing integrations.** Single-value filter parameters continue to work unchanged. This is an additive, backward-compatible change.
* **Use repeated query parameters for multi-select.** To filter by multiple values, repeat the parameter: `?status=failed&status=timed_out`. Do not use comma-separated values.

</details>

<details>

<summary>Scribe API: Session-Centric Provider Access (July 2026)</summary>

#### Scribe Sessions API <a href="#scribe-sessions-api" id="scribe-sessions-api"></a>

A new session-centric REST API gives providers read access to their own clinical documentation sessions and artifacts.

**What changed:**

* **New endpoint: `GET /v1/{workspace_id}/sessions`.** Lists clinical documentation sessions owned by the authenticated provider, ordered by creation time (newest first). Supports pagination via `limit` (1-200, default 50) and opaque `continuation_token` query parameters. Each session includes lifecycle status and artifact availability.
* **New endpoint: `GET /v1/{workspace_id}/sessions/{session_id}`.** Returns a single session owned by the authenticated provider.
* **New endpoint: `GET /v1/{workspace_id}/sessions/{session_id}/transcript`.** Returns the session transcript segmented by speaker with timing information (start and end in milliseconds). Returns 404 if the transcript is not yet available.
* **New endpoint: `GET /v1/{workspace_id}/sessions/{session_id}/note`.** Returns the clinical note for a session, including note type, authoring status (`draft`, `submitted`, `voided`), body text, structured content, generation timestamp, and signature timestamp.
* **New endpoint: `GET /v1/{workspace_id}/sessions/{session_id}/codes`.** Returns ICD code suggestions for a session, each with code, description, rationale, confidence score (0-1, optional), and acceptance status (`suggested`, `accepted`, `rejected`, `voided`).
* **Session lifecycle statuses.** Sessions report their status as `created`, `in-progress`, `in-review`, `completed`, `cancelled`, or `failed`.
* **Artifact availability tracking.** Each session reports per-artifact availability (`pending`, `available`, or `failed`) for transcript, note, summary, and codes independently.
* **Provider ownership enforcement.** All endpoints enforce that the authenticated provider owns the requested session. Sessions belonging to other providers return 404.
* **Structured error envelope.** All error responses use a consistent envelope with `code`, `message`, `correlation_id`, and optional `details` array for field-level validation errors.
* **Correlation ID support.** Every response includes an `X-Correlation-ID` header. Callers can send their own correlation ID on requests for end-to-end tracing.
* **Deployment-specific API base.** Scribe routes use the Scribe API base URL supplied for the deployment, not the general Platform API base URL.
* **OpenAPI schema.** The Scribe API publishes its schema at `/v1/openapi.json` on that base URL for client generation and discovery.

**What you need to do:**

* **No action required for existing integrations.** These are new, additive endpoints. Providers with valid scribe session tokens and the `scribe:sessions:read_own` scope can begin using the endpoints immediately.
* **Handle artifact availability states.** Artifacts may not be immediately available after a session completes. Check the `artifacts` field on the session response before requesting individual artifacts. A `pending` status means the artifact is still being processed; `failed` means it could not be produced.

</details>

<details>

<summary>Platform API: 'Paused' Conversation Run Status (July 2026)</summary>

#### Paused Conversation Run Status <a href="#paused-conversation-run-status" id="paused-conversation-run-status"></a>

Conversation runs now support a "paused" status that represents a human operator taking over a live run.

**What changed:**

* **New run status: `paused`.** When an operator takes over a conversation run, the run's status is now durably recorded as `paused` rather than remaining `running`. This is a live, non-terminal status - the conversation still exists and the agent is suspended until the operator hands back. On handback, the run resumes its previous status.
* **`ConversationStatus` enum updated.** The conversation status model now includes `paused` as a valid value alongside `active`, `closed`, `completed`, `in-progress`, and `failed`.
* **Run status mapping.** The `paused` conversation status maps to the canonical `paused` run status, which is a live (non-terminal) status. Paused runs continue to appear in the "live" filter (which expands to `running` + `paused`) and are counted in the summary's live total.
* **OpenAPI spec updated.** The conversation status enum in the API schema now includes `paused`.

**What you need to do:**

* **Handle the new status value.** If your integration reads conversation or run status values, ensure it handles `paused` as a valid, non-terminal status. Paused runs are live and will eventually return to `running` (on handback) or reach a terminal state.
* **No breaking changes.** Existing filters and queries continue to work. The "live" status filter already expands to include `paused`, so live-filtered views will automatically include paused runs.

</details>

<details>

<summary>Platform API: Run-Scoped Switch-Mode and Access-Token Endpoints (July 2026)</summary>

#### Run-Scoped Switch-Mode and Access-Token <a href="#run-scoped-switch-mode-and-access-token" id="run-scoped-switch-mode-and-access-token"></a>

Two new run-scoped endpoints let operators switch between listen and takeover modes on a live run and mint browser-audio credentials, both addressed by the channel-neutral run identifier.

**What changed:**

* **New endpoint: `POST /v1/{workspace_id}/runs/{run_id}/switch-mode`.** Toggles the operator between `listen` (monitor without driving) and `takeover` (suspend the agent and drive) on a run they have already joined. For voice runs, this mutes or unmutes the operator's conference participant. Requires Operator update permission (operator, admin, or owner role), bound to the caller's own operator identity (no impersonation). Returns 404 if the run is not live in the workspace; 409 if the channel does not support live takeover yet.
* **Request body:** `operator_id` (uuid), `participant_call_sid` (string, max 64 chars - the participant identifier from the takeover response), `mode` ("listen" or "takeover").
* **Response:** `run_id` (uuid), `mode` (the applied mode).
* **New endpoint: `POST /v1/{workspace_id}/runs/{run_id}/access-token`.** Mints browser-audio credentials so the console can attach the operator's WebRTC leg to a live voice run. This is the media-plane companion to the takeover endpoint (the control plane). Requires Operator update permission (operator, admin, or owner role), bound to the caller's own operator identity. Returns 404 if the run is not live; 409 if the channel has no browser-audio leg.
* **Request body:** `operator_id` (uuid).
* **Response:** `token` (string), `identity` (string), `conference_sid` (string, optional), `connect_params` (object, optional - a map of string key-value pairs the console passes to the browser audio device to route the operator's media leg correctly; pass these through verbatim when connecting).
* **Audit logging.** Both switch-mode and access-token attempts are audit-logged with operator attribution on success and failure paths, recording the run, operator identity, and outcome. The audit emit is best-effort and never fails an already-executed action.
* **Error handling.** A malformed upstream response (missing required fields) returns 502 and is not recorded as a success in the audit trail - the response is validated before the success audit is emitted.

**What you need to do:**

* **No action required.** These are additive endpoints. The existing takeover and handback flows continue to work unchanged. Use switch-mode when the operator needs to toggle modes after the initial join, and access-token when the console needs to establish the operator's browser audio leg. If you are building a custom operator console, pass the `connect_params` from the access-token response through to the browser audio device connect call - without them the operator's audio leg may not attach correctly.

</details>

<details>

<summary>Platform API: Simulation Recovery and Metering Accuracy (July 2026)</summary>

#### Simulation Recovery and Metering Accuracy <a href="#simulation-recovery-and-metering-accuracy" id="simulation-recovery-and-metering-accuracy"></a>

Two follow-up reliability improvements to the simulation run lifecycle.

**What changed:**

* **Safer recovery finalization.** A recovery attempt no longer marks a run failed if another attempt has already resumed it. This prevents an active, recovered run from being overwritten by a stale failure decision.
* **Metered duration cap.** Billable duration for one simulation run is capped at the platform's per-run execution timeout. This prevents queue and recovery delays from being counted as compute time.

**What you need to do:**

* **No action required.** Simulation recovery is safer and metering is more accurate for runs that experienced queueing or recovery delays. No API surface changes.

</details>

<details>

<summary>Platform API: Run-Scoped Takeover and Handback Endpoints + Takeover Eligibility on Run Object (July 2026)</summary>

#### Run-Scoped Takeover and Handback <a href="#run-scoped-takeover-and-handback" id="run-scoped-takeover-and-handback"></a>

Operators can now take over and hand back a live run using the channel-neutral run identifier, and every run now exposes server-computed takeover eligibility so the console no longer needs to derive it client-side.

**What changed:**

* **New endpoint: `POST /v1/{workspace_id}/runs/{run_id}/takeover`.** Registers the caller's operator identity on a live run and (in `takeover` mode) suspends the agent so the human drives. `listen` mode monitors without driving. For voice runs, the response includes the conference and participant identifiers the console needs to attach browser audio. Requires Operator update permission (operator, admin, or owner role), bound to the caller's own operator identity (no impersonation). Returns 404 if the run is not live in the workspace; 409 if the channel does not support live takeover yet.
* **New endpoint: `POST /v1/{workspace_id}/runs/{run_id}/handback`.** Releases the caller's operator from a run they took over so the agent resumes. Same permission and identity requirements as takeover. Returns 404 if the run is not live; 409 if the channel does not support live takeover.
* **New computed field on the run object: `takeover`.** Every run now includes a `takeover` object with `eligible` (boolean), `mode_options` (list of valid modes for the channel), and `reason` (human-facing explanation when not eligible). Eligibility is derived server-side from the run's kind, channel, status, and transport handle. Voice runs that are live and have a transport handle are eligible with modes `listen` and `takeover`. Other channels, non-conversation runs, and terminal runs are not eligible. Because this is a computed field, it is always consistent and requires no console-side derivation.
* **Audit logging.** Both takeover and handback attempts are audit-logged with operator attribution on success and failure, recording the run, channel, operator identity, mode (for takeover), and outcome. The audit emit is best-effort and never fails an already-executed action.
* **Idempotent takeover.** The same operator re-joining a run they already took over is safe and returns the existing session.

**What you need to do:**

* **No action required.** These are additive endpoints and a new computed field. The existing voice-specific operator flows continue to work. If you derive takeover eligibility client-side, you can replace that logic with the `takeover` field on the run object.

</details>

<details>

<summary>Platform API: Promote Playground Session to Coverage Run (July 2026)</summary>

#### Promote Playground Session to Coverage Run <a href="#promote-playground-session-to-coverage-run" id="promote-playground-session-to-coverage-run"></a>

Interactive playground sessions start without a coverage run, so run-scoped operations like fork and score are unavailable. A new endpoint promotes a playground session into a coverage run, unblocking these operations.

**What changed:**

* **New endpoint: `POST /v1/{workspace_id}/simulations/sessions/{session_id}/promote`.** Promotes a run-less interactive session into a coverage run. The endpoint creates a new coverage run, binds the existing session to it, and returns the run identifier along with the session identifier and an `already_bound` flag. The session itself is not recreated - only the run binding is established.
* **Idempotent.** If the session is already bound to a coverage run, the existing run is returned with `already_bound: true`. No new run is created.
* **Response fields:** `run_id` (uuid - the coverage run the session is now bound to), `session_id` (string - unchanged), `already_bound` (boolean).
* **After promotion,** fork, score, and other run-scoped operations work on the session.
* **Requires write permission** (`Service.update` scope).

**What you need to do:**

* **No action required.** This is an additive endpoint. Existing sessions and runs are unaffected. Use this endpoint when you want to fork or score an interactive playground session that was created without a run.

</details>

<details>

<summary>Platform API: Live Built-in Tool Catalog Endpoint (July 2026)</summary>

#### Live Built-in Tool Catalog <a href="#live-built-in-tool-catalog" id="live-built-in-tool-catalog"></a>

A new endpoint exposes the platform's built-in tool catalog at runtime, so integrators and the Developer Console can discover which built-in tools are available without hardcoding a static list.

**What changed:**

* **New endpoint: `GET /v1/{workspace_id}/tools/catalog`.** Returns the live set of built-in tools recognized by the platform, including each tool's identifier, display name, and description. The catalog is read from the running platform rather than a static list, so it stays current as built-in tools are added or retired.
* **Read-only.** The endpoint is a simple GET that requires workspace-level read access. No write operations are exposed.

**What you need to do:**

* **No action required.** This is an additive, read-only endpoint. If you maintain a hardcoded list of built-in tools in your integration, you can replace it with a call to this endpoint to stay current automatically.

</details>

<details>

<summary>Platform API: Run-Scoped Operator Guidance Endpoint (July 2026)</summary>

#### Run-Scoped Operator Guidance <a href="#run-scoped-operator-guidance" id="run-scoped-operator-guidance"></a>

Operators can now send text guidance to a live run using the run's channel-neutral identifier, without needing a channel-specific identifier like a call SID.

**What changed:**

* **New endpoint: `POST /v1/{workspace_id}/runs/{run_id}/guidance`.** Sends text guidance to the agent handling a live run. The agent incorporates the guidance into its next response without the operator taking over. The endpoint resolves the run within the workspace and dispatches per channel.
* **Channel-neutral addressing.** The endpoint accepts a `run_id` rather than a channel-specific identifier. The platform translates the run identifier to the appropriate channel coordinates internally.
* **Operator identity enforcement.** The `operator_id` in the request body must match the authenticated caller's own operator identity. Impersonation is not allowed.
* **Request body.** `operator_id` (UUID, required) and `message` (string, 1-5000 characters, required).
* **Response.** Returns the delivery status and the `run_id` the guidance was sent to.
* **Audit logging.** Every guidance attempt - successful or failed - is audit-logged with operator attribution, the target run, channel, and a content fingerprint. The raw guidance text is never stored in the audit record.
* **Voice-only delivery.** Voice runs are supported. Non-voice live channels return **409 Conflict** because operator guidance is not supported on those channels.
* **Error cases.** Returns 404 if the run is not a live run in the workspace or the operator is not found. Returns 409 if the run's channel does not yet support live guidance. Returns 403 if the caller does not have the required permission.
* **Permission required.** Requires Operator update permission (operator, admin, or owner role).

**What you need to do:**

* **No action required.** This is an additive endpoint. The existing voice-specific guidance path continues to work during the migration. Use this endpoint to send guidance to live voice runs by their channel-neutral run identifier.

</details>

<details>

<summary>Platform API: Unknown sort_by Fields Now Rejected with 422 + Skill Sampling Parameters in Tester (July 2026)</summary>

#### Unknown sort\_by Fields Now Rejected with 422 <a href="#unknown-sort_by-fields-now-rejected-with-422" id="unknown-sort_by-fields-now-rejected-with-422"></a>

Paginated list endpoints that accept a `sort_by` query parameter now return **422 Unprocessable Entity** when the requested field is not in the allowed set. Previously, an unrecognized field silently fell back to default ID sorting, which could make a client sort control appear to work while actually having no effect.

The 422 response body includes the list of allowed field names so callers can self-correct.

**What changed:**

* **`sort_by` validation is now strict.** Any paginated endpoint that accepts `sort_by` rejects unknown field names with a 422 instead of silently falling back to ID sort. This matches the validation behavior already used by other endpoints (such as integration runs) that reject unknown fields.
* **Error response includes allowed fields.** The 422 detail message lists the valid field names for the endpoint, so callers can update their requests without consulting documentation.

**What you need to do:**

* **Check any callers that pass `sort_by` values.** If a caller was sending an unsupported field and relying on the silent fallback to ID sorting, it will now receive a 422. Update the `sort_by` value to one of the allowed fields listed in the error response.

***

#### Skill Sampling Parameters Threaded Through Tester <a href="#skill-sampling-parameters-threaded-through-tester" id="skill-sampling-parameters-threaded-through-tester"></a>

Skill definitions now support optional `temperature` and `top_p` sampling parameters (both 0-1), and the skill tester in the Developer Console threads these parameters through to test executions so that a console "Test" run matches production behavior.

**What changed:**

* **New fields on skill definitions.** `temperature` (number, nullable) and `top_p` (number, nullable) can be set on a skill definition to control response randomness and diversity. Both are model-gated - models that reject sampling parameters ignore them silently.
* **Tester honors sampling parameters.** When you run a skill test from the Developer Console, the tester now passes the skill's configured `temperature` and `top_p` to the execution engine, so test results reflect the same sampling behavior as production runs.
* **At most one parameter.** If both `temperature` and `top_p` are set, the runtime keeps `temperature` and drops `top_p`.

**What you need to do:**

* **No action required for the tester change.** Skill tests will automatically use the configured sampling parameters.
* **Optionally configure sampling parameters.** If you want to control response randomness for a skill, set `temperature` or `top_p` on the skill definition through the API or Developer Console.

</details>

<details>

<summary>Platform API: Simulation Run Failure Reasons and Suite Completeness (July 2026)</summary>

#### Simulation Run Failure Reasons and Suite Completeness <a href="#simulation-run-failure-reasons-and-suite-completeness" id="simulation-run-failure-reasons-and-suite-completeness"></a>

Simulation runs now persist a short failure reason when they fail, and suite run summaries now report the expected case count so consumers can detect cases that failed to start.

**What changed:**

* **Failure reason on run responses.** Simulation run responses (`SimulationRunResponse` and per-run benchmark summaries) now include an `error` field containing a short failure reason when the run's status is `failed` - for example, "All 3 scenarios failed" or "bridge batch run failed". The field is null for successful runs and for legacy failed runs that predate this release. Max length is 500 characters.
* **Expected case count on suite run summaries.** Suite run summary responses now include an `expected_case_count` field that records how many cases were selected at launch time (after any max-cases cap). This count is frozen at launch because suite definitions are mutable and selection may be capped. A gap between `expected_case_count` and `total_runs` means some cases failed to start - they leave no run record and are otherwise invisible in the aggregate. The field is null on legacy suite runs that predate this release.
* **No breaking changes.** Both fields are nullable additions to existing response shapes. Existing integrations are unaffected.

**What you need to do:**

* **No action required.** These are additive fields. If you display simulation run details or suite run summaries, you can optionally surface the new `error` and `expected_case_count` fields to give users better visibility into why runs failed and whether all cases in a suite started successfully.

</details>

<details>

<summary>Platform API: Drain-Aware Simulation Executor with Instant Re-Drive Handoff (July 2026)</summary>

#### Drain-Aware Simulation Executor with Instant Re-Drive Handoff <a href="#drain-aware-simulation-executor-with-instant-re-drive-handoff" id="drain-aware-simulation-executor-with-instant-re-drive-handoff"></a>

Simulation batch launches are now drain-aware during graceful shutdowns (rolling deployments). A retiring platform instance stops accepting new batch launches and hands off in-flight runs to a healthy instance with minimal delay, so long-running suites are not interrupted by routine deployments.

**What changed:**

* **New batch launches rejected on retiring instances.** During a graceful shutdown, the platform instance stops accepting new simulation batch launches (bridge, benchmark, suite, and single-case runs) and returns a retryable 503 with a Retry-After header. The client's automatic retry lands on a healthy instance and the batch runs uninterrupted. Interactive session endpoints remain available through the shutdown grace window.
* **Instant heartbeat expiry for in-flight runs.** When a retiring instance shuts down, it immediately expires the heartbeats of any runs still owned by its in-flight batches. The recovery sweep on a healthy instance detects these runs on its very next pass and reclaims them, rather than waiting out the full staleness window. This reduces the handoff gap from minutes to seconds.
* **Heartbeat expiry is best-effort.** If the platform cannot reach the data store during shutdown (for example, if the connection is already closed), the expiry is skipped and the staleness window remains the fallback - the same behavior as an ungraceful exit.
* **No change to the run contract.** Run status values, API endpoints, and client-facing behavior are unchanged. The improvement is transparent to callers.

**What you need to do:**

* **No action required.** This is a reliability improvement. Simulation runs and suite runs that span a rolling deployment will recover faster. If your client already retries on 503 responses, new batch launches will land on a healthy instance automatically.

</details>

<details>

<summary>Platform API: Simulation Run Re-Drive and Automatic Recovery (July 2026)</summary>

#### Simulation Run Re-Drive and Automatic Recovery <a href="#simulation-run-re-drive-and-automatic-recovery" id="simulation-run-re-drive-and-automatic-recovery"></a>

Simulation suite runs orphaned by a process exit (rolling deployment, unexpected restart) are now automatically re-executed in place so long suites resume without manual re-triggering, with a bounded attempt cap and a terminal backstop for unrecoverable runs.

**What changed:**

* **Automatic re-execution of orphaned runs.** The platform now detects simulation runs orphaned mid-suite and re-executes them from a clean slate on a healthy replica. Each run's execution state is checkpointed at batch start, and a periodic heartbeat marks the run alive while it executes. When the heartbeat goes stale (the owning process died), a recovery sweep reclaims the run and re-executes it. This replaces the previous behavior of immediately failing orphaned runs, so large regression suites recover automatically instead of requiring manual re-triggering.
* **Bounded re-drive attempts.** Each run tracks how many times it has been re-driven. If a run exceeds the attempt cap (for example, because the scenario consistently causes a failure), it is failed terminally so the suite reaches a reportable state rather than re-driving forever.
* **Multi-replica safety.** The recovery claim uses a compare-and-swap so exactly one replica wins a given run. An attempt fence ensures that a superseded executor (one whose run was re-claimed by another replica) cannot finalize a run it no longer owns - it cannot write a terminal status or emit metering for a run another replica took over.
* **Active-run liveness tracking.** Recovery now uses execution liveness rather than record age when deciding whether a long-running suite has stalled. This prevents queued or active work from being failed only because its record was created earlier.
* **Suite run completion.** When orphaned runs are recovered or failed terminally, their parent suite runs reach a terminal state. Previously, a single orphaned run could cause an entire suite to appear stuck indefinitely.
* **Graceful interruption recovery.** Interrupted in-flight runs remain eligible for retry instead of being failed immediately. Runs that cannot be recovered eventually reach a terminal failure after the configured retry policy is exhausted.

**What you need to do:**

* **No action required.** This is a reliability improvement. Simulation runs and suite runs that previously required manual re-triggering after a platform restart will now recover automatically. There are no API changes - the run status values and endpoints are unchanged.

</details>

<details>

<summary>Platform API: Orphaned Simulation Run Reclaim and Graceful Cancellation (July 2026)</summary>

#### Orphaned Simulation Run Reclaim and Graceful Cancellation <a href="#orphaned-simulation-run-reclaim-and-graceful-cancellation" id="orphaned-simulation-run-reclaim-and-graceful-cancellation"></a>

Simulation runs that are orphaned by an ungraceful process exit are now automatically reclaimed and failed, and in-flight runs that are cancelled are now transitioned to a failed state instead of remaining stuck as running.

**What changed:**

* **Orphaned run reclaim.** The platform now periodically detects simulation runs stuck in a running state well past the expected completion window and marks them as failed. This covers the case where the process driving a run exits (for example, during a rolling deployment or unexpected restart) before it can record the run's result. The reclaim is idempotent and workspace-isolated - concurrent platform replicas cannot double-fail a run or clobber a run that completed normally between detection and the status update.
* **Suite run completion.** When orphaned runs are reclaimed, their parent suite runs also reach a terminal state. Previously, a single orphaned run could cause an entire suite run to appear stuck indefinitely.
* **Graceful cancellation handling.** When in-flight simulation runs are cancelled (for example, during a graceful platform shutdown), each run is now transitioned to a failed state before the cancellation completes. Previously, cancelled runs could remain in a running state with no process to finalize them.
* **Idempotent completion guard.** The run completion path now supports an idempotent guard that prevents a run already in a terminal state from being overwritten. This ensures that a run which completed normally is never clobbered back to failed by a concurrent reclaim or cancellation.

**What you need to do:**

* **No action required.** This is a reliability improvement. Simulation runs and suite runs that previously appeared stuck after a platform restart will now reach a terminal state automatically. There are no API changes - the run status values and endpoints are unchanged.

</details>

<details>

<summary>Platform API: Dynamic Behaviors Feature Removed (July 2026)</summary>

#### Dynamic Behaviors Feature Removed <a href="#dynamic-behaviors-feature-removed" id="dynamic-behaviors-feature-removed"></a>

The dynamic behaviors feature has been removed from the Platform API. The endpoints, data model, and underlying storage have been deleted.

**What changed:**

* **Endpoints removed.** The five dynamic behaviors endpoints (create, list, get, replace, delete) previously available under the service scope have been removed. Requests to these endpoints will return 404.
* **Data removed.** Existing dynamic behavior records, including triggers and action configurations, have been dropped. This data is no longer accessible.
* **Breaking change.** This is a breaking removal. Any integrations or automation that referenced the dynamic behaviors endpoints will need to be updated.

**What you need to do:**

* **Remove any references to dynamic behaviors endpoints.** If your integration creates, lists, or manages dynamic behaviors through the API, remove those calls. The endpoints no longer exist.
* **Update any SDK or CLI workflows.** If you used Agent Forge or SDK methods to sync dynamic behaviors, remove those steps from your workflows.

</details>

<details>

<summary>Platform API: Billable Token Metering on Raw Real-Time Voice Runtime (July 2026)</summary>

#### Billable Token Metering on Raw Real-Time Voice Runtime <a href="#billable-token-metering-on-raw-real-time-voice-runtime" id="billable-token-metering-on-raw-real-time-voice-runtime"></a>

The raw real-time speech-to-speech voice runtime now emits billable per-response token-usage events, bringing it to parity with the Atlas voice runtime. Calls on the raw real-time runtime now appear in standard usage reporting alongside Atlas and in-house pipeline calls.

**What changed:**

* **Per-response billing events.** Every model response during a raw real-time voice call now emits a billable token-usage event covering input and output tokens. Previously, the raw real-time runtime emitted observability metrics for token counts but did not produce billing events, so calls on this runtime were invisible in usage reporting.
* **Fire-and-forget billing.** The billing emit is non-blocking and never crashes or stalls the audio path. If the billing sink is unavailable or not yet configured for an environment, the emit is a no-op and the call proceeds normally. Transient billing failures are surfaced through observability metrics rather than affecting the call.
* **Parity with Atlas runtime.** Both session-owning voice runtimes (Atlas and raw real-time) now emit the same billable usage event shape per model response, so usage reporting is consistent regardless of which runtime handles a call.

**What you need to do:**

* **No action required.** This is a billing-accuracy improvement. If you use the real-time speech-to-speech voice family, calls on the raw runtime will now appear in your workspace's usage reporting automatically. There are no API changes or configuration changes.

</details>

<details>

<summary>Platform API: Reasoning Effort Pinned for Realtime Reasoning Models (July 2026)</summary>

#### Reasoning Effort Pinned for Realtime Reasoning Models <a href="#reasoning-effort-pinned-for-realtime-reasoning-models" id="reasoning-effort-pinned-for-realtime-reasoning-models"></a>

The voice pipeline now automatically pins reasoning effort to its lowest setting when using a reasoning-capable realtime model, fixing a regression where the model would speak its internal reasoning preamble instead of proceeding to tool calls.

**What changed:**

* **Automatic reasoning effort configuration.** When a voice service is configured with a reasoning-capable realtime model, both the Atlas voice provider and the raw realtime voice provider now set the reasoning effort to the lowest level in the session configuration. This follows the model provider's migration guidance, which requires explicitly setting reasoning effort rather than relying on the default. At the default effort level, the model would narrate a reasoning preamble (e.g., "let me check that for you...") and then fail to execute the intended tool call - the root cause of the zero-tool-call regression reported on the latest reasoning realtime model.
* **Model-gated behavior.** The reasoning effort setting is applied only for reasoning-capable realtime models. Non-reasoning realtime models are unaffected, as they do not support the reasoning effort parameter and would reject it.

**What you need to do:**

* **No action required.** This fix is applied automatically for all voice services using reasoning-capable realtime models. If you were experiencing a regression where the model spoke its thought process instead of calling tools, this resolves it.

</details>

<details>

<summary>Platform API: Full-Duplex Voice Family Is Generally Available (July 2026)</summary>

#### Full-Duplex Voice Family Is Generally Available <a href="#full-duplex-voice-family-is-generally-available" id="full-duplex-voice-family-is-generally-available"></a>

The full-duplex voice model family is now generally available and can be selected through service or agent voice configuration.

**What changed:**

* **Full-duplex voice family is now GA.** The full-duplex voice model family is now generally available. Services and agents configured to use the full-duplex provider will connect to the full-duplex session endpoint. The full-duplex family is selectable via service or agent voice configuration and is never the default.
* **Fail-safe unchanged.** If the upstream full-duplex session endpoint is unavailable, the call fails at session connect and the standard fail-safe routes the call to the in-house pipeline, consistent with existing behavior for all voice families.

**What you need to do:**

* **No action required.** Existing configurations continue to work. Select the full-duplex family only for services where you want that speech-to-speech runtime.

</details>

<details>

<summary>Platform API: Guidance Delivery Status Honesty, Transcript Read-Audit, and Blueprint GA Status (July 2026)</summary>

#### Guidance Delivery Status Honesty, Transcript Read-Audit, and Blueprint GA Status <a href="#guidance-delivery-status-honesty-transcript-read-audit-and-blueprint-ga-status" id="guidance-delivery-status-honesty-transcript-read-audit-and-blueprint-ga-status"></a>

Operator whisper guidance now reports the actual delivery outcome instead of falling back to a default, conversation transcript reads are now audit-logged as PHI access events, and the enterprise UX blueprint has moved to GA status.

**What changed:**

* **Honest guidance delivery status.** The send-guidance response now returns the real delivery outcome from the voice session engine. The `status` field on the response is expanded from `delivered | failed` to `delivered | queued_no_subscriber | deduplicated | failed | unknown`. Previously, unrecognized or missing statuses were silently mapped to a default value, which could mask delivery issues. The platform now reports `unknown` when it cannot determine the outcome rather than claiming success or a queue state.
  * `delivered` - the guidance was delivered to a live subscriber.
  * `queued_no_subscriber` - the session ended between the eligibility check and publish; the message was queued but no subscriber consumed it.
  * `deduplicated` - the message matched a recent guidance within the idempotency window and was not re-delivered.
  * `failed` - delivery failed.
  * `unknown` - the platform could not determine the delivery outcome (the upstream did not return a recognized status).
* **Conversation transcript reads are now audit-logged.** Retrieving a conversation transcript detail (the single-conversation endpoint that returns full message bodies) now emits a PHI read-audit event. This closes the gap where transcript access was not captured in the audit stream. List endpoints (conversation lists, run lists, entity lists) are deliberately not read-audited - they are polled on an interval and would flood the audit stream without expressing meaningful human access.
* **Enterprise UX blueprint moved to GA.** The developer console enterprise UX blueprint has been promoted from preview to generally available status.

**What you need to do:**

* **Update any client code that pattern-matches on the `status` field of the send-guidance response.** The field can now return `queued_no_subscriber`, `deduplicated`, or `unknown` in addition to the previous `delivered` and `failed` values. Clients that treat any non-`delivered` status as an error will continue to work correctly; clients that switch on exact values should add cases for the new values.
* **No action required for transcript read-audit.** The audit event is emitted automatically. If you consume audit events, you can filter for `conversation.transcript_read` to trace transcript access.
* **No action required for blueprint GA.** This is an access-broadening change.

</details>

<details>

<summary>Platform API: Durable Audit Event for Operator Whisper Guidance + Workspace Authorization on Operator Actions (July 2026)</summary>

#### Durable Audit Event for Operator Whisper Guidance <a href="#durable-audit-event-for-operator-whisper-guidance" id="durable-audit-event-for-operator-whisper-guidance"></a>

Sending whisper guidance to an agent during a live call now produces a durable audit event attributed to the operator, and operator action endpoints enforce workspace-level authorization on the credential.

**What changed:**

* **`operator.guidance_sent` audit event.** The send-guidance endpoint now emits a durable `operator.guidance_sent` audit event after successful delivery. The event records the operator identity, call reference, message length, a SHA-256 hash of the guidance text, and the delivery status. The raw guidance text is deliberately excluded - conversational content stays out of audit events, consistent with the platform's data handling posture for transcripts. This event joins the existing durable operator lifecycle events (join, mode change, wrap-up) so every action that steers what the AI says to a live caller is traceable.
* **Workspace authorization on operator actions.** Operator action endpoints (join, mode change, leave, access token, and guidance) now verify that the credential's workspace matches the workspace specified in the request body. A valid credential from one workspace can no longer act on another workspace's live calls. This is a defense-in-depth measure - these routes were already network-isolated, but the workspace check closes the gap regardless of network posture.

**What you need to do:**

* **No action required.** The audit event is emitted automatically. If you consume audit events, you can filter for `operator.guidance_sent` to trace whisper guidance actions.
* **No action required for workspace authorization.** If your operator credentials already belong to the same workspace as the calls they act on (the expected configuration), this change has no effect. Requests using a credential from a different workspace than the target call will now receive a 403 response.

</details>

<details>

<summary>Platform API: Surface Responses Expose Review and Archive Provenance (July 2026)</summary>

#### Surface Responses Expose Review and Archive Provenance <a href="#surface-responses-expose-review-and-archive-provenance" id="surface-responses-expose-review-and-archive-provenance"></a>

Surface responses now include review and archive provenance fields, so operators can distinguish a rejected surface from a naturally-expired one and access the reviewer's typed reject reason.

**What changed:**

* **New `review_notes` field on surface responses.** A string (or null) containing the notes entered by the reviewer when approving or rejecting the surface.
* **New `reviewed_by` field on surface responses.** A UUID string (or null) identifying the credential that approved or rejected the surface.
* **New `reviewed_at` field on surface responses.** An ISO 8601 timestamp (or null) recording when the surface was reviewed.
* **New `archived_at` field on surface responses.** An ISO 8601 timestamp (or null) recording when the surface was archived.
* **New `archive_reason` field on surface responses.** A string (or null) containing the reason provided when the surface was archived.

These fields are returned on all surface read endpoints (get, list). All five fields are null when no review or archive action has been taken.

**What you need to do:**

* **No action required.** These are additive, nullable fields on existing surface responses. Existing clients that do not read these fields are unaffected.
* **To display review provenance**, read `review_notes`, `reviewed_by`, and `reviewed_at` from surface responses. The `reviewed_by` value is the raw credential identifier recorded by the approve or reject action.
* **To display archive provenance**, read `archived_at` and `archive_reason` from surface responses.

</details>


---

# 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 dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.concurrence.com/api-reference/change-logs/amigo-api/amigo-api-history-05.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
