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

# API History: Archive 3

Retained API history, archive 3, 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: Zoom Bot Request Parity with BFF (July 2026)</summary>

#### Zoom Bot Request Parity with BFF <a href="#zoom-bot-request-parity-with-bff" id="zoom-bot-request-parity-with-bff"></a>

The Scribe API's Zoom session creation now sends a fully explicit bot dispatch request, matching the field set sent by the proven web BFF path. This improves reliability of meeting join by sending parsed meeting fields directly rather than relying on the control plane to re-parse them.

**What changed:**

* **Explicit meeting fields sent on bot dispatch.** When the Scribe API dispatches a Zoom bot, the request now includes explicitly parsed meeting number and meeting password fields (when available from the meeting link), along with explicit display name, maximum duration, and capture backend values. Previously, the request sent only the raw meeting link and relied on the control plane to extract these fields.
* **Personal room handling preserved.** Personal-room Zoom URLs (vanity URLs) do not carry a numeric meeting ID. For these links, the meeting number field is omitted and the meeting link remains the control plane's backstop for resolving the meeting - matching the behavior of the web BFF.
* **No changes to the Scribe API contract.** The Zoom session creation endpoint request and response shapes are unchanged. This is an improvement to the internal dispatch reliability, not a change to the public API surface.

**What you need to do:**

* **No action required.** Zoom session creation calls the same endpoint with the same request and response. Bot join reliability may improve, particularly for meeting links that include password parameters.

</details>

<details>

<summary>Scribe: Generic Session Create Scoped to In-Person Only (July 2026)</summary>

#### Generic Session Create Scoped to In-Person Only <a href="#generic-session-create-scoped-to-in-person-only" id="generic-session-create-scoped-to-in-person-only"></a>

The generic create-session endpoint now accepts only in-person sessions. Zoom sessions must be created through the dedicated Zoom session endpoint, which handles credential resolution, row creation, and bot dispatch as a single operation.

**What changed:**

* **In-person only on generic create.** The `POST /v1/{workspace_id}/sessions` endpoint now accepts only `in_person` as the session mode. The published API contract (OpenAPI and SDK) advertises `in_person` as the only allowed value.
* **New `use_zoom_endpoint` error.** Requesting a non-in-person mode on the generic create endpoint now returns a 422 with error code `use_zoom_endpoint` and a message directing callers to `POST /v1/{workspace_id}/zoom/sessions`. Previously, the endpoint accepted `zoom` but sessions created this way lacked bot dispatch and could not function.
* **`active_zoom_session_exists` conflict removed from generic create.** The 409 `active_zoom_session_exists` error is no longer returned by the generic create endpoint, because Zoom sessions are no longer created there. The Zoom session endpoint continues to enforce the per-practitioner active-session constraint.
* **Zoom session endpoint unchanged.** The dedicated Zoom session endpoint (`POST /v1/{workspace_id}/zoom/sessions`) remains the correct way to start a Zoom session. Its behavior, error codes, and bot dispatch logic are unchanged.

**What you need to do:**

* **Use the Zoom session endpoint for Zoom sessions.** If your integration previously passed `mode: "zoom"` to the generic create endpoint, switch to `POST /v1/{workspace_id}/zoom/sessions`.
* **Handle the `use_zoom_endpoint` error code.** If your integration sends a non-in-person mode to the generic create endpoint, it will now receive a 422 with code `use_zoom_endpoint`. Update your error handling to direct users to the Zoom session endpoint.
* **Remove `active_zoom_session_exists` handling from generic create responses.** This error code is no longer returned by the generic create endpoint. It remains relevant only for the Zoom session endpoint.

</details>

<details>

<summary>Scribe: Zoom OAuth Token Auto-Refresh and Reconnect Detection (July 2026)</summary>

#### Zoom OAuth Token Auto-Refresh and Reconnect Detection <a href="#zoom-oauth-token-auto-refresh-and-reconnect-detection" id="zoom-oauth-token-auto-refresh-and-reconnect-detection"></a>

Zoom connections now proactively refresh expiring credentials during status polling and surface a clear reconnect signal when the stored credential is terminally unusable.

**What changed:**

* **Proactive token refresh on status poll.** The connection-status endpoint now refreshes a near-expiry Zoom credential server-side before returning status, so a connected-but-idle workspace never lets its token lapse between sessions. Previously, refresh occurred only at session dispatch, which could leave an idle connection expired.
* **Reconnect detection.** When Zoom permanently rejects a stored credential (revoked or expired refresh token), the connection is flagged as requiring reconnect. The status endpoint surfaces this state so the client can prompt the provider to re-authorize, rather than silently failing at session start.
* **New `reconnect_required` field on connection status.** The Zoom connection status response now includes a `reconnect_required` boolean (defaults to `false`). When `true`, the provider must complete a new OAuth connect flow before starting a Zoom session.
* **New `zoom_reconnect_required` conflict response.** Starting a Zoom session with a terminally unusable credential now returns a 409 with code `zoom_reconnect_required`, distinct from the existing `zoom_not_connected` code. This tells the caller the connection exists but must be re-authorized.
* **New `zoom_refresh_unavailable` service-unavailable response.** A transient inability to refresh the credential (for example, a temporary Zoom outage or contention with a concurrent refresh) returns a retryable 503 with code `zoom_refresh_unavailable` instead of failing the session permanently.
* **Concurrent refresh serialization.** When multiple requests attempt to refresh the same connection simultaneously, they are serialized so only one refresh is sent to Zoom. Subsequent requests use the freshly rotated credential rather than racing and potentially invalidating each other's tokens.
* **Wider refresh buffer.** The token refresh window has been widened so credentials are rotated well before expiry, reducing the chance of a dispatch attempt hitting an expired token.

**What you need to do:**

* **Handle the `reconnect_required` field.** If you poll the Zoom connection status endpoint, check the new `reconnect_required` boolean. When `true`, prompt the provider to reconnect Zoom through the OAuth flow.
* **Handle `zoom_reconnect_required` on session start.** If your integration starts Zoom sessions, add handling for the 409 `zoom_reconnect_required` error code. This is distinct from `zoom_not_connected` and indicates the provider must re-authorize.
* **Retry on `zoom_refresh_unavailable`.** A 503 with code `zoom_refresh_unavailable` is transient. Retry the request after a short delay.

</details>

<details>

<summary>Scribe: Automatic Note Generation for In-Person Mic Sessions (July 2026)</summary>

#### Automatic Note Generation for In-Person Mic Sessions <a href="#automatic-note-generation-for-in-person-mic-sessions" id="automatic-note-generation-for-in-person-mic-sessions"></a>

In-person (mic) sessions that reach the review stage now automatically receive post-visit note generation, closing a gap where these sessions could complete without producing a clinical note.

**What changed:**

* **Note generation now enqueued for in-person sessions.** Previously, in-person mic sessions that ended cleanly could reach the review stage without a note generation job being created. Other session types - such as integrated video sessions, REST-ended sessions, and sessions finalized by the background cleanup process - already triggered note generation on transition to review. In-person mic sessions now follow the same path.
* **Backfill for recent sessions.** A periodic background pass discovers recently-completed review-stage sessions that have no pending note generation and enqueues one. The pass is bounded to a recent time window so it covers transient failures and recent sessions without reprocessing older history.
* **Idempotent enqueue.** Sessions that already have a note generation job are excluded from the backfill scan. A session is never double-enqueued regardless of how it reached the review stage.
* **Best-effort per session.** If a session cannot be enqueued in a given pass - for example, because clinical generation is not configured for that provider or no transcript is available yet - it is logged and retried on subsequent passes until it ages out of the backfill window.

**What you need to do:**

* **No action required.** In-person mic sessions that previously required manual intervention to produce a note will now generate one automatically. The note content, format, and retrieval endpoints are unchanged.

</details>

<details>

<summary>Scribe: Transcript Retrieval Resilience Improvement (July 2026)</summary>

#### Transcript Retrieval Resilience Improvement <a href="#transcript-retrieval-resilience-improvement" id="transcript-retrieval-resilience-improvement"></a>

The transcript retrieval endpoint is now more resilient to configuration changes that previously caused finalized transcripts to become temporarily unservable.

**What changed:**

* **Transcript served from stored reference.** The endpoint that returns a finalized session transcript now reads from the storage reference recorded when the transcript was written, rather than recomputing an expected reference at read time. Previously, if the storage layout changed after a transcript was finalized, the endpoint could return a 503 even though the transcript data was fully available. This affected sessions finalized under an earlier storage layout.
* **Transient unavailability scoped to genuine cases.** A 503 response is now returned only when the transcript data has not yet been durably written - for example, immediately after finalization completes but before the data is fully visible. Previously, the 503 could also fire for fully written transcripts whose stored reference did not match the recomputed reference.

**What you need to do:**

* **No action required.** This is a resilience fix. Callers that previously received spurious 503 responses for finalized transcripts should now receive the transcript data successfully. The endpoint contract - including the 503 for genuinely pending transcripts - is unchanged.

</details>

<details>

<summary>Platform API: Voice Turn Accept Header Content Negotiation (July 2026)</summary>

#### Voice Turn Accept Header Content Negotiation <a href="#voice-turn-accept-header-content-negotiation" id="voice-turn-accept-header-content-negotiation"></a>

The voice-turn endpoint now supports content negotiation via the `Accept` header, allowing callers to receive the agent's spoken reply as WAV PCM16 instead of the default OGG Opus.

**What changed:**

* **WAV output format.** Callers can set the `Accept` header to `audio/wav` to receive the agent's spoken reply as a WAV PCM16 file (16 kHz, mono, 16-bit). This is useful for mobile clients that cannot natively decode OGG Opus - for example, iOS AVPlayer.
* **Content negotiation.** The endpoint compares the q-values of `audio/wav` and `audio/ogg` in the request's `Accept` header. If `audio/wav` has a higher q-value, the response is WAV; otherwise, the response remains OGG Opus. Missing, wildcard, or unrecognized `Accept` values default to OGG Opus, so existing callers are unaffected.
* **Response Content-Type.** The response `Content-Type` header reflects the negotiated format: `audio/ogg` or `audio/wav`.
* **204 and error responses unchanged.** Silence (204) and error responses behave the same regardless of the requested format.

**What you need to do:**

* **No action required for existing integrations.** The default output format remains OGG Opus. Existing callers that do not send an `Accept` header continue to work without changes.
* **Send `Accept: audio/wav` for WAV output.** If your client needs WAV PCM16 (for example, for native iOS playback), include `Accept: audio/wav` in the voice-turn request.

</details>

<details>

<summary>Scribe: Assist Features - Action Items, Ask, Auto-Check, and Section Regeneration (July 2026)</summary>

#### Assist Features - Action Items, Ask, Auto-Check, and Section Regeneration <a href="#assist-features-action-items-ask-auto-check-and-section-regeneration" id="assist-features-action-items-ask-auto-check-and-section-regeneration"></a>

Scribe sessions now support four new assist capabilities: generated action items, streaming Q\&A, automatic checklist verification, and per-section note regeneration.

**What changed:**

