> 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-04.md).

# API History: Archive 4

Retained API history, archive 4, 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>Scribe API: Session Lifecycle Writes, Mode, and Idempotency (July 2026)</summary>

#### Session Lifecycle Writes, Mode, and Idempotency <a href="#session-lifecycle-writes-mode-and-idempotency" id="session-lifecycle-writes-mode-and-idempotency"></a>

Scribe sessions now carry a mode, support explicit end and cancel transitions through REST, and enforce stronger idempotency and conflict rules on create.

**What changed:**

* **Session mode.** Every session now has a `mode` field indicating the session modality: `in_person` (default) or `zoom`. The mode is set at creation and included in all session responses (list, detail, appointment nesting). It can also be changed via PATCH.
* **Create session - enhanced idempotency.** `POST /{workspace_id}/sessions` accepts an optional `mode` field (defaults to `in_person`). When a create request reuses an `external_id` that already exists for the same provider, the request fingerprint (mode, external appointment identifier, and metadata) is compared against the stored session. A matching fingerprint returns the existing session (idempotent success). A divergent fingerprint returns `409` with error code `idempotency_key_conflict` instead of silently returning a mismatched session.
* **Active Zoom guard.** A practitioner may have at most one non-terminal Zoom session at a time within a workspace. Attempting to create or update a second active Zoom session returns `409` with error code `active_zoom_session_exists`.
* **Update session.** `PATCH /{workspace_id}/sessions/{session_id}` updates mutable fields on an owned session. Supported fields are `external_appointment_id` (nullable - sending `null` clears the link), `mode`, and `metadata`. Only fields present in the request body are written. A mode change to `zoom` is subject to the active Zoom guard. Returns the full refreshed session.
* **End session.** `POST /{workspace_id}/sessions/{session_id}/end` transitions a `created` or `in-progress` session to `in-review`. If a streaming worker is still attached, the endpoint returns `409` with error code `session_streaming` instead of racing the worker. Sessions already in a terminal or non-endable state return `409` with error code `invalid_session_state`.
* **Cancel session.** `POST /{workspace_id}/sessions/{session_id}/cancel` transitions any non-terminal session to `cancelled`. This is the compensation path for orphaned sessions and user aborts. A session that is already terminal returns `409` with error code `invalid_session_state`.
* **Stable error codes.** Conflict responses from session lifecycle writes carry a machine-readable `code` in the error envelope so callers can distinguish conflict kinds without parsing free-text messages. Codes include `idempotency_key_conflict`, `active_zoom_session_exists`, `session_streaming`, and `invalid_session_state`.
* **Appointment session ordering.** When multiple sessions reference the same appointment, the appointment response now prefers the most recent non-cancelled session rather than the most recent session overall. A cancelled orphan no longer shadows a still-valid older session.

**What you need to do:**

* **Handle the `mode` field.** Session responses now include `mode`. Update integrations that deserialize session objects to accept this field.
* **Include `mode` on create when starting a Zoom session.** Pass `mode: "zoom"` in the create request body. Omitting the field defaults to `in_person`.
* **Handle new 409 error codes.** If your integration creates sessions with an `external_id`, add handling for `idempotency_key_conflict` (retry with consistent parameters or generate a new key) and `active_zoom_session_exists` (resolve the existing Zoom session before starting a new one).
* **Use the new lifecycle endpoints.** Call `POST .../end` to close a session through REST when no streaming worker is attached. Call `POST .../cancel` to abort or compensate an orphaned session. Both return the updated session on success.
* **Use PATCH to update session fields.** Call `PATCH /{workspace_id}/sessions/{session_id}` to modify `external_appointment_id`, `mode`, or `metadata` after creation.

</details>

<details>

<summary>Scribe API: Nested Session Object on Appointments (July 2026)</summary>

#### Nested Session Object on Appointments <a href="#nested-session-object-on-appointments" id="nested-session-object-on-appointments"></a>

The appointment response now returns a nested session object instead of a plain session identifier, giving clients enough context to render visit state directly from the appointments list.

**What changed:**

* **`session` replaces `session_id`.** Each appointment in `GET /{workspace_id}/appointments` and `GET /{workspace_id}/appointments/{appointment_id}` now returns a `session` object instead of a `session_id` string. The object includes the session identifier, status, lifecycle timestamps (started, ended, created, updated), and the external appointment identifier. When no session exists for an appointment, the field is `null`.
* **No second lookup required.** Clients can determine whether a visit is in progress, in review, or completed without calling the sessions endpoint. The nested object is a focused subset of the full session resource - it intentionally excludes artifact availability, which remains on the individual session detail endpoint.
* **Most-recent session wins.** When multiple sessions reference the same appointment, the response includes only the most recent session, determined by creation time with deterministic tie-breaking.

**What you need to do:**

* **Update integrations that read `session_id`.** Replace references to the `session_id` field with the `session` object. The session identifier is now at `session.id`. Status and timestamps are available at `session.status`, `session.started_at`, `session.ended_at`, `session.created_at`, and `session.updated_at`.
* **Remove follow-up session lookups where possible.** If your integration previously fetched the session detail solely for status or timing information, the nested object now provides that data inline.

</details>

<details>

<summary>Scribe API: Appointments Endpoint - Current-Day Listing and Retrieval (July 2026)</summary>

#### Appointments Endpoint - Current-Day Listing and Retrieval <a href="#appointments-endpoint-current-day-listing-and-retrieval" id="appointments-endpoint-current-day-listing-and-retrieval"></a>

Providers can now list and retrieve appointments for the current calendar day through dedicated Scribe API endpoints.

**What changed:**

* **List appointments.** `GET /{workspace_id}/appointments` returns a paginated list of appointments for the current calendar day, scoped to the authenticated provider and workspace. Pagination follows the same `limit` and `continuation_token` pattern used by the sessions endpoint.
* **Get a single appointment.** `GET /{workspace_id}/appointments/{appointment_id}` retrieves a single appointment by its identifier. Returns 404 if the appointment is not in the current day's data.
* **Session linking.** Each appointment includes a `session_id` field that is automatically populated when a session has been recorded against that appointment. The link is resolved by matching the appointment's external identifier against existing sessions, scoped to the authenticated provider and workspace. Appointments with no matching session return `session_id` as null.
* **Stable response contract.** The response shape is designed as the long-term contract. The current implementation returns a deterministic seed of appointments for the current day. A future release will source appointments from a downstream customer API through a managed External Integration with no change to the wire format.
* **Appointment fields.** Each appointment includes start and end times, duration, reason, appointment type, patient name and entity ID, practitioner name and entity ID, and location name.

**What you need to do:**

* **To list current-day appointments:** call `GET /{workspace_id}/appointments` with valid provider credentials. Use `limit` and `continuation_token` query parameters for pagination.
* **To retrieve a specific appointment:** call `GET /{workspace_id}/appointments/{appointment_id}`.
* **No action required for existing integrations.** These are new additive endpoints. Existing session and recording workflows are unaffected.

</details>

<details>

<summary>Platform API: Admin Provider Access Grant Management for Scribe (July 2026)</summary>

#### Admin Provider Access Grant Management for Scribe <a href="#admin-provider-access-grant-management-for-scribe" id="admin-provider-access-grant-management-for-scribe"></a>

Workspace administrators can now provision, list, inspect, and revoke provider Scribe access grants through dedicated admin endpoints, replacing manual provisioning workflows.

**What changed:**

* **Admin grant endpoints.** A new set of endpoints under `/admin/scribe/grant` lets workspace administrators manage provider access grants through the API. These are the public, workspace-admin-gated counterparts to the existing internal provisioning path.
* **Create a grant.** `POST /admin/scribe/grant` provisions a provider for Scribe access by email. Supplying a known provider entity ID creates the grant as `active` (the admin vouches for the identity). Omitting the entity ID creates a `pending_entity` grant that becomes active once the provider's entity is bound through the login or verification flow. Duplicate active grants for the same workspace and email return 409.
* **List grants.** `GET /admin/scribe/grant` returns a paginated list of a workspace's grants with optional status filtering. Results are ordered newest first with stable page ordering.
* **Get a grant.** `GET /admin/scribe/grant/{grant_id}` retrieves a single grant by ID.
* **Revoke a grant.** `POST /admin/scribe/grant/{grant_id}/revoke` soft-deletes the grant, immediately blocking new human provider logins and new machine-to-machine act-as-by-email token mints. Active sessions and refresh tokens bound to the grant are revoked on the spot. The email can be re-invited afterward, creating a fresh grant.
* **Role-based scopes.** Each grant carries a role (`provider` or `scribe_admin`) that determines the scopes the provider receives.
* **Authorization.** All endpoints require `identity:admin` for the target workspace or global `platform:admin`. Provisioning access is gated on admin authority, not on the Scribe capabilities the grant confers.
* **Backward compatible.** Existing internal grant provisioning and provider login flows are unaffected.

**What you need to do:**

* **No action required for existing integrations.** Internal provisioning paths continue to work.
* **To manage grants via API:** use the new `/admin/scribe/grant` endpoints. Ensure the calling identity has `identity:admin` scope for the target workspace.

</details>

<details>

<summary>Platform API: Source Provenance and PHI-Gated Filenames in the Analytical Catalog (July 2026)</summary>

#### Source Provenance and PHI-Gated Filenames in the Analytical Catalog <a href="#source-provenance-and-phi-gated-filenames-in-the-analytical-catalog" id="source-provenance-and-phi-gated-filenames-in-the-analytical-catalog"></a>

The intake pipeline now records source provenance for ingested files in the analytical catalog, and filenames can optionally flow into the catalog for datasets attested as filename-safe.

**What changed:**

* **Source provenance in the analytical catalog.** Every file ingested through a cloud-drive connector now records an opaque source file identifier and a derived source URL in the analytical catalog row. These identifiers contain no operator or patient text and are always populated when a source connector is involved.
* **PHI-gated filename and folder path.** Filenames and source folder paths are treated as PHI by default and remain only on the access-controlled operational record. Connector folder mappings now support a PHI attestation that marks a dataset's filenames as non-PHI. When a dataset carries this attestation, the platform propagates the filename and folder path into the analytical catalog so downstream workflows can cite the source by name.
* **Per-dataset attestation.** The attestation is configured per folder mapping. The default posture (PHI assumed) applies to any mapping without an explicit opt-in. Operators who enable the attestation accept responsibility for ensuring all files in the dataset have non-PHI filenames.
* **Backward compatible.** Existing datasets and integrations are unaffected. The new provenance columns are nullable - rows written before this change and rows without a source connector continue to work without modification.

**What you need to do:**

* **No action required for existing integrations.** All current behavior is preserved. Source provenance columns populate automatically for new ingestions through cloud-drive connectors.
* **To enable filename propagation:** update the connector folder mapping for the target dataset to attest that filenames do not contain PHI. Once attested, new ingestions will include the filename and folder path in the analytical catalog.

</details>

<details>