* **Action items.** Post-visit action items (follow-ups, referrals, labs, imaging, medication, patient education) are now generated automatically when a note succeeds and can also be triggered explicitly. Each item carries a short category and actionable text grounded in the transcript and clinical note. A reload-safe polling endpoint returns the current generation state, matching the contract used by other artifacts.
* **Streaming Ask (Q\&A).** A new endpoint accepts a clinician's question and optional conversation history, then streams the answer back as Server-Sent Events (`delta` frames with text, a terminal `done` frame with the generation identifier, and periodic `ping` keepalives). Answers are grounded in the session transcript and latest note. Ask is available on terminal sessions for post-visit review. Only a provenance record is persisted - the answer is not stored as an artifact.
* **Automatic checklist verification.** A new endpoint evaluates the generated checklist against the current transcript and returns a per-item match verdict with optional evidence text. Matched items are persisted as automatic checklist state that coexists with manual toggles - a manually-decided item is never overwritten by an automatic check.
* **Section regeneration.** A new endpoint regenerates a single named section of the current note as a new version. The request carries the note version the client last read; a stale version is rejected with a conflict response up front, and the worker re-checks it at persist time so a concurrent manual edit is never silently overwritten. The regenerated note preserves all other sections and carries forward confirmed structured fields. Section regeneration is rejected once the session is terminal.

**What you need to do:**

* **No action required for existing integrations.** Action items are auto-generated alongside summaries and codes when a note succeeds. The new endpoints are additive.
* **Adopt the new endpoints to surface assist features.** Use the action-items, Ask, auto-check, and section-regeneration endpoints to build assist workflows into your clinical UI.

</details>

<details>

<summary>Platform: Simulation Step Timeout Increased (July 2026)</summary>

#### Simulation Step Timeout Increased <a href="#simulation-step-timeout-increased" id="simulation-step-timeout-increased"></a>

The timeout for individual simulation steps has been increased to accommodate slow tool execution during simulated conversations.

**What changed:**

* **Longer simulation step budget.** The platform now allows more time for each simulation turn to complete. Simulations that exercise warehouse-backed tools or other data-heavy tool calls are less likely to be cancelled prematurely before the agent produces a transcript.
* **Bridge timeout aligned.** The internal coordination timeout between simulation components has been raised above the per-step budget, ensuring that a slow tool turn can finish and return its structured result rather than being interrupted by an earlier layer.

**What you need to do:**

* **No action required.** Simulations that previously timed out on data-heavy first turns should now complete normally. There are no API or configuration changes.

</details>

<details>

<summary>Platform API: Workspace Provision Endpoint Reschedules Scope Provisioner (July 2026)</summary>

#### Workspace Provision Endpoint Reschedules Scope Provisioner <a href="#workspace-provision-endpoint-reschedules-scope-provisioner" id="workspace-provision-endpoint-reschedules-scope-provisioner"></a>

The workspace provision endpoint now reschedules the workspace-scope data provisioner in addition to marking the workspace as provisioned.

**What changed:**

* **Scope provisioner rescheduled on provision.** Calling the provision endpoint now triggers the workspace-scope data provisioner on a best-effort basis. Previously, the scope provisioner ran only during workspace creation, so workspaces created before eager provisioning was available - or workspaces whose initial scope trigger was missed - had no self-serve way to backfill the serving layer.
* **Idempotent and safe to repeat.** The scope provisioner is idempotent. Operators can call the provision endpoint again on existing or partially-provisioned workspaces to backfill scope without risk of duplication or side effects.
* **Best-effort, non-blocking.** The scope provisioner runs asynchronously and does not delay the provision response or surface errors into it. The endpoint continues to return the same response shape as before.

**What you need to do:**

* **No action required for new workspaces.** New workspaces provisioned through the standard flow are unaffected.
* **Re-provision to backfill.** If you have workspaces that were created before eager provisioning or that appear to be missing their serving layer, call the provision endpoint again to trigger the scope provisioner.

</details>

<details>

<summary>Platform: World Model Workspace-Scoped Entity Projection (July 2026)</summary>

#### World Model Workspace-Scoped Entity Projection <a href="#world-model-workspace-scoped-entity-projection" id="world-model-workspace-scoped-entity-projection"></a>

Entity projection now enforces full workspace isolation at every stage, preventing cross-workspace data leakage when the same entity identifier exists in more than one workspace.

**What changed:**

* **Workspace-scoped projection.** Entity snapshot materialization now keys every stage - dirty-entity tracking, event scoping, enrichment overlay, and snapshot writes - on the combination of workspace and entity identifier rather than entity identifier alone. Previously, if the same entity identifier appeared in two workspaces, a change in one workspace could pull in the other workspace's events and enrichment data, producing incorrect snapshots or failing the write operation.
* **Isolated deletes.** When an entity's events are fully removed, the cleanup targets only that entity within its own workspace. Entities with the same identifier in other workspaces are unaffected.
* **Applies to all projection feeds.** The fix covers event-change, enrichment-change, and reconciliation feeds that drive entity materialization.

**What you need to do:**

* **No action required.** This is a correctness fix. Workspaces that share entity identifiers will now project independently. If you previously observed unexpected data in entity projections for multi-workspace deployments, those issues should no longer occur.

</details>

<details>

<summary>Platform: Scheduled Workspace Topic Classification (July 2026)</summary>

#### Scheduled Workspace Topic Classification <a href="#scheduled-workspace-topic-classification" id="scheduled-workspace-topic-classification"></a>

Conversation topic classification can now run on a recurring schedule across all enabled workspaces, without manual triggering.

**What changed:**

* **Automatic workspace discovery.** The platform periodically discovers workspaces that have the durable topic-modeling setting enabled, resolves each workspace's active taxonomy and service-derived domain context, and classifies recent conversations against the taxonomy.
* **Per-workspace isolation.** Each enabled workspace is evaluated independently. A workspace that is enabled but misconfigured - for example, missing an active taxonomy or lacking an active service with a non-blank name - is quarantined with a recorded reason rather than blocking classification for every other tenant.
* **Quarantine visibility.** Skipped workspaces and their skip reasons are persisted for operational triage, so misconfigurations surface rather than disappearing silently.
* **Rate limit adjustment.** The per-workspace daily classification call limit has been lowered from 20,000 to 2,000 to align with scheduled execution cadence.

**What you need to do:**

* **Enable topic modeling in workspace settings.** Workspaces with the topic-modeling setting enabled and a single active taxonomy plus at least one active service will be classified automatically.
* **Review skipped workspace reports.** If a workspace is enabled but does not appear in classification results, check the quarantine records for the skip reason and correct the configuration.

</details>

<details>

<summary>Platform: World Model Pipeline Event Log (July 2026)</summary>

#### World Model Pipeline Event Log <a href="#world-model-pipeline-event-log" id="world-model-pipeline-event-log"></a>

World-model pipeline runs now publish event logs to a queryable table, giving operators visibility into processing activity.

**What changed:**

* **Pipeline event log.** World-model data pipelines now emit structured event logs that are queryable through the platform's analytics layer. This supports operational monitoring, debugging, and triage of pipeline behavior.

**What you need to do:**

* **No action required.** Pipeline event logs are published automatically. Use the queryable event log for operational visibility into world-model processing.

</details>

<details>

<summary>Platform API: Authorization-Bound Test Values in Simulations (July 2026)</summary>

#### Authorization-Bound Test Values in Simulations <a href="#authorization-bound-test-values-in-simulations" id="authorization-bound-test-values-in-simulations"></a>

Simulations can now exercise SQL tools that depend on authorization-bound parameters using their configured per-parameter test values.

**What changed:**

* **Authorization-scoped test values enabled.** Simulation sessions, bridge runs, and benchmark cases now propagate an authorization-binding test-value flag alongside the existing identity-binding flag. When the launching workspace actor holds the data-query permission, authorization-bound SQL tool parameters may use their configured playground test values during the simulation.
* **Permission gating.** The flag is server-derived from the launching actor's role - it is not caller-controlled. The required permission is the same grant that the text playground uses for data queries, ensuring consistent access control between interactive testing and automated simulations.
* **Applies across simulation types.** The capability works for single simulation sessions, bridge runs (sync and async), individual case runs, and full benchmark suites.

**What you need to do:**

* **No action required for most users.** If your workspace actor already has data-query permission and your SQL tools have configured test values, simulations will use them automatically.
* **Review SQL tool test values.** If you have not configured per-parameter test values for authorization-bound SQL tool parameters, simulations will continue to behave as before. Add test values through the tool configuration to take advantage of this capability.

</details>

<details>

<summary>Scribe API: Post-Visit Mutations, Note Versioning, and Finalize Gates (July 2026)</summary>

#### Post-Visit Mutations, Note Versioning, and Finalize Gates <a href="#post-visit-mutations-note-versioning-and-finalize-gates" id="post-visit-mutations-note-versioning-and-finalize-gates"></a>

Scribe sessions now support versioned note editing, per-code provider decisions, manual checklist toggles, and server-authoritative finalize validation - the post-visit mutation surface that lets a provider review and refine generated artifacts before signing.

**What changed:**

* **Versioned note autosave.** `PUT /sessions/{id}/note` writes note content with optimistic concurrency. The client sends the `base_version` it last read; a stale version returns 409 `version_conflict`. Exactly one of `body` or `structured` is accepted per write. The response includes the new `version` and `updated_at` timestamp.
* **Note version field on reads.** `GET /sessions/{id}/note` and artifact read responses now include a `version` field (integer, starting at 1) when a note is available. Clients echo this value as `base_version` on subsequent writes and finalize requests.
* **Server-authoritative finalize with gates.** `POST /sessions/{id}/note/finalize` now requires a `base_version` in the request body. The server validates required-field gates on the stored document before submitting - a note with an empty body or unconfirmed structured fields returns 422 `finalize_validation_failed` with per-field `details`. A stale `base_version` returns 409 `version_conflict`. An already-submitted note remains an idempotent 200.
* **Per-code provider decisions.** `PATCH /sessions/{id}/codes/{suggestion_id}` records an `approved` or `rejected` decision on an individual ICD suggestion. Decisions are idempotent and re-decidable until the session is finalized. The response includes the suggestion `id`, `code`, `decision`, and `decided_at`. Code suggestion reads now include the suggestion `id` and a `decision` field (null until decided) instead of the previous `status` field.
* **Manual checklist toggles.** `PATCH /sessions/{id}/checklist` accepts per-item toggle state (`completed`, `source`). Each toggled item is an idempotent upsert with provenance (who toggled, when, and source). Unknown item IDs return 422 with per-field details. The response returns the full checklist with manual state overlaid on the generated items, and checklist status recomputed from effective item states.
* **Checklist reads reflect manual state.** `GET /sessions/{id}/checklist` now overlays persisted manual toggle state and provenance onto the generated checklist items, so a page reload reflects previous toggles. Items that have never been toggled show null provenance.
* **Append-only immutability on terminal sessions.** All mutation endpoints (`PUT /note`, `PATCH /codes/{suggestion_id}`, `PATCH /checklist`, and generate/regenerate requests) reject with 409 `invalid_session_state` once a session reaches a terminal state. Finalize is excluded from this guard because it is the transition into the terminal state.

**What you need to do:**

* **Custom integrations** should update note write and finalize calls to include `base_version` and handle 409 `version_conflict` by reloading the note and retrying. Update finalize calls to send a request body with `base_version` and handle 422 `finalize_validation_failed` responses. Parse the new `version` field from note reads.
* **Code suggestion integrations** should use `PATCH /codes/{suggestion_id}` to record provider decisions and read the `id` and `decision` fields from code suggestion responses instead of the previous `status` field.
* **Checklist integrations** should use `PATCH /checklist` to persist manual toggles and expect the updated item shape with provenance fields (`source`, `updated_by`, `updated_at`) on both reads and writes.
* **SDK users: no action required.** The web UI handles the new flows automatically.

</details>

<details>

<summary>Scribe API: Async Generation Orchestration (July 2026)</summary>

#### Async Generation Orchestration <a href="#async-generation-orchestration" id="async-generation-orchestration"></a>

Clinical generation (notes, summaries, checklists, and ICD-10 codes) is now fully asynchronous. Generate requests enqueue a durable job and return immediately; a background worker runs the model call and persists the result. Artifact GET endpoints are reload-safe pollers that reflect the current job state.

**What changed:**

* **Generate endpoints return 202 (Accepted).** `POST /sessions/{id}/note`, `POST /sessions/{id}/summary`, `POST /sessions/{id}/checklist`, and `POST /sessions/{id}/codes` now enqueue a generation job and return 202 with a job envelope (`id`, `artifact_kind`, `status`). If an identical job already succeeded, the endpoint returns 200 with the completed artifact instead. The model call no longer blocks the HTTP request.
* **Idempotent job creation.** Concurrent identical requests - duplicate tabs, reloads, retries - collapse onto a single job. A failed job is excluded from the idempotency window, so an explicit regenerate request after a failure creates a fresh job.
* **Artifact GETs are reload-safe pollers.** `GET /sessions/{id}/note`, `GET /sessions/{id}/summary`, `GET /sessions/{id}/checklist`, and `GET /sessions/{id}/codes` now return a `generation_status` field (`ready`, `pending`, or `failed`) alongside the artifact data. When the job is still pending or has failed, the response includes the status (and an error detail for failures) without a bare 404. A 404 is returned only when no generation was ever enqueued for that artifact.
* **Automatic generation on session review.** When a session transitions to the in-review state (via end, Zoom finalization, or stale-session cleanup), note generation is automatically enqueued server-side. Summary and code generation are then auto-enqueued when the note job succeeds. The browser does not need to trigger these.
* **Durable job state.** Generation jobs are persisted with their status (`pending`, `succeeded`, `failed`). A job that fails due to a provider or output error is marked failed and surfaced through the GET endpoints with a typed error. Transient infrastructure failures leave the job pending for automatic retry on the next worker cycle.
* **Generation response models updated.** The artifact GET endpoints now use response models that include `generation_status`, an optional `error` detail, and the artifact fields (present only when `generation_status` is `ready`).

**What you need to do:**

* **SDK users: no action required.** The web UI handles the asynchronous flow automatically.
* **Custom integrations** should update generate calls to expect 202 with a job envelope and poll the corresponding GET endpoint until `generation_status` is `ready` or `failed`. Handle the 200 fast path for already-completed identical jobs. Update response parsing to use the new response shape that includes `generation_status`.

</details>

<details>

<summary>Scribe API: Zoom Session Live Event Stream (July 2026)</summary>

#### Zoom Session Live Event Stream <a href="#zoom-session-live-event-stream" id="zoom-session-live-event-stream"></a>

Live Zoom sessions now expose a real-time event stream so the browser can follow bot lifecycle changes and transcript progress without polling.

**What changed:**