<summary>Platform API: Provider-M2M Act-As-by-Email Delegation (July 2026)</summary>

#### Provider-M2M Act-As-by-Email Delegation <a href="#provider-m2m-act-as-by-email-delegation" id="provider-m2m-act-as-by-email-delegation"></a>

Provider machine-to-machine clients can now mint provider tokens on behalf of a specific clinician in their workspace by supplying the clinician's email address at token request time.

**What changed:**

* **Act-as by email.** The `client_credentials` token request now accepts an optional `provider_email` form parameter. When supplied, the platform resolves the email to a clinician with an active access grant in the credential's workspace and mints the provider token with that clinician as the subject. The token carries the resolved clinician's identity while the credential remains the machine-to-machine client, preserving audit traceability.
* **Workspace-scoped resolution.** The email lookup is scoped to the credential's own workspace. A clinician who exists only in a different workspace is not resolvable, preventing cross-workspace delegation.
* **Active grant required.** Only clinicians with an active provider access grant in the workspace can be targeted. Clinicians whose grant is pending, unverified, or otherwise inactive produce a generic error with no state disclosure.
* **No entity-ID delegation.** The `provider_entity_id` form parameter is reserved and always rejected. Email is the only supported delegation identifier. Supplying both `provider_email` and `provider_entity_id` returns an `invalid_request` error.
* **Backward compatible.** Omitting both parameters preserves the existing bound-entity mint behavior. Existing integrations are unaffected.
* **Audit trail.** Delegated token mints record both the acting credential's bound entity and the resolved target entity, along with a delegation flag, so audit queries can distinguish self-minted tokens from delegated ones.

**What you need to do:**

* **No action required for existing integrations.** Token requests without the new parameter continue to work as before.
* **To use act-as delegation:** pass the clinician's email address as `provider_email` in the `client_credentials` token request. Ensure the clinician has an active provider access grant in the workspace. The returned token's subject will be the resolved clinician.

</details>

<details>

<summary>Platform API: External Auth Claims Mapping for Customer-Attested Authorization (July 2026)</summary>

#### External Auth Claims Mapping for Customer-Attested Authorization <a href="#external-auth-claims-mapping-for-customer-attested-authorization" id="external-auth-claims-mapping-for-customer-attested-authorization"></a>

External-user sessions can now carry customer-attested authorization claims that the platform resolves into internal roles and grants on every turn. This enables fine-grained, per-session access control without requiring the customer to create or manage entities in the platform.

**What changed:**

* **Customer-attested auth claims on session creation.** The external-user session token grant now accepts an optional `auth_claims` field - a JSON array of normalized claim atoms, each with a `namespace`, `key`, and `value`. Claims are validated, deduplicated, and stored immutably on the session. Omitting the field produces an empty claim set (backward compatible). Claims are accepted only on the external-user session grant type; including them on any other grant type returns a 400 error.
* **Claim-to-role mapping CRUD.** A new set of endpoints under `/v1/{workspace_id}/external-auth-claim-mappings` lets workspace administrators create, list, get, and supersede mappings from exact claim tuples to internal external roles. Each mapping links one `(namespace, key, value)` tuple to one external role. Mappings are immutable - semantic changes supersede the prior mapping and create a new active row, preserving the full authorization history for audit. Only one active mapping per claim tuple is allowed (409 on conflict). Creating a mapping that references a role not in the workspace returns 422.
* **Resolve preview endpoint.** A read-only `POST .../resolve-preview` endpoint accepts a list of claim atoms and returns the mapped roles, effective grants, and count of unmapped claims. This lets administrators test their mapping configuration without creating a session.
* **Per-turn authorization resolution.** On every external-user text turn, the platform resolves the session's stored claims against active mappings, deduplicates the resulting roles, unions their active grants, and passes the resolved authorization context to the agent engine. A revoked or absent session fails the turn with 403. A resolution error fails the turn with 503. An empty claim set or zero mapped roles produces an empty authorization context (the turn proceeds but the session sees nothing that requires authorization).
* **Cached resolution with workspace-scoped invalidation.** The per-session resolution is cached and automatically invalidated when any mapping, role, or grant in the workspace changes. A short time-to-live bounds staleness if an invalidation signal is missed.
* **Claim contract.** Claims use a bounded canonical identifier pattern for namespace and key (lowercase alphanumeric with dots, colons, hyphens, and underscores; up to 64 characters). Values are preserved exactly as attested (up to 256 characters). A maximum of 32 claims per session is enforced. Matching is exact, case-sensitive tuple equality only - no wildcards, prefixes, regex, or aliasing.

**What you need to do:**

* **No action required for existing integrations.** External-user sessions without `auth_claims` continue to work as before with an empty claim set.
* **To adopt claims-based authorization:** define external roles, create claim mappings linking your claim tuples to those roles, assign grants to the roles, then pass `auth_claims` when minting external-user session tokens. Use the resolve-preview endpoint to verify your mapping configuration before going live.

</details>

<details>

<summary>Platform API: Shared LLM Client Lifecycle for Production Evals and Insights (July 2026)</summary>

#### Shared LLM Client Lifecycle for Production Evals and Insights <a href="#shared-llm-client-lifecycle-for-production-evals-and-insights" id="shared-llm-client-lifecycle-for-production-evals-and-insights"></a>

The LLM client used by production evaluations and the insights chat agent is now managed at the application lifecycle level rather than constructed per request.

**What changed:**

* **Single shared client for LLM-backed features.** Production evaluation scoring and the insights chat agent now share a single LLM client that is created when the platform starts and closed when it stops. Previously, each incoming request constructed its own client, which could leave network connections unclosed under sustained traffic.
* **Connection leak resolved.** Under high call volumes, the per-request client pattern accumulated idle network connections over time. The shared client reuses a single connection pool, eliminating the leak.
* **Bounded in-memory cache for enrichment lookups.** The internal cache used during enrichment resolution now prunes expired entries periodically so that a burst of distinct lookups cannot cause unbounded memory growth.
* **No API or behavioral change.** Production evaluation verdicts, insights chat responses, and all public API contracts remain the same. This is a reliability and resource-management improvement.

**What you need to do:**

* **No action required.** The improvement applies automatically. Workspaces that run high volumes of production evaluations or insights chat sessions benefit from reduced connection overhead.

</details>

<details>

<summary>Platform API: Approval Rejection Closes Approval State Cleanly (July 2026)</summary>

#### Approval Rejection Closes Approval State Cleanly <a href="#approval-rejection-closes-approval-state-cleanly" id="approval-rejection-closes-approval-state-cleanly"></a>

When a human reviewer declines a gated tool call, the agent now treats the approval slot as fully closed. Previously, the agent could occasionally behave as though a replacement approval request had already been queued, which could cause a subsequent retry to skip the gated tool call and leave the UI without an approval control to render.

**What changed:**

* **Approval state fully closed on rejection.** After a reviewer declines a gated action, the platform now explicitly signals to the agent that no approval request is pending and no replacement request has been created. This prevents the agent from incorrectly telling the user that a new request is queued or ready for approval.
* **Clearer rejection language.** The rejection signal now instructs the agent to avoid saying the action is still pending review, queued again, or ready for approval. A replacement request can only be created after the user explicitly asks to retry and a new gated tool call returns an awaiting-approval status.
* **No change to the approval or rejection API.** The reviewer-facing workflow and API surface remain the same. This change affects only the internal steering given to the agent after a rejection.

**What you need to do:**

* **No action required.** Agents that use gated tool calls benefit automatically. After a rejection, the agent reports the decline accurately on the current turn and does not falsely claim a replacement request exists.

</details>

<details>

<summary>Platform API: Voice Provider Prompt Aligned with Runtime Tool Capabilities (July 2026)</summary>

#### Voice Provider Prompt Aligned with Runtime Tool Capabilities <a href="#voice-provider-prompt-aligned-with-runtime-tool-capabilities" id="voice-provider-prompt-aligned-with-runtime-tool-capabilities"></a>

The system prompt sent to session-owning voice providers now describes only the tool capabilities actually available at runtime, rather than deriving tool instructions solely from the service's context graph definition.

**What changed:**

* **Runtime-aware tool contract.** The voice provider's system prompt now includes a tool-use section only when tools are actually wired and available for the call. Previously, tool instructions were generated based on whether the context graph declared tool bindings, which could produce a mismatch when engine setup or executor wiring prevented tools from being offered.
* **Sequential tool execution guidance.** The prompt now instructs the provider to call one tool at a time and wait for its result before calling another, matching the provider's configured execution mode. This prevents duplicate concurrent writes observed in live calls.
* **Write-tool confirmation guidance.** When write-capable tools are present, the prompt includes explicit instructions to confirm the action with the caller and get agreement before calling. Sessions without write tools omit this guidance.
* **Dedicated persona rendering.** The voice provider's persona instructions are now rendered specifically for the session-owning runtime, excluding references to asynchronous background tools, external events, and memory behaviors that do not apply to the provider's execution model. This prevents the provider from receiving a false execution contract.
* **No change to tool authorization.** The set of tools offered to the provider remains limited to those referenced in the service's context graph - the same authorization boundary as before.

**What you need to do:**

* **No action required.** This change improves prompt accuracy for existing voice services. Services using session-owning voice providers benefit automatically from more accurate tool instructions.

</details>

<details>

<summary>Platform API: Streaming Attach-Ticket Handshake Enforcement (July 2026)</summary>

#### Streaming Attach-Ticket Handshake Enforcement <a href="#streaming-attach-ticket-handshake-enforcement" id="streaming-attach-ticket-handshake-enforcement"></a>

The streaming session handshake now enforces audience, scope, and session binding on attach tickets, completing the split-trust security model for browser-to-worker connections.

**What changed:**

* **Audience enforcement at the handshake.** The streaming worker now validates that the token presented during the WebSocket handshake carries the dedicated streaming audience. A standard REST provider token is rejected at connection time - it can never open a streaming session.
* **Scope enforcement at the handshake.** The worker requires the `scribe:streams:connect` scope on the attach ticket. A token that carries only REST-oriented scopes (such as `scribe:sessions:write`) is rejected, even if it were otherwise valid.
* **Session binding enforcement.** The attach ticket's embedded `session_id` must match the session being connected to. A valid ticket for one session cannot be used to attach to a different session. This prevents a provider-level credential from being used to access sessions beyond the one the ticket was minted for.
* **Distinct rejection reasons.** The handshake returns specific close codes for audience/principal failures, missing scope, and session mismatch, making integration debugging straightforward.

**What you need to do:**

* **No changes required if you already use attach tickets.** Tickets minted through the `token_exchange` grant already carry the correct audience, scope, and session binding. This update enforces checks that were previously documented but not fully validated at the handshake.
* **Do not pass REST provider tokens to the streaming handshake.** The worker now actively rejects them. Use the `token_exchange` grant to obtain a purpose-built attach ticket for each streaming session.

</details>

<details>

<summary>Platform API: Token Exchange - Browser Attach Tickets for Streaming Sessions (July 2026)</summary>