* **New endpoint: `GET /sessions/{id}/events`.** Opens a Server-Sent Event stream for an active Zoom session. The stream emits `bot_status` frames (the bot's lifecycle state - joining, listening, paused, leaving, done, error, and others), `transcript_segment` and `interim_transcript` frames (finalized and in-progress utterances with ordinal, speaker, text, and timestamp), a `transcript_finalized` frame when the transcript is complete, and a `ping` keepalive. The stream closes automatically after a terminal `bot_status` (`done` or `error`).
* **Reconnect with replay.** Clients may pass a `Last-Event-ID` header to resume from where they left off. The server replays recent events so no frames are lost across reconnects.
* **Header-authenticated streaming.** The stream uses the same provider-JWT authentication as other Scribe endpoints. It is consumed via `fetch` streaming, not the browser `EventSource` API, because it requires an `Authorization` header.
* **Error behavior.** Returns 404 for a non-Zoom, unknown, terminal, or bot-less session. Returns 401/403 for authentication or authorization failures. If the upstream event source becomes temporarily unavailable, the stream emits a terminal `bot_status` error frame and closes so the client can reconnect.
* **Frame format.** Each frame is delivered as `event: <type>\ndata: <json>\n\n`. Event IDs are preserved across the relay for client-side deduplication.

**What you need to do:**

* **SDK users: no action required.** The web UI connects to the event stream automatically.
* **Custom integrations** can open a streaming `GET` to `/sessions/{id}/events` with the session's auth token. Parse the SSE frames by event type. Store the last received event ID and include it as `Last-Event-ID` on reconnect. Stop reconnecting after receiving a terminal `bot_status` (`done` or `error`).

</details>

<details>

<summary>Scribe API: Zoom Session Lifecycle and Bot Controls (July 2026)</summary>

#### Zoom Session Lifecycle and Bot Controls <a href="#zoom-session-lifecycle-and-bot-controls" id="zoom-session-lifecycle-and-bot-controls"></a>

The Scribe API now handles the entire Zoom capture lifecycle server-side. A single endpoint creates the session, resolves the provider's stored Zoom credential, and dispatches the capture bot - the browser never handles Zoom tokens or talks to any internal service directly. Pause, resume, and end controls are also exposed through the Scribe API.

**What changed:**

* **New endpoint: `POST /zoom/sessions`.** Runs the full Zoom capture flow in one call. The caller provides a meeting link and disclosure preferences; the server validates the link, checks that the provider has a connected Zoom account, creates the session (`mode=zoom`, `in-progress`), and dispatches the capture bot. Returns the created session and the bot identifier (201). If bot dispatch fails, the session is automatically cancelled and any straggler resources are cleaned up before returning a retryable 503 (`bot_dispatch_failed`). Supports idempotency via `external_id` - a duplicate request whose bot is already running recovers the existing bot rather than dispatching a second one.
* **New endpoint: `POST /sessions/{id}/zoom/pause`.** Pauses the capture bot bound to the session. Returns the bot's status at command time. 409 `bot_not_active` when no live bot is bound; 502 `bot_command_failed` on a delivery failure.
* **New endpoint: `POST /sessions/{id}/zoom/resume`.** Resumes a paused capture bot. Same error contract as pause.
* **New endpoint: `DELETE /sessions/{id}/zoom`.** Ends a Zoom session. Returns 202 with `{status: "draining"}` immediately; the platform stops the bot and finalizes the transcript in the background. The session transitions to `in-review` once finalization completes. Idempotent - a second delete is a no-op. 409 `not_zoom_session` for a non-Zoom session.
* **Validation.** The meeting link must be a valid Zoom URL (`zoom.us` domain, `http` or `https`). Invalid links are rejected with 400 `invalid_meeting_link` before any session is created.
* **Pre-checks.** The endpoint checks that the provider has connected Zoom (409 `zoom_not_connected`) and does not already have an active Zoom session (409 `active_zoom_session_exists`) before creating the session, so no orphan sessions are left behind on a rejected request.
* **Disclosure configuration.** The request body includes a disclosure object (`enabled` flag and optional `audio_key`) that controls whether the bot plays a recording-disclosure clip when joining the meeting.
* **Removed endpoint: `POST /sessions/{id}/zoom-finalize`.** The previous external finalize endpoint has been replaced by the internal background finalization triggered by `DELETE /sessions/{id}/zoom`. Callers no longer need to coordinate finalization manually.

**What you need to do:**

* **SDK users: no action required.** The web UI handles the new Zoom session flow automatically.
* **Custom integrations** should use `POST /zoom/sessions` to start a Zoom capture, `POST /sessions/{id}/zoom/pause` and `resume` to control recording, and `DELETE /sessions/{id}/zoom` to end the session. Remove any calls to the previous `zoom-finalize` endpoint.

</details>

<details>

<summary>Scribe API: Zoom OAuth Granular Scopes (July 2026)</summary>

#### Zoom OAuth Granular Scopes <a href="#zoom-oauth-granular-scopes" id="zoom-oauth-granular-scopes"></a>

The Zoom connect flow now requests granular OAuth scopes, matching Zoom's current scope model. This resolves "Invalid scope" errors that could occur during the Zoom consent screen and adds the scopes required for real-time media capture.

**What changed:**

* **Granular OAuth scopes.** The Zoom authorization request now uses Zoom's granular scope format instead of the deprecated classic scope names. This change is transparent to callers - the authorize endpoint, callback behavior, and connection status responses are unchanged.
* **Scope coverage.** The requested scopes now cover reading the connected account's profile (used to display the associated email on the status card), reading meeting details, creating meetings, starting and stopping real-time media capture on a meeting, and receiving the meeting's audio stream. Audio is the only media type requested - transcript, video, chat, and screen-share scopes are not included because the platform produces its own transcript from the audio.
* **RTMS capture scope.** Without the real-time media capture scope on the provider's token, attempts to start the media stream were rejected by Zoom. This scope is now included in the consent flow, so new and re-authorized connections can start media capture without errors.

**What you need to do:**

* **Existing providers should re-authorize.** Providers who connected Zoom before this change will not have the new scopes on their stored token. They should disconnect and reconnect their Zoom account through the connect flow to pick up the additional scopes. Without re-authorization, real-time media capture for their meetings may be rejected by Zoom.
* **New connections require no extra steps.** The updated consent screen requests all necessary scopes automatically.

</details>

<details>

<summary>Scribe API: Zoom Integration Connect and Disconnect (July 2026)</summary>

#### Zoom Integration Connect and Disconnect <a href="#zoom-integration-connect-and-disconnect" id="zoom-integration-connect-and-disconnect"></a>

Providers can now connect and disconnect their Zoom account from the Scribe settings. The platform manages the full OAuth lifecycle server-side - the browser never handles or stores token material.

**What changed:**

* **New endpoint: `POST /zoom/oauth/authorize`.** Begins the Zoom connect flow. Returns a short-lived authorization URL and its expiration time. The browser navigates to this URL to start the Zoom consent screen. The server generates a single-use state parameter with a short TTL and a PKCE challenge, so the flow is protected against replay and interception. Requires `scribe:sessions:write` scope.
* **New endpoint: `GET /zoom/oauth/callback`.** Unauthenticated redirect target for the Zoom consent flow. Validates the single-use state, exchanges the authorization code for tokens server-side, encrypts the tokens at rest, and stores the connection. Every outcome - success or failure - redirects to the web settings page with a status flag. No token material is ever returned to the browser. No JSON is rendered on this endpoint.
* **New endpoint: `GET /zoom/connection`.** Returns whether the calling provider has a connected Zoom account, along with the associated email address and connection timestamp when connected. Never returns token material.
* **New endpoint: `DELETE /zoom/connection`.** Disconnects the provider's Zoom account. Revokes the token at Zoom on a best-effort basis, then deletes the stored connection. Idempotent - disconnecting a non-existent connection returns 204.
* **Token custody.** Zoom credentials are encrypted at rest using a platform-managed key. Tokens are resolved only server-side for operations such as revocation and are never exposed through any API response.
* **PKCE and single-use state.** The authorize flow uses S256 PKCE and a server-held, single-use state parameter with a short TTL. The state is consumed atomically on callback, preventing replay. If the state store is unavailable, the authorize endpoint fails closed (503) rather than issuing an unverifiable URL.

**What you need to do:**

* **SDK users: no action required.** The web settings UI handles the connect and disconnect flows automatically.
* **Custom integrations** can call `POST /zoom/oauth/authorize` to start the connect flow, poll `GET /zoom/connection` to check status, and call `DELETE /zoom/connection` to disconnect. The callback endpoint is called by Zoom's redirect and should not be called directly.

</details>

<details>

<summary>Scribe API: Attach-Ticket Endpoint and CORS Hardening (July 2026)</summary>

#### Attach-Ticket Endpoint and CORS Hardening <a href="#attach-ticket-endpoint-and-cors-hardening" id="attach-ticket-endpoint-and-cors-hardening"></a>

The Scribe API now exposes a ticket-minting endpoint that the SDK calls before every WebSocket (re)connect, and the CORS configuration has been tightened for credentialed cross-origin requests.

**What changed:**

* **New endpoint: `POST /sessions/{session_id}/ticket`.** Returns a short-lived attach ticket (`ticket` and `expires_at`) that the SDK presents at the streaming WebSocket handshake. The endpoint forwards the caller's provider token to the platform identity service via a server-side token exchange, so the browser's cross-origin surface stays limited to a single service. The ticket is scoped to the session, workspace, and provider with a roughly five-minute lifetime.
* **Authentication and authorization.** The caller must present a valid bearer token with the `scribe:sessions:write` scope and must own the target session. Requests against terminal sessions return `409 Conflict`.
* **Workspace-level rate limiting.** A sliding-window rate limit bounds ticket mints per workspace, preventing a reconnect storm from amplifying into the identity service. When the limit is exceeded, the endpoint returns `503 Service Unavailable` with a `Retry-After` header - the same retryable backoff contract the SDK already follows for session allocation.
* **Transient failure handling.** If the identity service is unreachable, returns a non-success status, or returns a malformed response, the endpoint returns `503` with a jittered `Retry-After` value to avoid thundering-herd retries. Identity-rejected provider tokens return `403`.
* **CORS origin regex anchored.** The allowed-origin pattern for production subdomains is now fully anchored, preventing potential suffix-match against hostile origins.
* **Additional CORS methods.** `PATCH` and `DELETE` are now permitted in preflight responses, preparing for upcoming mutation endpoints.
* **New CORS request header.** `X-Correlation-ID` is now accepted in cross-origin requests, allowing the SDK to pass a client-generated correlation identifier through to server logging.
* **New CORS exposed headers.** `Retry-After` and `X-Correlation-ID` are now exposed to browser JavaScript in cross-origin responses, enabling the SDK to read backoff timing and surface correlation identifiers in client-side logging.

**What you need to do:**

* **SDK users: no action required.** The SDK handles ticket minting and WebSocket attach automatically.
* **Custom integrations calling Scribe directly** should call `POST /sessions/{session_id}/ticket` before each WebSocket connect attempt and present the returned ticket at the handshake. Handle `503` responses by waiting for the duration in the `Retry-After` header before retrying.

</details>

<details>

<summary>Platform API: SQL Authorization Binding Test Values for Platform Functions and Workspace Data Queries (July 2026)</summary>

#### SQL Authorization Binding Test Values for Platform Functions and Workspace Data Queries <a href="#sql-authorization-binding-test-values-for-platform-functions-and-workspace-data-queries" id="sql-authorization-binding-test-values-for-platform-functions-and-workspace-data-queries"></a>

Server-bound SQL tool parameters can now carry an optional test fallback value. When a workspace operator tests a bound function or data query from the Developer Console playground or the function-test panel, the platform uses the configured test value instead of requiring a live external-user authorization context.

**What changed:**

* **Test value on bound parameters.** Platform Function and Workspace Data Query parameters that declare an authorization binding can now include an optional test value. The value is a comma-separated list of resource keys that the platform normalizes (sorted, deduplicated, whitespace-trimmed) at authoring time.
* **Trusted playground and function-test execution.** When a workspace member with data-query permission runs a bound tool from the playground or the function-test panel, the platform substitutes the configured test value for the bound parameter. Live external-user authorization always takes precedence when present. External-user turns, voice calls, and direct API invocations are unaffected.
* **Observability.** Each test-value substitution emits a structured log entry and a metric increment identifying the workspace, tool, parameter, and binding without exposing the configured value itself.
* **Validation rules.** A test value requires an authorization binding on the same parameter. Empty strings are rejected. Resource keys must match the lowercase alphanumeric-plus-underscore pattern, and the total length is capped. These rules are enforced at create and update time for both Platform Functions and Workspace Data Queries.
* **Create and update endpoints.** The Platform Function create endpoint and the Workspace Data Query create and update endpoints accept the new field on each parameter. The field is optional and defaults to absent.

**What you need to do:**

* **No action required for production workloads.** Live authorization resolution is unchanged. The test value is inert unless the server explicitly enables it for a trusted invocation.
* **To use test values,** add the field to any authorization-bound parameter when creating or updating a Platform Function or Workspace Data Query. Then test the tool from the Developer Console playground or function-test panel.

</details>

<details>

<summary>Platform API: Run-Scoped Token Caching for Integration Calls (July 2026)</summary>

#### Run-Scoped Token Caching for Integration Calls <a href="#run-scoped-token-caching-for-integration-calls" id="run-scoped-token-caching-for-integration-calls"></a>

Integration tool calls within an orchestrated skill run now reuse minted bearer tokens instead of re-minting on every call. This reduces latency and external token-endpoint load when a single agent turn fans out to many downstream API calls against the same integration.

**What changed:**

* **Run-scoped bearer reuse.** When an orchestrated skill run makes multiple calls to the same integration, the platform now mints the downstream bearer token once and reuses it for subsequent calls within that run. Previously, every integration call minted a fresh token even when the credentials and scopes were identical.
* **Scope and lifetime.** The cached token is scoped to a single skill run. It is never shared across runs, conversations, or services. The cache is discarded when the run completes, fails, or times out.
* **Expiry-aware.** Cached tokens respect the token lifetime reported by the downstream token endpoint. Tokens nearing expiry are treated as expired and re-minted automatically. A conservative safety margin prevents a cached token from being replayed into a call that could outlive the token's validity. Long-lived tokens are clamped to a shorter reuse window regardless of the reported lifetime.
* **Automatic invalidation on authentication failure.** If a downstream system rejects a cached token with an authentication error (HTTP 401), the cached entry is evicted immediately and the next call in the run mints a fresh token. Authorization failures (HTTP 403) do not evict the cache, since they indicate a permissions issue rather than a bad token.
* **At-rest protection.** Cached tokens are encrypted in memory using a per-run encryption key that is never persisted. This provides defense-in-depth against accidental exposure of bearer values in process diagnostics.
* **Supported auth types.** Caching applies to OAuth 2.0 Client Credentials, OAuth 2.0 JWT Bearer, and static-configuration custom token exchanges. Token exchanges that incorporate per-request values - such as identity bindings or caller-supplied parameters routed into the exchange - are never cached, because the minted token varies by request. Static header authentication is a secret read, not a token mint, and is unaffected.
* **No change for non-orchestrated paths.** Direct tool calls, voice direct tools, triggers, and atomic tool runners continue to mint a fresh token on every call. The caching optimization applies only to orchestrated skill runs.

**What you need to do:**

* **No action required.** This is a transparent performance improvement. Integration authentication contracts, API request and response shapes, and tool behavior are unchanged. Downstream systems see the same valid bearer tokens as before, with fewer token-endpoint calls under fan-out workloads.

</details>

<details>

<summary>Platform API: Scribe Sessions Now Born In-Progress (July 2026)</summary>

#### Scribe Sessions Now Born In-Progress <a href="#scribe-sessions-now-born-in-progress" id="scribe-sessions-now-born-in-progress"></a>

Scribe sessions no longer pass through a separate creation-only status before becoming active. New sessions start directly in the `in-progress` state with their start timestamp set at creation time.

**What changed:**

* **`created` status removed.** The `created` session status no longer exists. Every new session begins in `in-progress` with its start time recorded immediately. Mic sessions proceed to live capture via the WebSocket attach; Zoom sessions proceed via the connection saga. Neither path requires a separate status transition out of a creation-only state.
* **Start endpoint removed.** The `POST /sessions/{session_id}/start` endpoint has been removed. There is no need to explicitly start a session because sessions are active from the moment they are created.
* **Idempotent create scoped to active sessions.** The idempotent duplicate-detection window for session creation now covers only active (non-terminal) sessions. A prior session for the same appointment that ended in a terminal state (completed, cancelled, or failed) no longer blocks creation of a new session for that appointment.
* **Active Zoom guard simplified.** The active-Zoom-session check that prevents duplicate live Zoom sessions now considers only `in-progress` sessions, matching the simplified lifecycle.

**What you need to do:**

* **Remove any `POST /sessions/{session_id}/start` calls.** If your integration called the start endpoint after creating a Zoom session, remove that step. Sessions are now active immediately after creation.
* **Stop checking for the `created` status.** If your integration logic branches on a `created` status value, update it to handle `in-progress` as the initial state.
* **No changes needed for mic session integrations.** Mic sessions that rely on the WebSocket attach flow continue to work as before.

</details>

<details>

<summary>Platform API: Emotion Detection Now Requires GPU and Runs in Half-Precision (July 2026)</summary>

#### Emotion Detection Now Requires GPU and Runs in Half-Precision <a href="#emotion-detection-now-requires-gpu-and-runs-in-half-precision" id="emotion-detection-now-requires-gpu-and-runs-in-half-precision"></a>

The emotion detection pipeline now requires GPU compute and loads model weights in half-precision, reducing memory consumption and removing transient memory spikes during inference.

**What changed:**

* **GPU required.** Emotion detection no longer falls back to CPU execution. Environments without GPU availability cannot run the emotion detection pipeline. Previously, the service attempted CPU inference when no GPU was present.
* **Half-precision model weights.** Both emotion classification models now load their weights in half-precision. This approximately halves the resident GPU memory required by the model pair and eliminates a transient memory spike that occurred on each inference call when weights were converted on the fly. Emotion classification scores and dimensional outputs remain materially identical to the previous full-precision behavior.

**What you need to do:**

* **Verify GPU availability.** If your deployment runs emotion detection, confirm that GPU compute is available in the target environment. CPU-only deployments will no longer start the emotion detection service successfully.
* **No API or integration changes required.** Emotion detection request and response contracts are unchanged. Score outputs are not meaningfully affected.

</details>

<details>

<summary>Platform API: Intake Source Secret Namespace Change (July 2026)</summary>

#### Intake Source Secret Namespace Change <a href="#intake-source-secret-namespace-change" id="intake-source-secret-namespace-change"></a>

Intake source credentials are now stored in a dedicated per-source secret namespace, separate from the namespace used by general connectors.

**What changed:**

* **Dedicated secret namespace for intake sources.** Service-account keys provisioned for intake sources now use an intake-source-specific secret path rather than sharing the general connector secret namespace. The path is derived deterministically from the source identifier, so renaming a source does not orphan its provisioned credential.
* **Direct secret resolution.** Credential retrieval at sync time reads the secret directly from the parameter store instead of routing through the shared connector secret resolver. If the credential has not been provisioned, the sync returns an unprocessable-entity error with a message instructing the operator to upload the service-account key first.
* **No change to the registration API contract.** The request and response shapes for registering an intake source are unchanged. The secret path is still derived server-side and is never accepted from clients.

**What you need to do:**

* **Provision new intake source credentials in the updated namespace.** If you are setting up a new intake source, place the service-account key in the intake-source secret path provided by your platform administrator. Existing sources whose credentials were already provisioned in the previous namespace should be migrated in coordination with your platform operations team.
* **No client-side code changes required.** The API request and response contracts are unchanged.

</details>

<details>

<summary>Platform API: Mesh-Only Certificate Authentication for Internal Service Tokens (July 2026)</summary>

#### Mesh-Only Certificate Authentication for Internal Service Tokens <a href="#mesh-only-certificate-authentication-for-internal-service-tokens" id="mesh-only-certificate-authentication-for-internal-service-tokens"></a>

Internal platform services that mint agent-session tokens now authenticate exclusively through mesh-verified certificate identity. Client secrets are no longer sent or required for these callers.

**What changed:**

* **Secret-free authentication for internal callers.** Internal platform services that request agent-session tokens now rely entirely on their mesh-verified certificate identity (RFC 8705 `tls_client_auth`). The token request carries only the grant type and the caller's identity - no client secret is included. The mesh infrastructure verifies the caller's certificate at the transport layer before the request reaches the identity service.
* **Caller identity derived from deployment context.** The caller's identity is constructed from its deployment namespace and service account, provided through standard deployment metadata. This replaces the previous model where a separately provisioned secret was required.
* **One authentication method per credential.** Each credential uses either certificate-based authentication or a client secret - never both. Credentials registered for certificate authentication are provisioned without a client secret. A `client_secret` sent alongside a certificate-authenticated credential is ignored.
* **`client_id` always required.** The token request always requires `client_id` regardless of authentication method. The platform does not derive client identity from the certificate alone.
* **Service-account credentials only.** Certificate-based authentication is restricted to service-account principal types. Provider and external integration credentials continue to require client secrets.
* **No lockout on certificate failures.** Failed certificate authentication does not increment lockout counters because the certificate subject is transport-verified and cannot be brute-forced.
* **Audit trail.** Successful token grants via certificate authentication include `auth_method` in the audit event metadata. Failed certificate authentication attempts are logged with a distinct reason.
* **Existing secret-based flows unchanged.** Credentials without a certificate registration continue to require `client_secret` as before.

**What you need to do:**

* **No action required for existing integrations.** Secret-based `client_credentials` grants continue to work without changes. External OAuth2 clients and provider credentials are not affected.
* **Internal service teams** provisioning new mesh-authenticated credentials should coordinate with platform administration for certificate subject registration.

</details>

<details>

<summary>Platform API: Mutual TLS Client Authentication for Service Credentials (July 2026)</summary>

#### Mutual TLS Client Authentication for Service Credentials <a href="#mutual-tls-client-authentication-for-service-credentials" id="mutual-tls-client-authentication-for-service-credentials"></a>

The `client_credentials` grant now supports certificate-based authentication as an alternative to client secrets for internal service credentials.

**What changed:**

* **Certificate-based authentication method.** Service credentials can now be registered with a certificate subject instead of a client secret. When a credential has a certificate registration, the platform authenticates the client by verifying the certificate subject presented by the service mesh against the registered expected subject. The verified subject must match exactly.
* **One authentication method per credential.** Each credential uses either certificate-based authentication or a client secret - never both. Credentials registered for certificate authentication are provisioned without a client secret. A `client_secret` sent alongside a certificate-authenticated credential is ignored.
* **`client_id` always required.** The token request always requires `client_id` regardless of authentication method. The platform does not derive client identity from the certificate alone.
* **Service-account credentials only.** Certificate-based authentication is restricted to service-account principal types. Provider and external integration credentials continue to require client secrets. A certificate-authenticated request for a non-service-account credential returns `invalid_client`.
* **No lockout on certificate failures.** Failed certificate authentication does not increment lockout counters because the certificate subject is transport-verified and cannot be brute-forced. This prevents a misconfigured caller from locking out the legitimate service.
* **Audit trail.** Successful token grants via certificate authentication include `auth_method` in the audit event metadata. Failed certificate authentication attempts are logged with a distinct reason.
* **Existing secret-based flows unchanged.** Credentials without a certificate registration continue to require `client_secret` as before. The error message for a missing `client_secret` on those credentials is unchanged.

**What you need to do:**

* **No action required for existing integrations.** Secret-based `client_credentials` grants continue to work without changes.
* **Internal service teams** provisioning new mesh-authenticated credentials should coordinate with platform administration for certificate subject registration.

</details>

<details>

<summary>Platform API: Async Audit Exports and Export Catalog Removal (July 2026)</summary>

#### Async Audit Exports and Export Catalog Removal <a href="#async-audit-exports-and-export-catalog-removal" id="async-audit-exports-and-export-catalog-removal"></a>

Audit exports now run asynchronously and return short-lived download links instead of writing persistent artifacts to a workspace volume.

**What changed:**

* **Async export submission.** `POST /v1/{workspace_id}/audit/exports` now returns a statement handle (`statement_id`) instead of a completed export. The export runs asynchronously.
* **Export polling endpoint.** `GET /v1/{workspace_id}/audit/exports/{statement_id}` returns the current status of a submitted export. While the export is running, the status is `pending`. Once complete, the status is `ready` and the response includes presigned download chunks with row counts and byte sizes. The presigned links are short-lived.
* **Export listing removed.** The endpoint that listed persistent export artifacts in the workspace audit volume has been removed. Exports are ephemeral - download the results when the export finishes.
* **Export format.** Results are returned as downloadable files (CSV) rather than NDJSON artifacts stored in a volume. Large exports may be split across multiple download chunks.

**What you need to do:**

* **Adopt the submit-poll-download pattern.** After submitting an export, poll the statement endpoint until the status is `ready`, then download the files from the presigned links before they expire.
* **Remove any code that lists past exports.** The persistent export catalog no longer exists.
* **Update expected export format.** Exports now produce CSV files rather than NDJSON.

</details>

<details>

<summary>Platform API: Cross-Workspace Audit Log Reads (July 2026)</summary>

#### Cross-Workspace Audit Log Reads <a href="#cross-workspace-audit-log-reads" id="cross-workspace-audit-log-reads"></a>

Two new audit log read endpoints allow callers to query audit events outside the scope of a single workspace.

**What changed:**

* **Personal audit trail endpoint.** `GET /v1/audit-log/me` returns the authenticated caller's own audit events across every workspace. The actor is server-set from the credential - callers cannot query another user's trail. Any authenticated credential bound to an actor entity may call this endpoint; no additional permission is required.
* **Platform-wide audit read endpoint.** `GET /v1/audit-log/platform` returns audit events across all workspaces, including events that carry no workspace (such as login events emitted before workspace activation). This endpoint requires the `platform:admin` scope. Optional query filters include `workspace_id`, `service`, `action`, `actor_entity_id`, `resource_type`, `resource_id`, `phi_only`, `date_from`, `date_to`, `limit`, and `offset`.
* **Nullable workspace on audit events.** The `workspace_id` field in audit event responses is now nullable. Workspace-scoped reads always populate it, but the platform-wide read may return events with a null workspace for pre-workspace events such as login or credential-theft detection.
* **Existing workspace-scoped audit read unchanged.** The `GET /v1/{workspace_id}/audit` endpoint continues to work as before, always scoped to the specified workspace.

**What you need to do:**

* **Handle nullable `workspace_id` in audit event responses.** If your integration parses audit event responses, update your models to accept a null `workspace_id` field. This applies only to events returned by the platform-wide endpoint; workspace-scoped reads always include a workspace.
* **Use `/v1/audit-log/me` for self-service audit trails.** Callers who need their own cross-workspace activity history can use this endpoint without workspace-level audit permissions.
* **Use `/v1/audit-log/platform` for administrative audit review.** Platform administrators with the `platform:admin` scope can query the full audit trail, including pre-workspace events.

</details>

<details>

<summary>Platform API: Enterprise SSO, SAML/OIDC Federation, and MFA Retired (July 2026)</summary>

#### Enterprise SSO, SAML/OIDC Federation, and MFA Retired <a href="#enterprise-sso-saml-oidc-federation-and-mfa-retired" id="enterprise-sso-saml-oidc-federation-and-mfa-retired"></a>

The enterprise SSO federation and multi-factor authentication feature cluster has been removed from the identity service. These capabilities were never generally available and carried zero active configurations.

**What changed:**

* **Federation source endpoints removed.** The `GET/POST /admin/federation-sources`, `GET/PUT/DELETE /admin/federation-sources/{source_id}` endpoints are no longer available. Calls to these endpoints return a 404 response.
* **SSO connection endpoints removed.** The `GET/POST /admin/sso-connections`, `GET/PUT/DELETE /admin/sso-connections/{connection_id}` endpoints are no longer available.
* **MFA endpoints removed.** The `POST /mfa/enroll`, `POST /mfa/verify`, `POST /mfa/challenge`, `GET /mfa/status`, `DELETE /mfa/unenroll` user endpoints and the `GET /admin/mfa/coverage`, `POST /admin/mfa/reset/{entity_id}` admin endpoints are no longer available.
* **OIDC federation flow removed.** The `GET /federation/oidc/authorize` and `POST /federation/oidc/callback` endpoints are no longer available.
* **SAML federation flow removed.** The `GET /federation/saml/metadata/{source_id}`, `GET /federation/saml/login`, and `POST /federation/saml/acs` endpoints are no longer available.
* **MFA requirement removed from provider access grants.** The `requires_mfa` field is no longer accepted when creating provider access grants and is no longer returned in grant responses.
* **MFA coverage removed from compliance posture.** MFA enrollment statistics are no longer included in compliance or coverage responses.

**What you need to do:**

* **Remove references to federation, SSO, and MFA endpoints.** If your automation or admin tooling calls any of the removed endpoints, remove those calls.
* **Remove `requires_mfa` from provider access grant requests.** If your integration includes this field when creating grants, remove it. The field is no longer accepted.
* **Review authentication strategy.** These features were unused. If your security planning assumed future availability of platform-native SAML/OIDC federation or TOTP MFA, contact your account team to discuss alternatives.

</details>

<details>

<summary>Platform API: Per-Workspace IP Allowlist Removed (July 2026)</summary>

#### Per-Workspace IP Allowlist Removed <a href="#per-workspace-ip-allowlist-removed" id="per-workspace-ip-allowlist-removed"></a>

The per-workspace IP allowlist feature has been removed from the identity service. Workspaces that had allowlist entries configured are no longer enforcing IP-based access restrictions through this mechanism.

**What changed:**

* **Admin IP allowlist endpoints removed.** The `GET /admin/ip-allowlists`, `POST /admin/ip-allowlists`, `DELETE /admin/ip-allowlists/{id}`, and `POST /admin/ip-allowlists/test` endpoints are no longer available. Calls to these endpoints return a 404 response.
* **IP allowlist enforcement removed from token issuance.** Token grants (API key, client credentials, personal access token, email OTP, magic link, Google OAuth, and device code) no longer check the caller's IP address against a workspace allowlist. All previously blocked IPs are now permitted.
* **Compliance posture field removed.** The `ip_allowlist_active` field is no longer included in the compliance posture response. Integrations that read this field should remove references to it.

**What you need to do:**

* **Remove references to the IP allowlist endpoints.** If your automation or admin tooling calls any of the removed endpoints, remove those calls.
* **Update compliance dashboards.** If your compliance reporting reads the `ip_allowlist_active` field from the compliance posture response, remove that reference. The field is no longer returned.
* **Review network-level access controls.** If your security posture relied on the per-workspace IP allowlist, migrate to network-level controls (firewall rules, VPN, or reverse-proxy restrictions) to achieve equivalent protection.

</details>

<details>

<summary>Platform API: Per-Workspace Conversation Topic Analytics Controls (July 2026)</summary>

#### Per-Workspace Conversation Topic Analytics Controls <a href="#per-workspace-conversation-topic-analytics-controls" id="per-workspace-conversation-topic-analytics-controls"></a>

Workspace administrators can now control whether customer-facing conversation topic analytics are exposed through each workspace's data scope.

**What changed:**

* **New settings endpoints.** `GET /v1/{workspace_id}/settings/topic-modeling` returns the current exposure state. `PUT /v1/{workspace_id}/settings/topic-modeling` sets the desired state and queues an idempotent reconciliation of the workspace's data scope.
* **Fail-closed exposure model.** Conversation topic analytics are not exposed unless explicitly enabled for a workspace. If the desired state cannot be determined during reconciliation, topic analytics are withdrawn rather than retained.
* **Amigo-administrator-only access.** Both endpoints require workspace update permission and an authenticated Amigo administrator identity. Non-administrator callers receive a 403 response.
* **Reconciliation on archive.** Archiving a workspace now queues a data-scope reconciliation to ensure that sensitive feature views are withdrawn promptly, rather than waiting for the next scheduled safety-net run.
* **Protected setting mutation.** The topic analytics exposure flag is isolated from general workspace settings updates. Unrelated settings changes cannot overwrite or reset the flag.

**What you need to do:**

* **No action required for existing workspaces.** Topic analytics remain disabled until an Amigo administrator explicitly enables them.
* **Use the new endpoints to manage exposure.** To enable or disable conversation topic analytics for a workspace, call `PUT /v1/{workspace_id}/settings/topic-modeling` with `{"enabled": true}` or `{"enabled": false}`.

</details>

<details>

<summary>Platform API: Per-Turn Authorization for Durable Text Sessions (July 2026)</summary>

#### Per-Turn Authorization for Durable Text Sessions <a href="#per-turn-authorization-for-durable-text-sessions" id="per-turn-authorization-for-durable-text-sessions"></a>

Text conversations that use the durable session path now carry the caller's verified authorization context with each individual turn, rather than fixing it once at session start.

**What changed:**

* **Per-turn authorization propagation.** Each user message enqueued to a durable text session now includes the authorization context verified at request time. The session applies that context before running the turn's reasoning and tool calls, so successive turns from different callers (or the same caller with changed grants) each operate under the correct scope.
* **Authorization-bound tools execute inline.** Tools with server-bound parameters are always resolved within the turn that carries their authorization context. They are never deferred to a background worker, ensuring the bound values cannot be read by a later turn with a different scope.
* **Conversation-level authorization check.** When a request names an existing conversation, the platform now verifies that the authorization context's conversation identifier matches. A mismatch returns a 403 response. Requests that create a new conversation (no conversation identifier supplied) are not affected.
* **Fail-closed on missing or corrupt context.** If a turn arrives without an authorization context, or with a context that cannot be validated, server-bound tool calls are denied rather than falling back to a previous turn's scope.

**What you need to do:**

* **No action required for most integrations.** The per-turn authorization model is transparent to callers. Requests that already supply a valid authorization block continue to work.
* **Review integrations that reuse a single authorization block across conversations.** If your integration stamps the same authorization block onto turns for different conversations, the new conversation-level check may return 403. Ensure each turn's authorization block matches the conversation it targets.

</details>

<details>

<summary>Platform API: Authorization-Scoped Parameter Execution for SQL Tools (July 2026)</summary>

#### Authorization-Scoped Parameter Execution for SQL Tools <a href="#authorization-scoped-parameter-execution-for-sql-tools" id="authorization-scoped-parameter-execution-for-sql-tools"></a>

Platform functions and workspace data queries that declare server-bound parameters now inject scoping values at execution time from the conversation turn's authorization context.

**What changed:**

* **Server-bound parameter injection.** SQL and AI platform functions, and workspace data queries, can now declare parameters that are bound to the external principal's authorization grants. At execution time the platform resolves the principal's resource-level access and injects the appropriate scoping value into the query. The agent and caller never see or supply the bound parameter - it is omitted from the tool's input schema.
* **Fail-closed execution.** If the authorization context is unavailable (direct API invocation, Developer Console test panel, or MCP tool call), execution is denied with a 403 response rather than falling back to a caller-supplied or default value.
* **Direct and MCP invocation denied for bound tools.** The platform function invoke endpoint and the workspace data query invoke endpoint both return 403 when the target carries a server-bound parameter. MCP tool registration surfaces an error envelope for the same reason.
* **In-process execution enforced.** Tools with server-bound parameters are never dispatched to background workers. They always execute in the same process as the live conversation turn.

**What you need to do:**

* **No action required for existing tools.** Tools without server-bound parameters are unaffected. The new behavior applies only to tools authored with an authorization binding on one or more parameters.
* **Update integrations that invoke bound tools directly.** If you call a platform function or workspace data query that carries a server-bound parameter through the REST API or MCP, expect a 403 response. These tools are callable only during an authenticated conversation turn.

</details>

<details>

<summary>Platform API: Access Token Lifetime Restored to 15 Minutes (July 2026)</summary>

#### Access Token Lifetime Restored to 15 Minutes <a href="#access-token-lifetime-restored-to-15-minutes" id="access-token-lifetime-restored-to-15-minutes"></a>

The default access-token lifetime has been restored to 15 minutes, down from the temporary 24-hour value.

**What changed:**

* **Shorter access-token TTL.** Access tokens issued by the identity service now expire after 15 minutes by default, matching the original lifetime. The previous 24-hour lifetime has been reverted. Operator access tokens remain at 4 hours.
* **Tighter idle-session enforcement.** Because idle-timeout checks run on token refresh, the shorter TTL ensures that idle principals are re-evaluated more frequently rather than deferring the check for up to a full day.

**What you need to do:**

* **Verify token-refresh logic.** If your integration caches access tokens or refreshes them on a fixed schedule, confirm that it handles the 15-minute expiry window. Integrations that already use standard refresh-on-expiry flows require no changes.
* **Expect more frequent refresh requests.** Clients will refresh tokens more often under the restored TTL. This is expected behavior and does not indicate an error.

</details>

<details>

<summary>Platform API: Async Audit Export with Presigned Download Links (July 2026)</summary>

#### Async Audit Export with Presigned Download Links <a href="#async-audit-export-with-presigned-download-links" id="async-audit-export-with-presigned-download-links"></a>

Audit event exports now run asynchronously and return short-lived presigned download links instead of requiring a proxy download through the platform.

**What changed:**

* **Async export submission.** `POST /audit/export` now submits the export query asynchronously and returns a `statement_id` handle with a `status` of `pending` or `ready`. The response no longer contains an inline download URL or row count - poll for results instead.
* **Presigned result polling.** A new `GET /audit/export/{statement_id}` endpoint lets you poll the export status. When `status` is `ready`, the response includes `chunks` - each chunk carries a short-lived presigned URL (`external_link`) that you download directly from object storage. Links expire quickly (roughly 15 minutes); re-poll the endpoint to get fresh links.
* **CSV result format.** Export results are now delivered as CSV files via the presigned links, replacing the previous NDJSON proxy-download format.
* **No row limit on exports.** Compliance exports no longer enforce a maximum row count. The full date range is exported without silent truncation.
* **Removed endpoints.** The `GET /audit/exports` (list past exports) and `GET /audit/exports/download/{filename}` (proxy-download) endpoints have been removed. All export retrieval now goes through the poll-based presigned link flow.
* **Simplified response models.** The `AuditExportResponse`, `AuditExportListItem`, and `AuditExportListResponse` models are replaced by `AuditExportSubmitResponse` and `AuditExportResultResponse`.

**What you need to do:**

* **Update export integrations.** Replace calls to `POST /audit/export` that expect a synchronous download URL. Instead, capture the `statement_id` from the submit response, poll `GET /audit/export/{statement_id}` until `status` is `ready`, then download from the presigned URLs in `chunks`.
* **Remove references to deleted endpoints.** If your integration lists or downloads exports via `/audit/exports` or `/audit/exports/download/{filename}`, migrate to the new poll-based flow.
* **Handle link expiration.** Presigned download links are short-lived. Download promptly after polling, or re-poll for fresh links if they expire.

</details>

<details>

<summary>Platform API: Per-Workspace Intake Deduplication Override (July 2026)</summary>

#### Per-Workspace Intake Deduplication Override <a href="#per-workspace-intake-deduplication-override" id="per-workspace-intake-deduplication-override"></a>

Workspaces can now temporarily bypass content-hash deduplication during intake re-syncs, allowing newly added catalog columns to be backfilled.

**What changed:**

* **Per-workspace dedup override flag.** A new `intake-dedup-disabled` feature flag lets a workspace opt out of content-hash deduplication on file intake. When the flag is enabled for a workspace, an unchanged re-sync re-lands every file as a new version instead of short-circuiting to the existing catalog entry.
* **Backfill escape hatch.** The override exists to support backfilling catalog columns that were introduced after the original ingest - such as source-provenance fields whose values only populate on a fresh version. Without the override, byte-identical files would deduplicate and skip version creation.
* **Fails closed to dedup-on.** If the flag provider is unreachable or the flag is not configured, deduplication remains enabled. The default idempotent upload behavior is never lost by accident.
* **Intended for temporary use.** Enable the flag for a single workspace, re-sync the affected sources, then disable it. The flag is not designed for permanent use.

**What you need to do:**

* **No action required for most integrations.** Default deduplication behavior is unchanged. Uploads continue to be idempotent unless the flag is explicitly enabled.
* **Contact your Amigo team to enable the flag.** If you need to backfill catalog columns on previously ingested files, request that the `intake-dedup-disabled` flag be enabled for your workspace, run your re-sync, and then request that it be disabled.

</details>

<details>

<summary>Platform API: Source Provenance on Batch Document Intake (July 2026)</summary>

#### Source Provenance on Batch Document Intake <a href="#source-provenance-on-batch-document-intake" id="source-provenance-on-batch-document-intake"></a>

The batch document intake path now carries the same source provenance metadata as the single-file intake path, ensuring consistent lineage in the analytical catalog.

**What changed:**

* **Source identifiers on every batch file entry.** Each file in a batch processing manifest now includes a source file id and source URL. These opaque identifiers always populate, matching the single-file intake behavior.
* **PHI-gated human-readable fields.** The source folder path and original filename are included in the manifest only when the target dataset has been attested as PHI-free. For datasets that contain PHI, these fields remain absent so that protected information does not leave the durable intake record.
* **Consistent provenance across intake paths.** Downstream catalog rows now receive the same source lineage fields regardless of whether a document was ingested individually or as part of a batch. Previously, batch-ingested documents did not carry source provenance.

**What you need to do:**

* **No action required for most integrations.** The new fields are optional and additive. Existing batch intake workflows continue to work without changes.
* **Consume source provenance if needed.** If your integration reads batch manifests and needs to correlate documents back to their original source location, the new source file id and source URL fields are now available on every file entry. The folder path and filename are available only for PHI-free datasets.

</details>

<details>

<summary>Platform API: FHIR Patient Bundle Builder Endpoint (July 2026)</summary>

#### FHIR Patient Bundle Builder Endpoint <a href="#fhir-patient-bundle-builder-endpoint" id="fhir-patient-bundle-builder-endpoint"></a>

A new endpoint lets integrators convert a flat list of user records into a FHIR Patient Bundle without hand-authoring FHIR resources.

**What changed:**

* **New `POST /fhir/build-bundle` endpoint.** Accepts a list of user records - each with an optional external id, email, and name fields - and returns a FHIR `collection` Bundle containing one Patient resource per user. The returned `bundle` value is the exact shape that `POST /fhir/import` expects under its `bundle` field.
* **Pure transformation, no side effects.** The endpoint does not create entities or events. It builds the bundle and returns it. Import is a separate step.
* **Deterministic, idempotent resource ids.** Each Patient receives a stable, spec-valid FHIR resource id derived from the external id (used verbatim when it satisfies the FHIR id grammar) or from a deterministic derivation when the external id is absent or non-conformant. Re-calling the endpoint with the same input produces the same ids, so a later import upserts rather than duplicates.
* **MR identifier for canonical id resolution.** Every Patient includes an MR-type identifier. When the external id is present it becomes the identifier value (preserving the source-system binding); otherwise the derived resource id is used. Raw email addresses are never placed into the identifier value.
* **Name and contact mapping.** A full name or first/last name parts are mapped to an official FHIR HumanName. Email addresses are mapped to an email telecom entry.
* **Up to 1,000 users per request.** The `users` list accepts between 1 and 1,000 entries. Each user must supply at least one of external id or email.

**What you need to do:**

* **Use this endpoint to simplify FHIR onboarding.** Instead of manually constructing Patient resources, pass your user list to `POST /fhir/build-bundle` and then send the returned bundle to `POST /fhir/import`.
* **Supply external ids when available.** External ids (such as your system's user identifiers) produce the most stable canonical id binding on import. Email-only users still work but use a derived identifier.

</details>

<details>

<summary>Platform API: Default Access-Token Lifetime Extended to 24 Hours (July 2026)</summary>

#### Default Access-Token Lifetime Extended to 24 Hours <a href="#default-access-token-lifetime-extended-to-24-hours" id="default-access-token-lifetime-extended-to-24-hours"></a>

The default lifetime for access tokens issued by the identity service has been raised from 15 minutes to 24 hours. Operator access tokens remain at 4 hours.

**What changed:**

* **Default access-token TTL is now 24 hours.** Access tokens issued through standard authentication flows now default to a 24-hour lifetime instead of the previous 15-minute lifetime. This reduces the frequency of token refreshes for most integrations.
* **Operator access-token TTL unchanged at 4 hours.** Tokens issued for operator sessions remain capped at 4 hours. Operators hold live patient-data views through passive monitoring sessions, so a shorter credential window limits exposure if a token is compromised.
* **Agent session TTL unchanged.** Agent session tokens continue to use a 1-hour lifetime.
* **Idle-timeout enforcement on refresh.** The platform's idle-timeout check runs when a refresh-token grant is evaluated. With a longer access-token lifetime, an idle principal's session may persist up to 24 hours before the idle-timeout check fires on the next refresh. Organizations with strict idle-session requirements should review their refresh-token and session-management policies.

**What you need to do:**

* **No action required for most integrations.** Existing authentication flows benefit from fewer token-refresh cycles. Tokens that were previously refreshed every 15 minutes now remain valid for up to 24 hours.
* **Review token storage and revocation practices.** A longer-lived token increases the window during which a leaked credential is usable. Ensure your integration stores access tokens securely and revokes them when they are no longer needed.
* **Evaluate idle-session policies.** If your organization enforces HIPAA idle-timeout requirements, confirm that your refresh-token grant interval and session-management configuration align with the new 24-hour access-token window.

</details>

<details>

<summary>Platform API: Authorization-Bound Parameter Schema Hiding (July 2026)</summary>

#### Authorization-Bound Parameter Schema Hiding <a href="#authorization-bound-parameter-schema-hiding" id="authorization-bound-parameter-schema-hiding"></a>

Server-bound parameters are now hidden from every caller-facing schema, completing the caller-side enforcement for authorization bindings on platform functions and workspace data queries.

**What changed:**

* **Bound parameters excluded from all caller-facing schemas.** Parameters that carry an `authorization_binding` are now automatically removed from the JSON Schema returned by read endpoints, the MCP tool schema, and the `required` list. Models and callers never see or attempt to supply a server-bound parameter.
* **Consistent hiding across all schema paths.** The exclusion applies uniformly to the schema derived during function registration, the schema returned by the platform API read path, the schema used by the agent engine for tool descriptions, and the MCP server schema for workspace data queries.
* **Example validation against bound parameters.** A function definition is rejected at authoring time if any example's input names a server-bound parameter. Because the caller-facing schema hides that parameter, an example that supplies it would be inconsistent with what the caller actually sees.
* **Bound parameter values remain injected at runtime.** The parameter continues to exist in the authored definition and as a SQL bind variable. Only the caller-facing projection is affected.

</details>

<details>

<summary>Platform API: Authorization-Bound Parameters for Platform Functions and Workspace Data Queries (July 2026)</summary>

#### Authorization-Bound Parameters for Platform Functions and Workspace Data Queries <a href="#authorization-bound-parameters-for-platform-functions-and-workspace-data-queries" id="authorization-bound-parameters-for-platform-functions-and-workspace-data-queries"></a>

Platform function parameters and workspace data query parameters can now declare an authorization binding that ties them to the calling turn's resolved external authorization context. This lays the data-model and authoring groundwork for server-injected, tamper-proof parameter values.

**What changed:**

* **New `authorization_binding` field on function parameters.** When creating or updating a platform function, each parameter in the `parameters` array can now include an optional `authorization_binding` object. When set, the parameter becomes server-bound: at execution time the platform will inject the value from the turn's authorization context rather than accepting it from the caller or model.
* **New `authorization_binding` field on workspace data query parameters.** The same binding field is available on workspace data query parameters (create and update endpoints), with identical semantics.
* **Mutual exclusion with `default`.** A parameter that declares an `authorization_binding` cannot also declare a `default` value. The API rejects requests that set both.
* **Deployment gated until execution support lands.** Deploying a function or workspace data query that includes a bound parameter is currently rejected with a validation error. This prevents a bound parameter from silently falling back to caller-supplied behavior before the execution path is ready to enforce it.
* **`kb_scope` role grant validation.** Role grants with resource type `kb_scope` now enforce that `param_binding` is not set (the grant is unconditional) and that `resource_key` contains only lowercase letters, digits, and underscores (`[a-z0-9_]+`).
* **Bound parameters hidden from caller schemas.** Server-bound parameters are excluded from every emitted caller-facing schema, so models and callers cannot attempt to supply them.

**Authorization binding object shape:**

| Field           | Type   | Description                                               |
| --------------- | ------ | --------------------------------------------------------- |
| `source`        | string | The binding source. Currently `role_grant_resource_keys`. |
| `resource_type` | string | The resource type to project. Currently `kb_scope`.       |
| `access`        | string | The access level. Defaults to `read`.                     |
| `encoding`      | string | How the projected keys are encoded. Defaults to `csv`.    |

**What you need to do:**

* **No action required for existing functions or queries.** The new field defaults to `null`, so all existing resources are unchanged.
* **Prepare for server-bound parameters.** If you plan to use authorization-scoped data access, you can begin defining parameters with authorization bindings. Note that deployment is gated until execution-time injection is available in a subsequent release.
* **Update `kb_scope` grant creation.** If you create `kb_scope` role grants, ensure `resource_key` uses only lowercase letters, digits, and underscores, and do not set `param_binding`.

</details>

<details>

<summary>Platform API: Extended Default Token TTL for Email OTP Provider Grants (July 2026)</summary>

#### Extended Default Token TTL for Email OTP Provider Grants <a href="#extended-default-token-ttl-for-email-otp-provider-grants" id="extended-default-token-ttl-for-email-otp-provider-grants"></a>

Provider access tokens issued through the email OTP flow now default to a longer time-to-live when no explicit TTL is requested.

**What changed:**

* **Default TTL extended to 7 days.** When a provider token is minted through the email OTP grant and the caller does not specify a TTL, the issued access token now lives for 7 days instead of the previous short-lived default. Callers that explicitly request a TTL continue to receive the requested value, subject to the existing allowed set.

**What you need to do:**

* **No action required for most integrations.** If your integration omits the TTL parameter when requesting an email OTP provider token, it will now receive a token valid for 7 days. If your integration explicitly passes a TTL value, behavior is unchanged.
* **Review token storage and revocation practices.** Longer-lived tokens increase the window during which a leaked token is usable. Ensure your integration stores provider tokens securely and revokes them when they are no longer needed.

</details>

<details>

<summary>Platform API: Receipt-Backed Background Polling and Delivery Acknowledgement (July 2026)</summary>

#### Receipt-Backed Background Polling and Delivery Acknowledgement <a href="#receipt-backed-background-polling-and-delivery-acknowledgement" id="receipt-backed-background-polling-and-delivery-acknowledgement"></a>

Text conversations now support a receipt-backed protocol for collecting background answers. When a tool crosses the server's blocking window, the platform hands it to a background task and signals the client. Clients poll for the completed answer, receive a delivery receipt, render the result, and then acknowledge the receipt so the platform knows the answer was consumed.

**What changed:**

* **Delivery protocol version 2.** Turn responses include a `delivery_protocol_version` field (value `2` when the serving agent supports the receipt protocol). Clients should begin polling only after observing version 2.
* **Receipt-backed poll responses.** A `poll=true` turn that claims a ready background answer now returns a `delivery` object containing `delivery_id`, `request_id`, and `receipt`. The receipt is opaque and required for acknowledgement.
* **New acknowledgement endpoint.** `POST /{workspace_id}/conversations/{conversation_id}/turns/{delivery_id}/ack` confirms that the client rendered or durably persisted the delivered answer. The request body carries `request_id` and `receipt`. Returns 204 on success.
* **Idempotency-Key header.** `POST .../turns` and `POST .../turns/stream` accept an optional `Idempotency-Key` header (UUID) for client-controlled replay of the same logical send or poll after an ambiguous failure.
* **Poll validation tightened.** `poll=true` now rejects any user content (message text, structured content, media URL, or media type), not only a non-empty message string. The error message reflects the broader check.
* **Streaming does not support poll.** `POST .../turns/stream` with `poll=true` is rejected (422).
* **Conversation close notifies the agent.** `DELETE /{workspace_id}/conversations/{conversation_id}` now signals the serving agent before marking the conversation closed, ensuring in-flight background work is cleaned up. A narrow database conflict during close returns 409 instead of 404.
* **Independent rate-limit budgets.** Polls and acknowledgements each have their own per-conversation rate-limit budget (30 requests per minute per conversation), separate from the turn-send budget.
* **Background delivery turn identity.** A background delivery returned by a poll keeps the originating exchange identity even when later user turns already exist, so feedback and artifacts anchor to the correct exchange.

**What you need to do:**

* **Adopt the receipt-based delivery loop.** When a turn response has `background_pending=true` and `delivery_protocol_version=2`, poll with `poll=true` at moderate intervals (no more than once every few seconds). When the poll returns a `delivery` object, render the answer, then call the acknowledgement endpoint with the delivery receipt. Repeat polling until `background_pending` clears or the poll returns idle.
* **Pass the Idempotency-Key header for replay safety.** When retrying a turn send or poll after a network timeout, reuse the same UUID so the platform can deduplicate.
* **Update poll validation expectations.** If your integration previously sent media or structured content alongside `poll=true`, remove that content. The platform now rejects it.
* **Handle 409 on conversation close.** If a close request returns 409, retry the close.

</details>

<details>

<summary>Scribe API: Generate ICD-10-CM Code Suggestions (July 2026)</summary>

#### Generate ICD-10-CM Code Suggestions <a href="#generate-icd-10-cm-code-suggestions" id="generate-icd-10-cm-code-suggestions"></a>

Scribe sessions can now generate ICD-10-CM diagnosis code suggestions derived from the session transcript and clinical note.

**What changed:**

* **New `POST /{workspace_id}/sessions/{session_id}/codes` endpoint.** Generates ICD-10-CM code suggestions for an owned scribe session. The endpoint derives suggestions from the canonical transcript and the latest clinical note (if available), persists the results, and returns them with generation provenance metadata. No request body is required.
* **Grounded suggestions only.** The generation is instructed to propose only codes substantively supported by the source material. Each suggestion includes the ICD-10-CM code, its official description, a brief rationale grounded in the transcript or note, and a confidence score between 0 and 1.
* **Deduplication and bounding.** Suggestions are deduplicated by code value and capped at 50 per generation. Code values, descriptions, and rationales are bounded to prevent unbounded storage.
* **Generation provenance.** The response includes generation metadata (model provider, model name, prompt version, and completion timestamp), consistent with the existing note and summary generation endpoints.
* **Empty extraction treated as failure.** If the model returns no grounded codes, the generation is marked failed and the endpoint returns 503, preserving the invariant that a completed generation always has at least one suggestion.
* **Scope requirement.** The caller must hold the `scribe:notes:rw_own` scope and own the session.

**What you need to do:**

* **Call the new endpoint to generate code suggestions.** Use `POST /{workspace_id}/sessions/{session_id}/codes` after a transcript is available. The response shape includes a `codes` object (with `session_id` and `items` array) and a `generation` metadata object.
* **Handle 409 for empty transcripts.** The endpoint returns 409 if the session transcript is empty.
* **Handle 503 for generation failures.** If the model produces no usable codes or the generation pipeline is unavailable, the endpoint returns 503.

</details>

<details>

<summary>Scribe API: Zoom Active-Session Guard Narrowed to Live-Capture States (July 2026)</summary>

#### Zoom Active-Session Guard Narrowed to Live-Capture States <a href="#zoom-active-session-guard-narrowed-to-live-capture-states" id="zoom-active-session-guard-narrowed-to-live-capture-states"></a>

The duplicate-session guard for Zoom scribe sessions now considers only live-capture states when deciding whether a provider already has an active Zoom session in a workspace.

**What changed:**

* **Narrower active-session definition.** The guard that prevents duplicate Zoom sessions in a workspace now treats only sessions in the initial and live-capture states as "active." Sessions that have moved past live capture into the review stage are no longer counted. This means a provider whose previous Zoom session is awaiting review can start a new Zoom session without receiving a conflict error.
* **Consistent enforcement.** The pre-check and the underlying uniqueness constraint use the same state set, so a request that passes the pre-check cannot be rejected by the constraint (and vice versa).

**What you need to do:**

* **No action required.** Integrations that create Zoom scribe sessions benefit automatically. If your workflow previously required completing or cancelling a session in review before starting a new Zoom session, that step is no longer necessary.

</details>

<details>

<summary>Scribe API: Zoom Session Lifecycle and Transcript Finalization (July 2026)</summary>

#### Zoom Session Lifecycle and Transcript Finalization <a href="#zoom-session-lifecycle-and-transcript-finalization" id="zoom-session-lifecycle-and-transcript-finalization"></a>

Zoom-based scribe sessions are now managed as first-class session rows with dedicated lifecycle transitions, transcript finalization, and a join-deadline watchdog that fails stuck bots cleanly.

**What changed:**

* **Zoom sessions as first-class session rows.** Zoom scribe sessions now share the same session model and status lifecycle as in-person sessions. A Zoom session is created with `mode=zoom` and progresses through the same `created`, `in-progress`, `in-review`, and terminal states.
* **Start transition endpoint.** A new `POST /{workspace_id}/sessions/{session_id}/start` endpoint marks a `created` session as `in-progress`. For Zoom sessions, this is driven by the caller once the meeting bot has joined and audio is flowing. The transition is idempotent - calling it on an already `in-progress` session succeeds without change. A terminal session returns 409 with `invalid_session_state`.
* **Zoom finalize endpoint.** A new `POST /{workspace_id}/sessions/{session_id}/zoom-finalize` endpoint promotes the drained transcript snapshot to the canonical transcript artifact and flips the session to `in-review`. This endpoint is the trigger that makes `GET .../transcript` resolve for Zoom sessions, so a partial transcript is never served to reviewers. The endpoint rejects non-Zoom sessions (409 `not_zoom_session`), sessions whose transcript has not been drained yet (409 `transcript_not_finalized`), and terminal sessions (409 `invalid_session_state`). The operation is idempotent.
* **Terminal-session immutability.** A completed, cancelled, or failed session never gains a fresh transcript artifact. The finalize endpoint enforces this with both an early state check and a transactional guard, so even a narrow race between the check and the state flip cannot produce an orphaned artifact on a terminal session.
* **Mode-aware session reaping.** The background reaper that detects abandoned sessions now uses different staleness rules by mode. In-person sessions are reaped when the streaming worker stops reporting liveness within a short grace period. Zoom sessions - which have no streaming worker liveness signal - are reaped only after a much longer maximum-session horizon measured from creation, preventing live Zoom calls from being incorrectly terminated.
* **Join-deadline watchdog.** Zoom meeting bots that remain in a `joining` state past a configurable deadline (default: 120 seconds) are now automatically failed with a `join_timeout` reason. This prevents the caller from waiting indefinitely when the meeting host never admits the bot or the audio handshake never completes. The deadline covers a slow host-admit and audio handshake.
* **Bot lookup by session.** A new query parameter on the bot listing endpoint resolves the active bot bound to a given scribe session identifier. This lets callers recover a running bot after a timed-out or duplicate creation request instead of orphaning it.
* **Duplicate bot creation returns existing bot.** When a bot creation request conflicts with an already-running bot for the same session, the 409 response now includes the existing bot identifier in the response body, enabling callers to bind to the running bot without a separate lookup.
* **Finalize signal from the transcription relay.** Once the transcription relay drains its final transcript snapshot during shutdown, it reports a `transcript_finalized` signal with the snapshot location and segment count. This signal gates the `zoom-finalize` endpoint. The signal is delivered on a best-effort basis - the session reaper serves as the backstop if the signal is lost.

**What you need to do:**

* **Zoom session integrations should call the start and finalize endpoints.** If your integration manages Zoom scribe sessions, use `POST .../start` once the bot has joined the meeting, and `POST .../zoom-finalize` once the finalize signal confirms the transcript has been drained. The `GET .../transcript` endpoint will not resolve for a Zoom session until finalization completes.
* **Handle new 409 error codes.** The `not_zoom_session` and `transcript_not_finalized` codes are new. Update error handling if your integration calls the finalize endpoint.
* **Bot creation retry logic.** If your integration retries bot creation, check the 409 response body for the existing `bot_id` to recover the running bot instead of retrying blindly.
* **No schema changes to existing endpoints.** The session response shape, existing lifecycle endpoints, and transcript retrieval contract are unchanged.

</details>

<details>

<summary>Platform: Connector Sync-Run Telemetry Routing Change (July 2026)</summary>

#### Connector Sync-Run Telemetry Routing Change <a href="#connector-sync-run-telemetry-routing-change" id="connector-sync-run-telemetry-routing-change"></a>

Successful connector sync-run completion events are no longer written to the durable event store. They are recorded to operational metrics and logs only.

**What changed:**

* **Success heartbeats removed from durable events.** The per-poll `connector.sync_run.completed` event - a heartbeat that fires every polling interval whether or not new data was ingested - is no longer emitted through the durable event pipeline. At fleet scale, these heartbeats dominated per-workspace event counts (a single connector could produce thousands of rows per day), inflating analytics for otherwise idle tenants.
* **Failures remain durable.** `connector.sync_run.failed` events continue to be written as durable records with classification and error-code metadata, in addition to operational metrics. These are low-volume and diagnostically valuable.
* **Analytics and projections exclude operational exhaust.** Per-workspace event counts and entity projections now filter out operational connector exhaust, so connector polling volume no longer distorts workspace activity summaries or drives unnecessary compute.
* **Operational metrics coverage.** Every sync run (success and failure) is recorded to operational metrics with bounded-cardinality tags, including record counts and duration. Monitoring dashboards that rely on these metrics are unaffected.

**What you need to do:**

* **No action required for most integrations.** If your integration consumes connector events from the durable event store, successful sync-run completions are no longer present. Failure events are unchanged.
* **Use operational metrics for sync-run monitoring.** If you previously counted durable sync-run events to monitor connector health, use the operational metrics surface instead.

</details>

<details>

<summary>Scribe API: Appointments Endpoint Returns a Rolling Date Window (July 2026)</summary>

#### Appointments Endpoint Returns a Rolling Date Window <a href="#appointments-endpoint-returns-a-rolling-date-window" id="appointments-endpoint-returns-a-rolling-date-window"></a>

The appointments list endpoint now returns a multi-day rolling window of appointments instead of only the current calendar day, so appointment-to-session links remain stable across day boundaries.

**What changed:**

* **Rolling window instead of today-only.** `GET /{workspace_id}/appointments` now returns appointments spanning a configurable window around the current date (default: 14 days back and 30 days forward, inclusive of today). Previously the endpoint returned only the current calendar day's appointments.
* **Stable appointment-session linkage across days.** Because appointment identifiers are date-pinned, a session created on one day stays linked to its appointment as long as that date falls within the rolling window. Previously, a session's linked appointment disappeared from the list the next day, silently breaking the join.
* **Same response contract.** The response shape, pagination behavior, and nested session object are unchanged. The endpoint returns more items (up to approximately 360 with default window sizes), paginated with the same `limit` and `continuation_token` parameters.
* **Environment-tunable window.** The look-back and look-forward window sizes are configurable per deployment. Defaults are chosen to cover multi-week usage periods without generating excessive result counts.

**What you need to do:**

* **Expect more items in the appointments list.** If your integration pages through appointments, be prepared for a larger total result set. The maximum item count with default settings is modest (under 400) and pages through in a few requests at the default page size.
* **Bucket appointments client-side.** The endpoint no longer filters to today only. Clients that display appointments grouped by past, today, and upcoming should bucket by date on the client side.
* **No schema changes required.** The response fields, pagination, and nested session object are identical to the previous release.

</details>

<details>

<summary>Scribe API: Provider Grants No Longer Require a Pre-Existing Provider Entity (July 2026)</summary>

#### Provider Grants No Longer Require a Pre-Existing Provider Entity <a href="#provider-grants-no-longer-require-a-pre-existing-provider-entity" id="provider-grants-no-longer-require-a-pre-existing-provider-entity"></a>

Provider access grants are now created immediately active, even when no provider entity identifier is supplied. A provider no longer needs to exist in the system before being provisioned for Scribe access.

**What changed:**

* **Immediate activation.** Admin and internal grant creation endpoints now produce an `active` grant when the provider's email is verified, regardless of whether a `provider_entity_id` is supplied. Previously, omitting the entity identifier created the grant in a `pending_entity` state that required a subsequent login or verification step to activate.
* **Server-generated scoping identifier.** When `provider_entity_id` is omitted, the server generates a random scoping identifier for the grant. This identifier serves as the opaque ownership key for sessions and downstream access checks. Callers that supply a `provider_entity_id` continue to have that value honored verbatim.
* **No request or response schema changes.** The `provider_entity_id` field on the create request remains optional. The response shape is unchanged. Existing integrations that already supply the field are unaffected.
* **`pending_entity` status preserved for legacy grants.** Grants created before this change that carry `pending_entity` status continue to resolve through the existing login and verification flow. New grants are no longer created in this state.

**What you need to do:**

* **No action required for most integrations.** If your integration creates provider grants through the admin or internal endpoints, grants are now active sooner. Remove any polling or retry logic that waited for a `pending_entity` grant to transition to `active`.
* **Update status handling if needed.** If your integration explicitly checks for the `pending_entity` status on newly created grants, that status is no longer returned for new grants. Existing grants in that state are unaffected.

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