#### Token Exchange - Browser Attach Tickets for Streaming Sessions <a href="#token-exchange-browser-attach-tickets-for-streaming-sessions" id="token-exchange-browser-attach-tickets-for-streaming-sessions"></a>

The token endpoint now supports an RFC 8693 token exchange grant that lets a backend service exchange a provider access token for a short-lived, session-bound browser attach ticket scoped exclusively to streaming.

**What changed:**

* **New grant type: `token_exchange`.** The existing token endpoint accepts `grant_type=token_exchange` following RFC 8693. The subject token must be a valid provider access token obtained through the `client_credentials` grant (provider M2M). The result is a short-lived attach ticket that a browser can present to the streaming worker to join a session.
* **Dedicated streaming audience.** The attach ticket is issued with a dedicated audience (`scribe-streaming`) that is distinct from the shared REST API audience. A ticket can never be used at a REST endpoint, and a REST provider token can never be presented as an attach ticket.
* **Single-purpose scope.** The ticket carries only the `scribe:streams:connect` scope - a new, non-REST scope that is not included in any standard role or provider scope set. It cannot satisfy any REST authorization check.
* **Session and provider binding.** Each ticket is bound to the caller-supplied `session_id` and inherits the `workspace_id` and `provider_entity_id` from the subject token. The streaming worker enforces these bindings at connection time.
* **5-minute TTL.** Attach tickets expire after five minutes. They are not refreshable - request a new exchange when a ticket expires.
* **Anti-escalation controls.** A ticket can never be exchanged for another ticket. The subject token must not already carry the streaming scope, and must hold session-write authority. Both audience separation and explicit scope checks enforce this.
* **Rate limiting.** Token exchange minting is subject to a per-provider sliding-window rate limit, layered on the existing per-IP rate limit. Exceeding the limit returns HTTP 429 with a `Retry-After` header.
* **Audit logging.** Successful and failed exchange attempts are recorded in the audit log, including the grant type, provider identity, session, and failure reason.

**Request parameters (form-encoded):**

| Parameter              | Required | Description                                                                                       |
| ---------------------- | -------- | ------------------------------------------------------------------------------------------------- |
| `grant_type`           | Yes      | Must be `token_exchange`                                                                          |
| `subject_token`        | Yes      | A valid provider access token (from `client_credentials` provider M2M)                            |
| `subject_token_type`   | No       | Defaults to `urn:ietf:params:oauth:token-type:access_token` if omitted; no other type is accepted |
| `requested_token_type` | No       | Defaults to `urn:ietf:params:oauth:token-type:access_token` if omitted; no other type is accepted |
| `audience`             | No       | Defaults to `scribe-streaming` if omitted; no other audience is accepted                          |
| `session_id`           | Yes      | UUID of the session the ticket is bound to                                                        |
| `scope`                | No       | Defaults to `scribe:streams:connect` if omitted; no other scope is accepted                       |

**Response:** Standard token response with `access_token`, `token_type`, `expires_in`, and `scope`.

**What you need to do:**

* **No changes required for existing integrations.** This is an additive grant type. Existing `client_credentials` and other grants continue to work unchanged.
* **To use token exchange,** first obtain a provider access token through the provider M2M `client_credentials` grant, then exchange it at the token endpoint with `grant_type=token_exchange`, passing the provider token as `subject_token` and the target `session_id`. Pass the resulting attach ticket to the browser for streaming connection. See the [OAuth2 documentation](https://docs.concurrence.com/developer-guide/platform-api/platform-api/oauth2) for usage guidance.

</details>

<details>

<summary>Platform API: Provider M2M Client Credentials - Machine-to-Machine Provider Tokens (July 2026)</summary>

#### Provider M2M Client Credentials - Machine-to-Machine Provider Tokens <a href="#provider-m2m-client-credentials-machine-to-machine-provider-tokens" id="provider-m2m-client-credentials-machine-to-machine-provider-tokens"></a>

Backend applications can now obtain provider-scoped access tokens through the standard `client_credentials` OAuth2 grant, without requiring an interactive provider sign-in.

**What changed:**

* **Provider M2M client provisioning.** A new set of admin endpoints (`/admin/provider-m2m-clients`) lets workspace administrators create, list, inspect, and revoke machine-to-machine credentials bound to a specific provider identity. The one-time plaintext `client_secret` is returned only at creation.
* **Provider-scoped token minting.** A provisioned M2M client authenticates through the existing token endpoint with `grant_type=client_credentials`. The issued access token carries the provider's identity as its subject, uses `provider` as the principal type, and includes only the scopes allowed for that client. The token is identical in shape to one obtained through the interactive provider sign-in, so downstream services accept it without modification.
* **Scope control.** Each client is provisioned with an allowed scope set (defaulting to session creation, session reads, and note read/write). Token requests may down-scope by passing a `scope` parameter; requests that exceed the allowed set are rejected.
* **Optional lifetime.** Clients can be created with an expiry (1 to 3650 days) or as non-expiring credentials that must be explicitly revoked.
* **Revocation.** Revoking a client is a soft delete - the credential is deactivated and new token requests are rejected immediately. Already-issued short-lived tokens expire naturally. Both `POST .../revoke` and `DELETE .../{credential_id}` perform the same operation.
* **Rate limiting.** Provider M2M token minting is subject to a per-client sliding-window rate limit, layered on existing per-IP and per-client lockout protections. Exceeding the limit returns HTTP 429 with a `Retry-After` header.
* **Audit logging.** Client creation, revocation, successful mints, and failed mints (revoked, rate-limited, invalid scope) are all recorded in the audit log.

**What you need to do:**

* **No changes required for existing integrations.** This is an additive feature. Existing `client_credentials` grants for service accounts continue to work unchanged.
* **To use provider M2M clients,** provision a client through the admin endpoints, store the one-time secret securely, and use the `client_id`/`client_secret` pair in standard `client_credentials` token requests. See the [Provider M2M Clients](https://docs.concurrence.com/developer-guide/platform-api/platform-api/oauth2-clients#provider-m2m-clients) documentation for endpoint details and usage guidance.

</details>

<details>

<summary>Platform API: Internal Auth Simplification - Shared Secrets Retired for Cluster-Internal Dispatch (July 2026)</summary>

#### Internal Auth Simplification - Shared Secrets Retired for Cluster-Internal Dispatch <a href="#internal-auth-simplification-shared-secrets-retired-for-cluster-internal-dispatch" id="internal-auth-simplification-shared-secrets-retired-for-cluster-internal-dispatch"></a>

Internal service-to-service authentication for outbound dispatch has been simplified. Several shared-secret environment variables that were previously required for outbound call and text initiation are no longer used.

**What changed:**

* **Outbound dispatch no longer requires a shared secret.** The platform services that initiate outbound calls and outbound text conversations no longer send or validate a dedicated shared-secret header for those internal dispatch requests. Transport-level trust boundaries replace the application-level shared secret.
* **Connector-to-platform authentication simplified.** The connector service no longer requires a static API key to call internal platform endpoints. Cluster-internal routes that the connector uses are secured at the transport boundary rather than by a per-request shared secret.
* **Outbound dispatch is always available when the service is running.** Previously, outbound call and text dispatch were disabled when the shared-secret environment variable was empty. The dispatch path is now unconditionally available, removing a class of silent misconfiguration where a missing secret would quietly disable outbound features.
* **Retired environment variables.** The following operator-facing environment variables are no longer read and can be removed from deployment configuration: `OUTBOUND_API_KEY`, `RUNTIME_ADMIN_API_KEY`, `CONNECTOR_RUNNER_PLATFORM_API_KEY`, `VOICE_AGENT_OUTBOUND_API_KEY`, and `MBP_BACKFILL_TOKEN`.

**What you need to do:**

* **No API integration changes required.** Public API contracts, request formats, and response shapes are unchanged. Outbound call and text creation endpoints continue to work as before.
* **Remove retired variables from deployment configuration.** If your deployment sets any of the variables listed above, they are no longer read and can be safely removed.

</details>

<details>

<summary>Platform API: Environment Variable Cleanup - Four Runtime Knobs Retired (July 2026)</summary>

#### Environment Variable Cleanup - Four Runtime Knobs Retired <a href="#environment-variable-cleanup-four-runtime-knobs-retired" id="environment-variable-cleanup-four-runtime-knobs-retired"></a>

Four environment-level configuration knobs have been retired and replaced with code-level defaults or narrower runtime boundaries.

**What changed:**

* **Realtime voice model is now a code-level default.** The voice model used for speech-to-speech sessions is no longer configurable through an environment variable. The platform uses a fixed default (`gpt-realtime-2.1`) that was validated for reliable tool calling. Per-agent provider configuration still overrides the default on a per-call basis.
* **Cartesia voice identifier is now a code-level default.** The default text-to-speech voice for calls whose agent has no per-agent voice configuration is now set in code rather than through an environment variable. The previously deployed value was stale and never matched the live fleet default - the new constant reflects the actual production value.
* **Workspace scope eager provisioning is always on.** Workspace scope provisioning now fires automatically when the provisioning job is configured, without requiring a separate enablement flag. The scheduled reconciliation job remains the durable backfill.
* **Egress drain runs only in production.** The external-write proposal egress drain - which delivers human-approved proposals to their destination systems - is now gated by a code-level environment boundary rather than a per-deployment configuration flag. Non-production environments never push approved proposals to real external systems.

**What you need to do:**

* **No integration changes required.** These were operator-facing environment knobs, not API contract changes. Existing API contracts, request formats, and response shapes are unchanged.
* **Remove retired variables from deployment configuration.** If your deployment sets `REALTIME_MODEL`, `CARTESIA_VOICE_ID`, `WORKSPACE_SCOPE_EAGER_PROVISION_ENABLED`, or `EGRESS_DRAIN_DISABLED`, they are no longer read and can be removed.

</details>

<details>

<summary>Platform API: Empty Tool Description Warning for Realtime Voice Sessions (July 2026)</summary>

#### Empty Tool Description Warning for Realtime Voice Sessions <a href="#empty-tool-description-warning-for-realtime-voice-sessions" id="empty-tool-description-warning-for-realtime-voice-sessions"></a>

Realtime voice sessions now detect and warn when a tool is declared without a description, preventing a class of silent tool-selection failures.

**What changed:**

* **Empty description detection.** When the platform translates tools for a Realtime voice session, any tool whose description is empty or whitespace-only now triggers a warning. The warning identifies the tool by name only and does not include any caller or session data.
* **Metrics emitted.** A counter metric is incremented for each description-less tool, tagged by tool name, so operations teams can alert on the condition.
* **Tool still declared.** A missing description is degraded behavior, not fatal. The tool remains in the session's tool set. However, a description-less tool may lose tool selection to any better-described peer - the model tends to prefer tools with clear descriptions.
* **No change for well-described tools.** Tools with non-empty descriptions are unaffected.

**Why it matters:**

If a tool arrives at the voice session without a description - for example, because graph-authored instructions were not merged correctly - the model may silently ignore it in favor of other tools that have descriptions. This warning makes that failure mode visible rather than requiring manual debugging of missed tool calls.

**What you need to do:**

* **No integration changes required.** The warning is automatic. Existing API contracts, request formats, and response shapes are unchanged.
* **Review tool descriptions.** If you see warnings for specific tools, ensure those tools have meaningful descriptions in their catalog entry or that their context graph states include usage instructions that get merged into the description.

</details>

<details>

<summary>Platform API: Graph Tool Instructions Preserved for Realtime Voice Sessions (July 2026)</summary>

#### Graph Tool Instructions Preserved for Realtime Voice Sessions <a href="#graph-tool-instructions-preserved-for-realtime-voice-sessions" id="graph-tool-instructions-preserved-for-realtime-voice-sessions"></a>

Realtime voice sessions now include per-tool usage instructions authored in the context graph, matching the behavior of the in-house stateful runtime.

**What changed:**

* **Tool instructions merged from the context graph.** When a service's context graph attaches usage instructions to a tool reference in one or more states, those instructions are now appended to the tool's description before the tool set is sent to the voice provider. Previously, Realtime sessions received only the base tool catalog description and silently dropped any graph-authored instructions.
* **Cross-state deduplication.** If the same tool appears in multiple states with identical instructions, the instruction text is included only once. Instructions from different states are merged in graph order.
* **No change for tools without instructions.** Tools that have no additional instructions in the context graph are sent with their catalog description unchanged.
* **No change for in-house voice sessions.** The in-house stateful runtime already applied per-state tool instructions. This fix brings Realtime sessions to parity.

**What you need to do:**

* **No integration changes required.** The fix is automatic for all Realtime voice services. Existing API contracts, request formats, and response shapes are unchanged.
* **Review tool behavior after deployment.** If your context graph includes tool instructions that were previously ignored during Realtime sessions, those instructions now take effect. Verify that the voice agent's tool-calling behavior matches expectations.

</details>

<details>

<summary>Platform API: Structural Tool-Calling Enforcement for Realtime Voice Sessions - Reverted (July 2026)</summary>

#### Structural Tool-Calling Enforcement for Realtime Voice Sessions - Reverted <a href="#structural-tool-calling-enforcement-for-realtime-voice-sessions-reverted" id="structural-tool-calling-enforcement-for-realtime-voice-sessions-reverted"></a>

~~Realtime voice sessions that use reasoning-class speech-to-speech models now enforce tool calling structurally rather than relying solely on the prompt contract.~~

**This change has been reverted.** The structural enforcement approach - which injected a decline signal and set the session tool choice to force a tool selection on every caller turn - was not effective under real audio conditions. The model consistently selected the decline signal instead of calling the intended tool, and the fallback mechanism did not reliably break the cycle. The enforcement has been removed.

**Current state:**

* **No structural tool-calling enforcement.** Realtime voice sessions use `auto` tool choice. The platform does not force the model to select a tool on every turn.
* **No tool name restriction.** The previously reserved name `no_tool_needed` is no longer injected or restricted. Services may use any tool name.
* **Known limitation: autonomous tool calling is not reliable on Realtime voice sessions.** Under `auto` tool choice, reasoning-class speech-to-speech models may narrate about an action instead of emitting a tool call, particularly when the agent persona includes conversational behaviors. Voice services that depend on autonomous tool calling should use the in-house voice provider, which calls tools reliably. Realtime voice sessions are appropriate for tool-less services or for the test-only forced-first-tool configuration.

**What you need to do:**

* **Move tool-dependent voice services to the in-house provider.** If your service requires autonomous tool calling during voice sessions, configure it to use the in-house voice provider rather than the Realtime speech-to-speech provider.
* **No integration changes required.** The revert is automatic. Existing API contracts, request formats, and response shapes are unchanged.
* **The `no_tool_needed` name restriction is lifted.** If you renamed a tool to avoid the previously reserved name, you may rename it back.

</details>

<details>

<summary>Platform API: Autonomous Tool Calling for Realtime Voice Sessions (July 2026)</summary>

#### Autonomous Tool Calling for Realtime Voice Sessions <a href="#autonomous-tool-calling-for-realtime-voice-sessions" id="autonomous-tool-calling-for-realtime-voice-sessions"></a>

Realtime voice sessions that use reasoning-class speech-to-speech models now call tools autonomously when the service's context graph binds tools. Previously, these sessions could speak about an action without ever executing the corresponding tool call.

**What changed:**

* **Prompt-driven autonomous tool calling.** When a service's context graph includes tool-bound states, the generated system prompt now ends with an explicit tool-use contract that reliably triggers tool calls for look-ups and actions rather than letting the model answer from its own knowledge. The contract makes clear that conversational scripts (greetings, step guidance) govern only what the agent says - they never excuse or defer a tool call. An anti-narration rule prevents the agent from claiming it is "checking" or "looking up" something without actually calling the tool in that turn. Read-only look-ups are called immediately; mutating actions are confirmed with the caller first. The directive is omitted for services with no tool bindings.
* **Reasoning effort returned to the low-latency default.** The earlier interim change that raised the default reasoning budget for tool-bound sessions has been reverted. Testing confirmed that the prompt contract alone drives reliable tool calling at the minimal reasoning budget, so all sessions - with or without tools - now default to the lowest reasoning effort to preserve first-audio latency. A per-service override in the voice configuration still takes precedence.
* **No change for tool-less services.** Services whose context graphs do not bind any tools are unaffected. Their prompt structure and reasoning budget remain unchanged.

**What you need to do:**

* **No integration changes required.** The prompt adjustments are automatic based on the service's context graph. Existing voice configurations, API contracts, and response shapes are unchanged.
* **Review per-service reasoning effort overrides.** If you previously raised reasoning effort in your voice configuration to work around missed tool calls, the prompt-driven fix makes that override unnecessary. Removing it returns first-audio latency to the platform default.

</details>

<details>

<summary>Platform API: Per-Modality Voice COGS Tracking (July 2026)</summary>

#### Per-Modality Voice COGS Tracking <a href="#per-modality-voice-cogs-tracking" id="per-modality-voice-cogs-tracking"></a>

Realtime voice usage is now priced per modality - audio and text, input and output, cached and uncached - instead of a single blended token rate. This gives operators accurate cost-of-goods-sold breakdowns for voice workloads where audio and text token costs differ significantly.

**What changed:**

* **Per-modality cost tracking.** Realtime voice conversations now record six granular token categories: audio input, audio output, text input, text output, cached audio input, and cached text input. Each category is priced against its own rate, reflecting the large cost difference between audio and text tokens.
* **Blended pricing unchanged for non-voice models.** Chat and text models continue to use the existing input, output, and cached token pricing. The new modality categories apply only to realtime voice sessions.
* **Accurate COGS reporting.** Monthly cost-of-goods-sold reporting now includes a per-modality breakdown for realtime voice, replacing the previous blended estimate.

**What you need to do:**

* **No integration changes required.** Per-modality tracking is automatic for realtime voice sessions. Existing API contracts, request formats, and response shapes are unchanged.
* **Review voice cost reports.** If your organization uses realtime voice, COGS reports now reflect the actual per-modality cost split rather than a blended approximation.

</details>

<details>

<summary>Scribe API: Workspace-Level Allocation Rate Limit and Fleet Exhaustion Visibility (July 2026)</summary>

#### Workspace-Level Allocation Rate Limit and Fleet Exhaustion Visibility <a href="#workspace-level-allocation-rate-limit-and-fleet-exhaustion-visibility" id="workspace-level-allocation-rate-limit-and-fleet-exhaustion-visibility"></a>

The streaming session allocation endpoint now enforces a workspace-scoped rate limit in addition to the existing per-session cooldown, and reports capacity metrics that make allocation failures attributable to a specific workspace.

**What changed:**

* **Workspace-level allocation rate limit.** A sliding-window rate limit now caps the number of streaming session allocations a single workspace can request within a rolling time window. This is layered on top of the existing per-session cooldown so that one workspace opening many sessions cannot exhaust shared streaming capacity and affect other tenants. The default budget is deliberately generous to accommodate legitimate reconnection bursts across many providers.
* **Consistent retryable response.** When the workspace limit is exceeded, the allocate endpoint returns `503 Service Unavailable` with a `Retry-After` header, the same contract used by the per-session cooldown and capacity-exhaustion paths. SDKs that already implement backoff-and-retry handle this response without changes.
* **Fleet exhaustion metrics.** Allocation failures caused by capacity exhaustion are now tagged with the requesting workspace, making it possible to attribute shared-capacity pressure to a specific tenant. Separate tags distinguish genuine capacity exhaustion from transient network errors.
* **Throttle-offline visibility.** If the backing store used for rate-limit state is temporarily unavailable, the rate limits fail open (allocation is allowed) and a counter is emitted so monitoring can distinguish "no throttling needed" from "throttling temporarily offline."

**What you need to do:**

* **No integration changes required.** The 503 + `Retry-After` response shape is unchanged. SDKs and clients that respect `Retry-After` handle the new workspace-level limit automatically.
* **Review allocation patterns if you receive 503s with a workspace-scope message.** The response detail distinguishes per-session cooldown ("Too many allocation requests for this session") from the workspace limit ("Too many allocation requests for this workspace"). If you see the workspace-level message, your workspace is allocating sessions faster than the platform permits.

</details>

<details>

<summary>Platform API: External Identity Binding Documentation Consolidated (July 2026)</summary>

#### External Identity Binding Documentation Consolidated <a href="#external-identity-binding-documentation-consolidated" id="external-identity-binding-documentation-consolidated"></a>

Internal platform documentation for external identity binding has been reorganized and corrected. No API or behavioral changes are included.

**What changed:**

* **Consolidated guidance.** Scattered references to external identity binding across guides and runbooks have been unified into a single reference document.
* **Stale field reference corrected.** Documentation that referenced a deprecated identifier field now uses the current field name.

**What you need to do:**

* **No action required.** This is a documentation-only change with no impact on API contracts or runtime behavior.

</details>

<details>

<summary>Platform API: Environment Configuration Simplified - Volume Paths, Transcript Settings, and Connector URL (July 2026)</summary>

#### Environment Configuration Simplified - Volume Paths, Transcript Settings, and Connector URL <a href="#environment-configuration-simplified-volume-paths-transcript-settings-and-connector-url" id="environment-configuration-simplified-volume-paths-transcript-settings-and-connector-url"></a>

Several environment variables that were previously required for deployment have been retired or made mandatory. Volume paths for call recordings and audit exports are now derived automatically from the deployment stage, and the connector-runner internal URL no longer falls back to a default.

**What changed:**

* **Call recording and audit export volume paths derived automatically.** The platform now computes volume paths for call recordings and audit exports from the deployment stage rather than reading them from separate environment variables. The `CALL_RECORDING_VOLUME_PATH` and `AUDIT_EXPORT_VOLUME_PATH` environment variables are no longer read.
* **Database schema passed inline.** Each service now declares its own database schema as a fixed constant rather than reading it from a shared `LAKEBASE_SCHEMA` environment variable. The `LAKEBASE_SCHEMA` environment variable is no longer read by services. Scripts that still need a per-invocation schema can continue to set it for their own use.
* **Connector-runner URL now required.** The connector-runner internal URL no longer falls back to a built-in default. If `CONNECTOR_RUNNER_INTERNAL_URL` is not set, the service fails at startup. Every deployed environment already provisions this value.
* **Legacy transcript settings retired for meeting bots.** The `TRANSCRIPT_S3_BUCKET` and `TRANSCRIPT_S3_PREFIX` settings for the meeting-bot control plane have been removed. Transcript persistence uses the scribe artifact contract exclusively. The `SCRIBE_ARTIFACTS_S3_PREFIX` setting is now a fixed layout constant and no longer read from environment configuration.
* **Text interaction wait budget is now a fixed constant.** The `TEXT_WAIT_FOR_FINAL_TIMEOUT_SECONDS` environment variable is no longer read. The synchronous wait budget for text turns is a fixed platform constant.

**What you need to do:**

* **Remove retired environment variables.** If your deployment sets any of the following, they are no longer read and can be removed: `CALL_RECORDING_VOLUME_PATH`, `AUDIT_EXPORT_VOLUME_PATH`, `LAKEBASE_SCHEMA`, `TRANSCRIPT_S3_BUCKET`, `TRANSCRIPT_S3_PREFIX`, `SCRIBE_ARTIFACTS_S3_PREFIX`, `TEXT_WAIT_FOR_FINAL_TIMEOUT_SECONDS`.
* **Ensure `CONNECTOR_RUNNER_INTERNAL_URL` is set.** If your deployment relied on the previous built-in default for the connector-runner URL, add the variable explicitly. All standard deployments already provision this value.
* **No API or integration changes required.** These are deployment-configuration changes only. API contracts, request formats, and response shapes are unchanged.

</details>

<details>

<summary>Platform API: Drive Sync Resilience - Interrupted Syncs No Longer Strand Files (July 2026)</summary>

#### Drive Sync Resilience - Interrupted Syncs No Longer Strand Files <a href="#drive-sync-resilience-interrupted-syncs-no-longer-strand-files" id="drive-sync-resilience-interrupted-syncs-no-longer-strand-files"></a>

Google Drive folder syncs now handle mid-sync interruptions gracefully instead of leaving batches and files stranded in an unrecoverable preparing state.

**What changed:**

* **Per-file fault isolation broadened.** Previously, only a narrow set of known file-level errors were caught during sync. Any other transient failure - such as a network timeout or an unexpected service error while fetching a single file - would abort the entire folder sync. Now, any per-file failure is caught, logged, and skipped so the remaining files in the folder continue processing.
* **Batch marked failed on folder-level errors.** If a folder-level failure occurs (for example, an authentication or listing error, or a failure finalizing the batch), the batch is now marked as failed rather than left in a preparing state. This makes the failure visible in the batch list and prevents orphaned files from accumulating silently.
* **Sync error recorded on the source.** When a sync fails at the source level, the error is now recorded on the source record so it is visible through the source listing. Previously, a mid-sync abort left no trace on the source, and the failure was only observable through stranded batches.
* **No change to successful syncs.** The sync workflow, deduplication behavior, file-size limits, and per-folder caps are unchanged for syncs that complete without errors.

**What you need to do:**

* **No action required.** Syncs that previously failed silently now surface failures visibly on the batch and source. If you monitor batch or source status, you may see `failed` statuses where previously the sync appeared to hang indefinitely at a preparing state.

</details>

<details>

<summary>Platform API: Simplified Rolling-Transcript Storage Key Layout (July 2026)</summary>

#### Simplified Rolling-Transcript Storage Key Layout <a href="#simplified-rolling-transcript-storage-key-layout" id="simplified-rolling-transcript-storage-key-layout"></a>

The rolling-transcript artifact path no longer includes an environment segment. Existing snapshots at the previous path are not migrated automatically.

**What changed:**

* **Environment segment removed from transcript snapshot keys.** The rolling-transcript snapshot path previously included an `env=<environment>` segment between the storage prefix and the workspace identifier. That segment has been removed. The new layout is `<prefix>/workspace=<wid>/provider=<pid>/session=<sid>/raw-transcript/snapshots/latest.json`.
* **Consistent key structure.** The snapshot key now follows the same workspace/provider/session hierarchy used by other artifact keys, without an extra environment partition.

**What you need to do:**

* **No integration changes required.** Rolling-transcript snapshots are internal artifacts consumed by the platform. If you have tooling that reads snapshot keys directly using the old `env=` layout, update it to use the new path structure.
* **Old snapshots are not relocated.** Previously written snapshots remain at their original keys. Active sessions will write new snapshots to the updated path.

</details>

<details>

<summary>Platform API: Feature Gates Retired - Six Capabilities Now Always On (July 2026)</summary>

#### Feature Gates Retired - Six Capabilities Now Always On <a href="#feature-gates-retired-six-capabilities-now-always-on" id="feature-gates-retired-six-capabilities-now-always-on"></a>

Six platform capabilities that were previously behind per-environment or per-workspace feature gates are now unconditionally enabled. No new API surface or behavioral changes are introduced - each capability works exactly as it did when its gate was enabled.

**What changed:**

* **Multi-provider model routing.** Non-voice engage requests route through the provider abstraction layer for all wired model families. Previously gated by an environment variable; now always active.
* **Cartesia speech-to-text for English callers.** English-language voice calls use the Cartesia ink-2 STT provider by default when the API key is provisioned. Previously required an explicit per-environment opt-in flag.
* **Inbound channel-turn processing.** The background consumer that drains inbound channel work (email turns) now runs unconditionally on every pod. Previously required a per-environment enable flag; environments without bound channel use cases simply process an empty work list.
* **World-model read tools on the MCP server.** The MCP server's read surface - entity reads and workspace data queries - is now always registered when the platform's database session is available. Previously gated by an environment variable.
* **Trace export endpoint.** The read-only trace export endpoint is now always active. Previously gated by an environment variable that returned 404 when disabled.
* **Provider-principal authentication.** The provider-principal login flow is now enabled by default, with per-workspace control retained through the existing feature flag. The environment-variable fallback that defaulted to off has been removed.

**What you need to do:**

* **Remove retired environment variables.** If your deployment sets any of the following, they are no longer read and can be removed: `PROVIDER_ROUTER_ENABLED`, `CARTESIA_STT_ENABLED`, `CHANNEL_TURN_CONSUMER_DISABLED`, `MCP_WORLD_TOOLS_ENABLED`, `OTEL_TRACE_EXPORT_ENABLED`, `PROVIDER_PRINCIPAL_ENABLED`.
* **No integration changes required.** All six capabilities behave identically to how they worked when their respective gates were enabled. Existing API contracts, request formats, and response shapes are unchanged.

</details>

<details>

<summary>Platform API: Required Tool Retry for Realtime Voice Sessions (July 2026)</summary>

#### Required Tool Retry for Realtime Voice Sessions <a href="#required-tool-retry-for-realtime-voice-sessions" id="required-tool-retry-for-realtime-voice-sessions"></a>

Realtime voice sessions that require a specific tool call from the first caller response now retry automatically instead of failing immediately when the model does not emit the expected tool.

**What changed:**

* **Automatic retry on missing required tool.** When a realtime voice session is configured to require a specific tool call from the first caller response and the model completes that response without emitting the tool, the platform now sends a single retry with an explicit tool-choice override before failing. Previously, the session raised an error immediately.
* **Cancelled-response tolerance.** If the first caller response is cancelled because a new caller turn was detected, the session now waits for the next response rather than failing. This handles cases where the caller speaks again before the model finishes its initial reply.
* **Bounded retry.** The retry is attempted at most once. If the retried response also completes without the required tool, the session ends with an error. This prevents unbounded retry loops.
* **Stricter end-of-session validation.** The session now verifies that the required tool was emitted before the realtime connection closes. Previously, certain timing conditions could allow the connection to end without the required tool having been called.

**What you need to do:**

* **No action required.** Sessions that previously failed when the model omitted the required tool on the first attempt now have one automatic retry. If your integration handles these failures with external retry logic, the additional resilience may reduce the number of externally retried sessions.

</details>

<details>

<summary>Platform API: Voice Tool Authorization Aligned Across Playground and Phone Calls (July 2026)</summary>

#### Voice Tool Authorization Aligned Across Playground and Phone Calls <a href="#voice-tool-authorization-aligned-across-playground-and-phone-calls" id="voice-tool-authorization-aligned-across-playground-and-phone-calls"></a>

Realtime voice sessions now enforce a consistent, graph-derived tool authorization boundary regardless of whether the call originates from the browser Playground or a phone number.

**What changed:**

* **Graph-authorized tool set.** Realtime voice sessions now receive only the tools referenced across the service's context graph states, resolved against the registered platform tool catalog. Previously, the full platform tool set was passed to the provider without graph-level filtering.
* **Mandatory allowlist enforcement.** Every tool call from the realtime provider is checked against the authorized set before execution. Calls to tools outside the set are denied with an explicit error returned to the provider. This applies to both Playground and phone-originated sessions.
* **Playground greeting optimization.** Browser-originated voice sessions that use a session-owning provider no longer pre-render a greeting through the default speech pipeline. The provider generates its own opening audio, removing the dead-air delay that occurred when a pre-rendered greeting was discarded.
* **Consistent credential resolution.** Playground sessions skip telephony credential lookup since they connect through the browser rather than a phone network leg. This eliminates unnecessary fallback resolution and aligns the session startup path with phone calls.

**What you need to do:**

* **Review context graph tool references.** Tools that the realtime provider could previously call but that are not referenced in any context graph state will now be denied. Ensure every tool the agent should use during a voice session is referenced in at least one state of the service's context graph.
* **No other action required.** Playground and phone call behavior is otherwise unchanged.

</details>

<details>

<summary>Platform API: Playground Voice Test Calls Use the Configured Voice Provider (July 2026)</summary>

#### Playground Voice Test Calls Use the Configured Voice Provider <a href="#playground-voice-test-calls-use-the-configured-voice-provider" id="playground-voice-test-calls-use-the-configured-voice-provider"></a>

Browser voice test calls initiated from the Playground now respect the service's configured session-owning voice provider instead of always running through the default in-house voice pipeline.

**What changed:**

* **Provider-aware test calls.** When a service is configured with a session-owning voice provider, Playground voice test calls now route through that provider. Previously, test calls always used the default pipeline regardless of the service's voice configuration.
* **Consistent call lifecycle.** Test calls through a session-owning provider now emit the same call-started, call-ended, and call-intelligence lifecycle events as production calls, so they appear in conversation history and the Runs surface.
* **Slot management.** Test calls acquire and release session slots with the same guarantees as production calls, including bounded duration caps to prevent slot leaks.
* **Tool and workflow parity.** The configured provider receives the full tool set and workflow prompt from the service's agent, matching production behavior.

**What you need to do:**

* **No action required.** Playground voice test calls automatically use the service's configured voice provider. If you were previously seeing different behavior between Playground tests and production calls on services with a session-owning provider, those differences are now resolved.

</details>

<details>

<summary>Platform API: Exhaustive Skill Reference Scanning on Delete (July 2026)</summary>

#### Exhaustive Skill Reference Scanning on Delete <a href="#exhaustive-skill-reference-scanning-on-delete" id="exhaustive-skill-reference-scanning-on-delete"></a>

The skill deletion safety check now scans all context graphs in the workspace before allowing a skill to be removed, rather than examining only the first page of results.

**What changed:**

* **Complete reference scan.** The delete guard now pages through every context graph in the workspace when checking whether a skill is still referenced. Previously, the scan examined only the default first page of context graphs, which could miss references in older graphs and allow deletion of a skill still bound to a live context graph version.
* **Batch version lookup.** The scan retrieves the latest version of all context graphs in a single query instead of one query per graph, reducing latency for workspaces with many context graphs.
* **Targeted service lookup.** Services bound to referenced context graphs are now found through a direct lookup by context graph identity, replacing the previous approach that scanned only the first page of services in the workspace.
* **Fail-closed safety cap.** If the workspace contains too many context graphs to scan completely, the delete request returns a `503` response instead of proceeding on a partial scan. This protects against removing a skill that is still in use.

**What you need to do:**

* **No action required for most workspaces.** The change makes skill deletion safer by ensuring all references are found before the delete proceeds. Workspaces with a large number of context graphs may see slightly longer delete times due to the exhaustive scan.
* **Handle `503` on skill delete.** In the unlikely event that a workspace exceeds the scan safety cap, the delete request will return `503 Service Unavailable`. Retry after reducing the number of unused context graphs or contact support.

</details>

<details>

<summary>Platform API: Tiered Permission Enforcement for Workspace Data Query Invocation (July 2026)</summary>

#### Tiered Permission Enforcement for Workspace Data Query Invocation <a href="#tiered-permission-enforcement-for-workspace-data-query-invocation" id="tiered-permission-enforcement-for-workspace-data-query-invocation"></a>

Invoking a stored workspace data query now enforces a tiered permission check based on whether the query template performs read-only or write-capable operations.

**What changed:**

* **Read-only queries remain invokable at the view tier.** Stored query templates that contain only read operations continue to require the `Workspace.view` permission, matching prior behavior. These queries run under a read-only transaction backstop that rejects any unintended write.
* **Write-capable queries require the update tier.** Stored query templates classified as write-capable (DML operations against custom schemas) now require `Workspace.update` permission. Callers with only `Workspace.view` receive a `403` response when invoking a write-capable template.
* **Consistent with MCP invoke path.** The tiered gate mirrors the permission split already enforced by the MCP data-access invoke path, so both invocation methods apply the same authorization rules.
* **No change to query creation.** The create-time validation rules for stored query templates are unchanged. This update only affects the invoke path.

**What you need to do:**

* **Verify API key permissions for write-capable queries.** If your integration invokes stored query templates that perform write operations (inserts, updates, deletes against custom schemas), confirm that the API key or credential carries the `Workspace.update` permission. View-tier credentials that previously invoked these templates will now receive a `403` response.
* **No action needed for read-only queries.** Integrations that invoke only read-only stored queries continue to work with `Workspace.view` credentials.

</details>

<details>

<summary>Platform API: Permission-Based Access Control for API Keys and MCP Tools (July 2026)</summary>

#### Permission-Based Access Control for API Keys and MCP Tools <a href="#permission-based-access-control-for-api-keys-and-mcp-tools" id="permission-based-access-control-for-api-keys-and-mcp-tools"></a>

API key authentication and MCP tool authorization now enforce permission-based access control instead of role-name checks. Every API key is linked to a canonical platform role with an explicit permission set, and MCP tools verify individual permissions rather than checking for a named role.

**What changed:**

* **API keys require a canonical role link.** New API keys must reference a recognized platform role and include an explicit, non-empty permission list. The permission list can only narrow the grants of the linked role, never expand them.
* **Existing keys with empty permission lists inherit role defaults.** API keys created before this change that have no stored permission list temporarily receive the full default grants of their assigned role. This preserves backward compatibility while workspaces migrate to explicit scopes.
* **MCP tools enforce individual permissions.** World-model read tools require `Data:View`. SQL and function-call tools require `Data:Query`. Surface configuration tools require `Surface:Create`. Platform function tools require `Workspace:View`. Each tool returns a structured error envelope when the caller lacks the required permission.
* **Prompt log and trace export routes require `Audit:View`.** These endpoints previously checked for admin or owner role names. They now verify the `Audit:View` permission grant on the caller's effective role.
* **Workspace management routes use permission checks.** Update, provision, archive, environment conversion, and test-traffic configuration routes now verify permissions on the caller's resolved role object rather than checking the role name string.
* **API key creation and listing return `503` when canonical roles are unavailable.** If the platform's role registry is missing or ambiguous, key creation and listing fail with a clear service-unavailable response instead of silently proceeding.
* **No change for standard admin and owner keys.** The admin and owner roles include all previously available permissions by default. Existing integrations using those roles continue to work without modification.

**What you need to do:**

* **Verify custom permission lists.** If your API keys use a narrowed permission list, confirm the list includes the permissions needed for your integration's MCP tools and API routes. Add `Data:View` for world-model reads, `Data:Query` for SQL and function calls, `Audit:View` for prompt logs and trace exports, and `Surface:Create` for surface configuration tools.
* **Update integrations that check role names.** If your code inspects the role name string returned from API key endpoints, switch to checking the permission list instead. Role names remain available but are no longer the authorization mechanism.
* **Handle `503` responses on key management endpoints.** API key creation and listing can now return `503 Service Unavailable` if the canonical role configuration is unavailable. Add retry logic for these responses in automation workflows.

</details>

<details>

<summary>Platform API: Compliance Routes Require Audit.view Permission (July 2026)</summary>

#### Compliance Routes Require Audit.view Permission <a href="#compliance-routes-require-audit.view-permission" id="compliance-routes-require-audit.view-permission"></a>

The compliance dashboard, HIPAA report, and access review endpoints now enforce the `Audit.view` permission. Previously these routes were accessible to any admin or owner API key without an explicit permission check.

**What changed:**

* **Permission enforcement on compliance endpoints.** The compliance dashboard, HIPAA report, and access review routes now verify that the caller's role includes the `Audit.view` permission before processing the request. Requests from roles that lack this permission receive a `403` response.
* **No change for standard admin and owner keys.** The admin and owner roles include `Audit.view` by default, so existing integrations using those roles continue to work without modification.

**What you need to do:**

* **Verify custom roles.** If your workspace uses custom roles that previously accessed compliance endpoints, confirm those roles include the `Audit.view` permission. Add the permission if needed to restore access.

</details>

<details>

<summary>Platform API: External Identity Binding for Memory v2 (July 2026)</summary>

#### External Identity Binding for Memory v2 <a href="#external-identity-binding-for-memory-v2" id="external-identity-binding-for-memory-v2"></a>

Memory v2 now supports binding conversation memory to an external patient or user identity, so that behavioral memory dimensions persist across conversations and channels for the same individual.

**What changed:**

* **External identity binding at conversation start.** When a conversation is created with an external identity reference, the platform resolves the reference against workspace records and loads the matching memory dimensions into the conversation context.
* **Persistent cross-conversation memory.** Memory updates during a conversation are written back to the external identity's record. Subsequent conversations for the same identity receive the updated memory state.
* **Graceful fallback on resolution failure.** If the external identifier does not match a known workspace record, the conversation proceeds without persistent memory. The resolution failure is recorded in conversation metadata.
* **Concurrent conversation support.** Multiple conversations can bind to the same external identity. Writes from each conversation are reconciled asynchronously after the conversation ends.

**What you need to do:**

* **Ensure external records exist before binding.** The external identity must be present in the workspace before conversation creation. Conversations that reference an unknown identifier will start without persistent memory.
* **Allow for asynchronous reconciliation.** Memory updates from a completed conversation may not be immediately visible to a new conversation for the same identity. If your workflow creates back-to-back conversations for the same individual, allow a short interval between them.

</details>

<details>

<summary>Scribe: Session Allocation for Streaming Workers (July 2026)</summary>

#### Session Allocation for Streaming Workers <a href="#session-allocation-for-streaming-workers" id="session-allocation-for-streaming-workers"></a>

Browser-based clinical recording sessions now allocate a dedicated streaming worker before connecting over WebSocket. The new allocation step sits between session creation and the WebSocket attach, giving the SDK an explicit host and expiration window.

**What changed:**

* **Allocate endpoint.** A new `POST /sessions/{session_id}/allocate` endpoint assigns a dedicated streaming worker to an existing session and returns a routable host and an expiration timestamp. The SDK opens its WebSocket connection to the returned host.
* **Session state guard.** Allocation is accepted only for sessions in a state that supports streaming. Sessions that have already completed or moved to a terminal state are rejected with a `409 Conflict` response.
* **Capacity-aware retryable errors.** When no streaming capacity is available or the allocation cannot be fulfilled, the endpoint returns `503 Service Unavailable` with a `Retry-After` header. The SDK treats this as a retryable signal and backs off before retrying.
* **Per-session cooldown.** Repeated allocation requests for the same session within a short window are throttled to prevent a single caller from consuming shared capacity. Throttled requests receive the same retryable `503` response.
* **Expiration window.** The allocation response includes an `expires_at` timestamp representing the session ceiling. A reconnect after expiration re-allocates a fresh worker.

**What you need to do:**

* **No action required for existing integrations.** This endpoint supports the upcoming browser recording SDK. Existing session creation and management APIs are unchanged. If you are building a custom integration against the streaming flow, call allocate after creating a session and before opening the WebSocket connection.

</details>

<details>

<summary>Scribe: Resumable WebSocket Streaming for Browser Recording Sessions (July 2026)</summary>

#### Resumable WebSocket Streaming for Browser Recording Sessions <a href="#resumable-websocket-streaming-for-browser-recording-sessions" id="resumable-websocket-streaming-for-browser-recording-sessions"></a>

Browser-based clinical recording sessions now connect to the scribe worker over a resumable WebSocket, replacing the previous connection model with a protocol that supports pause, resume, reconnect, and structured lifecycle transitions.

**What changed:**

* **WebSocket streaming endpoint.** A new WebSocket endpoint accepts browser microphone audio in real-time. Authentication uses the provider JWT passed through the WebSocket sub-protocol header; workspace scope is derived from the token claims.
* **Session lifecycle over the socket.** The connection drives the full session state machine: attach validation, first-audio activation, pause and resume, clean end, and unclean disconnect. Each transition is fenced so that a stale or superseded connection cannot overwrite state owned by a newer attach.
* **Reconnect support.** If a connection drops, the browser SDK can reconnect to the same session. The worker rehydrates accumulated transcript state and resumes from the last acknowledged audio offset. An opening handshake frame lets the client declare how much audio it has already delivered.
* **Structured close codes.** The endpoint uses typed close codes to distinguish authentication failure, session-not-found, terminal-state rejection, capacity limits, clean completion, fatal errors, and recoverable disconnects. Clients can use these codes to decide whether to retry, reconnect, or surface an error.
* **Pause and resume.** A pause control frame flushes a transcript snapshot and releases the speech-to-text upstream connection. A subsequent resume frame opens a fresh upstream connection while preserving transcript ordering through the session.
* **Per-worker capacity guard.** Each worker enforces a concurrent session limit. A connection that arrives when the worker is at capacity receives a specific close code rather than silently failing.
* **Periodic acknowledgment frames.** The worker sends periodic acknowledgment frames that report the last processed audio offset. These frames also serve as server-initiated keepalives to prevent idle-timeout disconnects on intermediate infrastructure.

**What you need to do:**

* **No action required for existing integrations.** This endpoint supports the upcoming browser recording SDK. Existing session creation and management APIs are unchanged.

</details>

<details>

<summary>Platform API: Use-Case Ownership Endpoints and List Proxy Removed (July 2026)</summary>

#### Use-Case Ownership Endpoints and List Proxy Removed <a href="#use-case-ownership-endpoints-and-list-proxy-removed" id="use-case-ownership-endpoints-and-list-proxy-removed"></a>

The use-case list proxy, ownership assignment, ownership release, and ownership query endpoints have been removed from the Platform API. The separate ownership concept is retired - service binding is now the only linkage between a workspace and a channel use case.

**What changed:**

* **Use-case list endpoint removed.** The `GET /use-cases` proxy that filtered channel-manager use cases by workspace ownership is no longer available. Consumers that need to enumerate use cases should query the channel-manager API directly.
* **Ownership endpoints removed.** `PUT /{use_case_id}/ownership`, `DELETE /{use_case_id}/ownership`, `GET /{use_case_id}/ownership`, and `GET /use-cases/ownership` are removed. There is no replacement - ownership as a separate concept is retired.
* **Ownership permission removed.** The `Channel.ManageOwnership` permission no longer exists. API keys and roles that referenced it will no longer see it in permission lists. No other Channel permissions are affected.
* **Service binding no longer requires prior ownership.** Binding a use case to a service now requires only that the use case exists and the caller has `Channel.create` permission. The previous requirement to first assign ownership before binding is gone.
* **Ownership data dropped.** Existing ownership records have been removed. Workspaces that previously assigned ownership do not need to take any action - their service bindings continue to function.

**What you need to do:**

* **Remove calls to ownership endpoints.** Any integration that assigned, released, or queried use-case ownership should remove those calls. Service binding (`PUT /{use_case_id}/service-binding`) is the only workspace-to-use-case linkage going forward.
* **Remove use-case list proxy calls.** If your integration listed use cases through the Platform API, switch to the channel-manager API directly.
* **Remove `Channel.ManageOwnership` references.** If you checked for or granted this permission, remove those references. It is no longer recognized.

</details>

<details>

<summary>Platform API: Unified Default and Custom Memory Dimensions (July 2026)</summary>

#### Unified Default and Custom Memory Dimensions <a href="#unified-default-and-custom-memory-dimensions" id="unified-default-and-custom-memory-dimensions"></a>

Default memory dimensions are now pre-seeded as standard enrichment key registry entries in every workspace, making them indistinguishable from custom dimensions at the API level.

**What changed:**

* **Default dimensions pre-seeded per workspace.** The eight default behavioral memory dimensions - preferred name, communication style, personality, values and goals, motivation and readiness, emotional state, concerns and beliefs, and personal context - are now registered as enrichment keys in every workspace. New workspaces receive them at provisioning; existing workspaces received them through a one-time migration.
* **Same API path for defaults and custom keys.** Default memory dimensions can be read and written through the same enrichment API endpoints as any custom dimension. There is no longer a separate code path or special bypass for writing to default dimensions.
* **System-owned dimensions unchanged.** Clinical state and the consolidated user model remain system-owned and are not writable through the enrichment API. Attempts to create or write to these keys through the enrichment endpoints are rejected.
* **Idempotent, non-destructive seeding.** If a workspace has already customized a default dimension key, the existing configuration is preserved. The seeding inserts only where no registry entry exists.
* **Migration seed bypass removed.** The previous mechanism that allowed writing to default dimensions without a registry entry (used for v1-to-v2 migration seeding) has been removed. All writes now go through the standard registry validation path.

**What you need to do:**

* **No action required.** Default memory dimensions continue to work as before. Workspaces that were already writing to custom dimensions see no change. Integrations that used the migration seed path should switch to the standard enrichment write endpoint, which now resolves default dimensions through the registry like any other key.

</details>

<details>

<summary>Platform API: Patient-Scoped Memory Expansion Tool (July 2026)</summary>

#### Patient-Scoped Memory Expansion Tool <a href="#patient-scoped-memory-expansion-tool" id="patient-scoped-memory-expansion-tool"></a>

The agent can now search the current patient's memory during a conversation, retrieving observation history and past conversation transcripts beyond the summary already in the prompt.

**What changed:**

* **New `expand_memory` tool.** Agents can search two layers of patient memory: the full observation history behind each memory dimension, and prior conversation turns. Results are keyword-filtered, sorted by recency, and capped.
* **Server-bound patient scope.** The patient identity is resolved server-side from the session's caller binding. The agent cannot choose or override which patient is searched. Sessions without a resolved patient return an empty result.
* **Fail-open behavior.** If the data source is slow or unavailable, the tool returns an unavailable status and the conversation continues without the expanded context.
* **Scoped to the agent engine.** This tool is available only within the agent engine and is not exposed through external integration channels.

**What you need to do:**

* **No action required.** The tool is available automatically to agents that have access to patient memory. No API changes, request format changes, or configuration are needed.

</details>

<details>

<summary>Platform API: Version-List Endpoints Scoped to Caller's Workspace (July 2026)</summary>

#### Version-List Endpoints Scoped to Caller's Workspace <a href="#version-list-endpoints-scoped-to-callers-workspace" id="version-list-endpoints-scoped-to-callers-workspace"></a>

Agent version and context graph version endpoints now enforce workspace ownership before returning results, closing a path where a valid but cross-workspace resource identifier could enumerate another workspace's versions.

**What changed:**

* **Agent version list and get.** The list-agent-versions and get-agent-version endpoints now verify that the agent belongs to the caller's workspace before returning version data. A request with an agent identifier from a different workspace receives a `404 Not Found` instead of version results.
* **Context graph version list and get.** The list-context-graph-versions and get-context-graph-version endpoints now verify that the context graph belongs to the caller's workspace before returning version data. The same `404 Not Found` behavior applies.
* **No request or response shape changes.** All request parameters, response fields, pagination, and status codes remain the same for correctly scoped requests.

**What you need to do:**

* **No action required for correctly scoped calls.** If your integration already uses agent and context graph identifiers that belong to the authenticated workspace, behavior is unchanged.
* **Update any cross-workspace tooling.** Automation that passes resource identifiers from one workspace into API calls authenticated against a different workspace will now receive `404` responses.

</details>

<details>

<summary>Platform API: Path-Bearing MCP Resource URIs and SAML SP Base Decoupling (July 2026)</summary>

#### Path-Bearing MCP Resource URIs and SAML SP Base Decoupling <a href="#path-bearing-mcp-resource-uris-and-saml-sp-base-decoupling" id="path-bearing-mcp-resource-uris-and-saml-sp-base-decoupling"></a>

The MCP protected-resource identifier is now a path-bearing URI, and the SAML service provider entity ID can be pinned independently of the token issuer.

**What changed:**

* **Path-bearing MCP resource identifier.** The OAuth 2.1 resource identifier for the world-tools MCP server now includes the endpoint path, making each environment and region's identifier match the URL that clients connect to. Per RFC 9728 §3.1, the protected-resource metadata URL inserts the well-known segment between host and path. Clients that derive the `resource` parameter from the connection URL will match the value the server verifies.
* **Updated discovery location.** The protected-resource metadata endpoint moved to the RFC 9728 §3.1 canonical location for path-bearing resources. The previous origin-root location is no longer served.
* **SAML SP entity ID decoupled from issuer.** The SAML service provider entity ID and assertion consumer URL can now be pinned to a stable base URL independently of the token issuer. This prevents issuer changes - such as regionalization - from silently altering the entity ID that customer identity providers have registered. When no override is configured, the entity ID continues to derive from the issuer.

**What you need to do:**

* **MCP OAuth 2.1 integrations.** If your MCP client hard-codes the resource identifier or the protected-resource metadata URL, update both to use the path-bearing form. Clients that derive `resource=` from the MCP endpoint URL they connect to require no change.
* **SAML federations.** No action is required. Existing SAML federations continue to work. The decoupling prevents future issuer changes from affecting your identity provider configuration.

</details>

<details>

<summary>Platform API: Healthie Inbound Connector (July 2026)</summary>

#### Healthie Inbound Connector <a href="#healthie-inbound-connector" id="healthie-inbound-connector"></a>

The connector framework now supports Healthie as an inbound EHR data source. The connector reads clinical data from Healthie's proprietary API, maps it to FHIR R4 resources, and emits normalized records to the platform - so downstream systems only ever see FHIR.

**What changed:**

* **New Healthie connector.** Workspaces can now configure a Healthie data source that polls patient and appointment data. The connector maps vendor-specific objects to FHIR R4 Patient and Appointment resources before emitting them, consistent with other proprietary-source adapters.
* **Contract verification guard.** The connector remains inert until Healthie access is explicitly verified for the customer's account and the required credentials are configured. Registering the data source before verification is safe and will not trigger polling.
* **Static API key authentication.** Healthie credentials are stored securely and resolved at poll time. No OAuth token exchange or refresh is required.
* **Paginated polling.** The connector paginates through results with configurable stream selection through poll cadence configuration.

**What you need to do:**

* **Contact your Amigo team to enable Healthie access.** The connector requires verified API access on your Healthie account and provisioned credentials before it will begin polling.
* **No action required for existing data sources.** This change adds a new connector type and does not affect other EHR integrations.

</details>

<details>

<summary>Platform API: Version-List Endpoints Scoped to Caller's Workspace (July 2026)</summary>

#### Version-List Endpoints Scoped to Caller's Workspace <a href="#version-list-endpoints-scoped-to-callers-workspace-1" id="version-list-endpoints-scoped-to-callers-workspace-1"></a>

Agent version and context graph version endpoints now enforce workspace ownership before returning results, closing a path where a valid but cross-workspace resource identifier could enumerate another workspace's versions.

**What changed:**

* **Agent version list and get.** The list-agent-versions and get-agent-version endpoints now verify that the agent belongs to the caller's workspace before returning version data. A request with an agent identifier from a different workspace receives a `404 Not Found` instead of version results.
* **Context graph version list and get.** The list-context-graph-versions and get-context-graph-version endpoints now verify that the context graph belongs to the caller's workspace before returning version data. The same `404 Not Found` behavior applies.
* **No request or response shape changes.** All request parameters, response fields, pagination, and status codes remain the same for correctly scoped requests.

**What you need to do:**

* **No action required for correctly scoped calls.** If your integration already uses agent and context graph identifiers that belong to the authenticated workspace, behavior is unchanged.
* **Update any cross-workspace tooling.** Automation that passes resource identifiers from one workspace into API calls authenticated against a different workspace will now receive `404` responses.

</details>

<details>

<summary>Platform API: Canonical Pagination for List Endpoints (July 2026)</summary>

#### Canonical Pagination for List Endpoints <a href="#canonical-pagination-for-list-endpoints" id="canonical-pagination-for-list-endpoints"></a>

All Category-A list endpoints now use a unified pagination contract with opaque continuation tokens and deterministic sort ordering.

**What changed:**

* **Opaque continuation tokens.** All list endpoints now return an opaque `continuation_token` in the response and accept it as a query parameter. Do not parse, construct, or depend on the internal format of this token.
* **Consistent response shape.** Every list endpoint returns `items`, `has_more`, and `continuation_token`. The `total` field has been removed from endpoints that previously included it.
* **Per-endpoint sort\_by parameter.** List endpoints that support sorting now accept repeatable `sort_by` query parameters in the form `+field` (ascending) or `-field` (descending). Each field may appear at most once. Supported fields vary by resource.
* **Deterministic page ordering.** The server appends a stable tiebreaker to every sort order, so results do not shift between pages even when the selected sort fields contain duplicate values.
* **Invalid token handling.** Supplying a corrupted or invalid continuation token now returns `422 Unprocessable Entity` with a message to restart from the first page, instead of an internal server error.

**Affected endpoints** include list operations for agents, agent versions, API keys, context graphs, context graph versions, services, skills, surfaces, data sources, operators, dashboards, scheduling rule sets, external integrations, external write proposals, production eval definitions, simulation cases, source events, outbound sync log, active escalations, escalation events, audit log, billing invoices, and billing customers.

**What you need to do:**

* **Treat continuation tokens as opaque.** If your integration constructs or parses tokens, update it to pass them through unmodified.
* **Remove total-count dependencies.** If you relied on the `total` field for UI pagination controls, switch to the `has_more` flag to determine whether another page exists.
* **Adopt sort\_by if needed.** If you were passing a `sort_by` string parameter, update to the new `+field` / `-field` format. Check each endpoint's documentation for supported sort fields.

</details>

<details>

<summary>Platform API: OAuth Authorization Server Discovery and Updated Model Routing (July 2026)</summary>

#### OAuth Authorization Server Discovery and Updated Model Routing <a href="#oauth-authorization-server-discovery-and-updated-model-routing" id="oauth-authorization-server-discovery-and-updated-model-routing"></a>

The identity service now publishes the RFC 8414 OAuth 2.0 Authorization Server metadata alongside the existing OpenID Connect discovery document, and the platform's routable model set has been expanded.

**What changed:**

* **RFC 8414 discovery endpoint.** The identity service now serves `/.well-known/oauth-authorization-server` in addition to `/.well-known/openid-configuration`. Both paths return the same discovery document, so OAuth 2.0 clients that follow RFC 8414 can locate the authorization server metadata without depending on OpenID Connect conventions.
* **Expanded routable models.** The platform's model routing layer now includes additional model identifiers. All models listed in the developer console model picker are validated end-to-end; the routing sweep no longer allows partial passes when provider credentials are missing.

**What you need to do:**

* **No action required for existing integrations.** The OpenID Connect discovery path continues to work. Clients that prefer the RFC 8414 path can switch to `/.well-known/oauth-authorization-server` at any time.
* **Review model selection.** If you pin a model identifier in your service configuration, confirm it appears in the current model picker. Retired identifiers that are no longer routable will fail at configuration time.

</details>

<details>

<summary>Platform API: External Identity Bindings for Returning Users (July 2026)</summary>

#### External Identity Bindings for Returning Users <a href="#external-identity-bindings-for-returning-users" id="external-identity-bindings-for-returning-users"></a>

External subject keys can now be bound explicitly to world entities so an external-user conversation can load the correct returning-user context without accepting an entity ID from the caller.

**What changed:**

* **New endpoint: `PUT /v1/{workspace_id}/external-identity-bindings`.** Creates or updates the binding between an `external_subject_key` and an `entity_id`. Repeating the same binding is idempotent; attempting to move an active subject key to a different entity returns a conflict.
* **New read endpoints.** `GET /v1/{workspace_id}/external-identity-bindings` lists bindings, and `GET /v1/{workspace_id}/external-identity-bindings/{binding_id}` returns one binding.
* **New revoke endpoint.** `DELETE /v1/{workspace_id}/external-identity-bindings/{binding_id}` revokes the binding so the subject key can be bound again later.
* **Conversation-start resolution.** An external-user conversation resolves the token's stable subject key through this binding. Bound users load the linked entity context; unbound users start without entity context.
* **Principal safety.** Bindings cannot target an entity with an active external role assignment. Conversation start also rejects a binding that later resolves to such a principal.

**What you need to do:**

* **Provision bindings before starting returning-user sessions.** Send the stable subject key in the external-user token and manage its entity mapping through the binding endpoints.
* **Use the binding as the identity source of truth.** Do not rely on a caller-supplied entity ID to resolve returning-user context.

</details>

<details>

<summary>Scribe API: Notes, Summaries, and Checklists (July 2026)</summary>

#### Scribe Notes, Summaries, and Checklists <a href="#scribe-notes-summaries-and-checklists" id="scribe-notes-summaries-and-checklists"></a>

The session-centric Scribe API now supports generating, retrieving, and finalizing additional clinical documentation artifacts.

**What changed:**

* **New note generation endpoint.** `POST /v1/{workspace_id}/sessions/{session_id}/note` generates a draft note from the session transcript. The request selects a supported note type and can include additional instructions.
* **New note finalization endpoint.** `POST /v1/{workspace_id}/sessions/{session_id}/note/finalize` submits the draft note and returns the updated note artifact.
* **New summary endpoints.** `POST /v1/{workspace_id}/sessions/{session_id}/summary` generates a summary, and `GET` on the same path retrieves the current summary.
* **New checklist endpoints.** `POST /v1/{workspace_id}/sessions/{session_id}/checklist` accepts a checklist title and items, evaluates those items against the transcript, and returns their state and supporting evidence. `GET` retrieves the current checklist.
* **Independent artifact retrieval.** Summary and checklist use separate endpoints and response models, so clients can retrieve only the artifact they need.

**What you need to do:**

* **Treat generation as an explicit action.** Call the relevant `POST` endpoint before expecting a note, summary, or checklist to be available. Supply the checklist items you want evaluated when generating a checklist.

</details>

<details>

<summary>Platform API: Event-Based Triggers Now Match Live Workspace Events (July 2026)</summary>

#### Event-Based Triggers Now Match Live Workspace Events <a href="#event-based-triggers-now-match-live-workspace-events" id="event-based-triggers-now-match-live-workspace-events"></a>

Active event-based triggers can now enqueue their configured action when a matching workspace event arrives.

**What changed:**

* **Live event matching.** An active trigger without a cron schedule is matched by its `event_type` and optional `event_filter` when the workspace emits a supported event.
* **Standard run history.** A match enqueues the trigger through the existing run pipeline with `source: "event"`, so status, attempts, results, and errors remain available from the trigger run history endpoint.
* **Duplicate suppression.** Repeated delivery of the same event is collapsed to one run for each event-and-trigger pair.
* **At-most-once event intake.** Matching observes live events only. Events emitted while the matcher is unavailable are not replayed or reconciled later.

**What you need to do:**

* **Review active event triggers.** Existing active definitions can begin running when their configured event arrives.
* **Do not treat event matching as a guaranteed-delivery queue.** Keep a durable source and a separate reconciliation path when every event must be processed.

</details>

<details>

<summary>Platform API: Conversation Starters and Typed Real-Time Voice Controls (July 2026)</summary>

#### Conversation Starters and Typed Real-Time Voice Controls <a href="#conversation-starters-and-typed-real-time-voice-controls" id="conversation-starters-and-typed-real-time-voice-controls"></a>

Services can now return structured starter choices and configure real-time voice sessions through a validated schema.

**What changed:**

* **New endpoint: `POST /v1/{workspace_id}/services/{service_id}/conversation-starters`.** Returns starter chips for a service, optionally personalized with an `entity_id`. The request supports `auto`, `generate`, and `configured` generation modes, an optional deterministic fallback, and a maximum result count from 1 to 10.
* **Starter selection contract.** Each returned starter is intended to become the first user message when the client creates a conversation.
* **New `voice_config.realtime` object.** Real-time voice services can set an approved model, built-in or custom voice, speech speed, noise reduction, transcription, turn detection, reasoning effort, output-token limit, and context truncation behavior.
* **Strict validation.** The `realtime` object requires `session_provider: "gpt_realtime"`. Unknown fields are rejected, incompatible reasoning settings fail validation, and `realtime.voice` cannot be combined with the deprecated `realtime_voice` shortcut.

**What you need to do:**

* **Prefer `voice_config.realtime.voice` over `realtime_voice`.** The shortcut remains readable for compatibility but is deprecated.
* **Handle starter fallback.** A response can contain configured, generated, or deterministic starters depending on service configuration and request mode.

</details>

<details>

<summary>Platform API: Evaluation Quality Trends and Conversation-Level Simulation Verdicts (July 2026)</summary>

#### Evaluation Quality Trends and Conversation-Level Simulation Verdicts <a href="#evaluation-quality-trends-and-conversation-level-simulation-verdicts" id="evaluation-quality-trends-and-conversation-level-simulation-verdicts"></a>

Quality analytics now support channel-neutral trends and direct drill-down from a simulation metric to the conversations behind it.

**What changed:**

* **New endpoint: `GET /v1/{workspace_id}/analytics/eval-quality`.** Returns workspace-wide pass rate and score, per-evaluation-key aggregates, and a time-series trend across voice, text, SMS, and email.
* **Conversation-level metric results.** Simulation performance metrics now include the individual conversation verdicts behind each aggregate, ordered with failures first for review.
* **Normalized checks view.** Each simulation metric includes a consistent checks projection for clients that render lexical, model-judge, and other verdict types together.
* **Human-readable labels.** Metric responses use the evaluation definition's display name when available and fall back to the evaluation key.

**What you need to do:**

* **No action required.** Existing aggregate fields remain available. Use the new results and checks fields to add conversation drill-down.

</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-04.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.
