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

# Sessions

List and retrieve provider-owned clinical documentation sessions with lifecycle state and transcript, note, summary, and coding artifact availability.

Sessions represent provider-owned clinical documentation encounters. Each session tracks its mode, lifecycle status, and artifact availability. Use the Scribe API base URL assigned to your deployment; these are not the Platform text-session WebSocket routes.

Read operations require `scribe:sessions:read_own`. Creating, updating, ending, cancelling, and controlling Zoom sessions require `scribe:sessions:write`, in addition to provider ownership and workspace access.

{% hint style="info" %}
Scribe has its own public OpenAPI schema and provider access boundary. Use the [Scribe HTTP Reference](https://docs.concurrence.com/api-reference/readme/scribe) for canonical operation contracts and [Capture to Finalized Note](/developer-guide/guides/scribe-capture-to-note.md) for the complete workflow. Use the Scribe deployment base assigned to your integration, separately from Platform text sessions.
{% endhint %}

## Session Object

Read the session schema in [Get Session](#get-session). Session lifecycle, capture mode, and individual artifact readiness describe different parts of the encounter.

### Artifact Availability

The `artifacts` object reports readiness for each artifact. Inspect the artifact's own [generation state](/developer-guide/platform-api/scribe/artifacts.md) before displaying a note or concluding that generation completed.

## List Sessions

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

Returns a paginated list of sessions owned by the authenticated provider, ordered by creation time (newest first).

### Query Parameters

Use the limit and continuation token in [List Sessions](#list-sessions); pass the returned continuation token unchanged for the next page.

### Response

Use the session items and pagination fields rendered in [List Sessions](#list-sessions).

### Example Request

```bash
curl -H "Authorization: Bearer {token}" \
  "$SCRIBE_API_BASE/v1/{workspace_id}/sessions?limit=10"
```

### Example Response

```json
{
  "items": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "status": "completed",
      "mode": "in_person",
      "external_appointment_id": "APT-12345",
      "local_timezone": "America/New_York",
      "started_at": "2026-07-15T10:00:00Z",
      "ended_at": "2026-07-15T10:30:00Z",
      "created_at": "2026-07-15T09:55:00Z",
      "updated_at": "2026-07-15T10:31:00Z",
      "artifacts": {
        "transcript": "available",
        "note": "available",
        "summary": "available",
        "codes": "available"
      }
    }
  ],
  "has_more": false,
  "continuation_token": null
}
```

## Get Session

```
GET /v1/{workspace_id}/sessions/{session_id}
```

Returns a single session owned by the authenticated provider.

### Path Parameters

Use the matching workspace and the provider-owned session identifier in [Get Session](#get-session).

### Response

Returns a session object (see schema above).

### Errors

| Status | Code        | Description                                                          |
| ------ | ----------- | -------------------------------------------------------------------- |
| 404    | `not_found` | Session does not exist or is not owned by the authenticated provider |

{% hint style="info" %}
**Cancelled sessions are excluded from list results.** The list sessions endpoint does not return sessions with a cancelled status. Cancelled sessions are also excluded when resolving the current session for an appointment. An appointment whose only sessions are cancelled appears with no linked session, the same as an appointment that has never had a session started.
{% endhint %}

## Create an In-Person Session

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

Create a provider-owned microphone session. The general create route accepts only `in_person` mode. Supply a canonical `visit_type`; the service resolves and pins the corresponding supported note template. A supplied `note_template` must match. A missing visit type returns `422 note_template_required`. An unrecognized `visit_type` or `note_template` value fails request validation with `422 validation_error`, and a `note_template` that does not match the visit type returns `422 unsupported_note_template`. No session is created in either case. The former generic templates (`full`, `medical`, `soap`, `dap`, `birp`) and the `medical` visit type are no longer accepted.

Each supported visit type has a telehealth and an in-person template variant (the `-ip` identifiers). When the session links to an appointment whose modality is known, that modality selects the variant, whatever the capture method. Otherwise an in-person session gets the in-person variant and a Zoom session gets the telehealth one. A supplied `note_template` can be the resolved template or its telehealth base, and the service pins the variant it resolved. Read `note_template` on the returned session for the pinned value.

Send `timezone` on every create: the provider's IANA time zone name, such as `America/New_York`. It is required on this route and on [Create a Zoom Session](#create-a-zoom-session); a missing or unrecognized zone returns `422`. The session's `local_timezone` records the zone used for its local times and dates, for example in a configured EHR writeback. It is the supplied `timezone` unless the session links to an appointment whose own time zone can be resolved. A client generated from a schema without `timezone` must add it.

The request also supports patient display-name fields, an optional appointment reference, metadata, and `external_id` for idempotency. Inspect the assigned Scribe schema for field constraints. A successful create returns the session with HTTP 201. Recording attachment and artifact generation are separate operations.

## Session Mode

Every session carries a `mode` that indicates the session modality:

| Value       | Description                                                                                                         |
| ----------- | ------------------------------------------------------------------------------------------------------------------- |
| `in_person` | An in-person microphone recording (default).                                                                        |
| `zoom`      | A Zoom meeting session. A practitioner may have at most one non-terminal Zoom session at a time within a workspace. |

The mode is fixed at creation. The general create-session route accepts `in_person`; use `POST /v1/{workspace_id}/zoom/sessions` for a Zoom session. PATCH does not change the mode.

## Session Lifecycle

{% hint style="info" %}
**In-person (mic) sessions.** In-person mic sessions receive post-visit note generation automatically when they reach the review stage. Note generation is asynchronous. Inspect artifact availability separately from the session transition.
{% endhint %}

A session is `in-progress` from creation; there is no separate created state. Sessions support explicit REST transitions in addition to streaming-driven state changes:

| Transition | Endpoint                                               | From States                | To State    | Notes                                                                              |
| ---------- | ------------------------------------------------------ | -------------------------- | ----------- | ---------------------------------------------------------------------------------- |
| End        | `POST /v1/{workspace_id}/sessions/{session_id}/end`    | `in-progress`              | `in-review` | Refused with `409 session_streaming` while a streaming worker is attached.         |
| Cancel     | `POST /v1/{workspace_id}/sessions/{session_id}/cancel` | `in-progress`, `in-review` | `cancelled` | Compensation and abort path. Terminal sessions return `409 invalid_session_state`. |

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

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

## Update Session

{% openapi src="<https://scribe.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/sessions/{session\_id}" method="patch" %}
<https://scribe.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

Only fields present in the request body are written. Omission preserves a field; an accepted explicit null clears the corresponding nullable value. Mode remains fixed.

Use the operation above for accepted fields and constraints.

Returns the full refreshed session response. Changes to `visit_type` or `note_template` revalidate the supported pair. Changing the visit type also refreshes its checklist; that change is rejected for a terminal session.

### Clinician-Confirmed Visit Times

The session response reports `effective_started_at` and `effective_ended_at`, the visit start and end the EHR note's times use, along with `effective_started_at_edited` and `effective_ended_at_edited`, which indicate whether each value is a clinician edit rather than the recorded `started_at` / `ended_at`. When no edit has been saved, the effective value is the recorded value and the edited flag is `false`. The same four fields appear on an appointment's linked session.

PATCH accepts `effective_started_at` and `effective_ended_at` as timezone-aware values. Omitting a field leaves it untouched; sending an explicit `null` resets it to the recorded `started_at` / `ended_at`. The resulting pair must keep the end after the start, and the visit at most 12 hours long. A start cannot be in the future or more than 12 hours before the session was created; an end can be edited only once the session has ended, cannot be in the future, and cannot be more than 12 hours after the start. An out-of-range value returns `422` with the error `code` `invalid_effective_start` or `invalid_effective_end`, and `details` names the offending field.

Both edits are rejected with `409 invalid_session_state` once the session is terminal (`completed`, `cancelled`, or `failed`). Finalizing a note completes its session, so the times of a submitted note cannot change.

## Create Session Idempotency

When `external_id` is supplied on create, it acts as a per-attempt idempotency key:

* **Matching fingerprint**: A repeated `external_id` with the same effective create parameters returns the existing session. Use the same request when retrying an ambiguous result.
* **Divergent fingerprint**: If the effective create parameters differ from the stored session, the endpoint returns `409` with error code `idempotency_key_conflict`. Generate a new `external_id` for a deliberately new session.

Omitting `external_id` or sending `null` does not supply an idempotency key. Creation remains subject to permissions, validation, and the active-Zoom-session guard.

## Conflict Error Codes

Session lifecycle and create endpoints return `409` responses with a stable machine-readable `code` in the error envelope:

| Code                         | Trigger                                                                     |
| ---------------------------- | --------------------------------------------------------------------------- |
| `idempotency_key_conflict`   | Create reused an `external_id` with a different request fingerprint.        |
| `active_zoom_session_exists` | A second non-terminal Zoom session was attempted for the same practitioner. |
| `session_streaming`          | End was called while a streaming worker is still attached.                  |
| `invalid_session_state`      | The session is not in a state that permits the requested transition.        |
| `conflict`                   | Create used an `external_id` that another provider's session already holds. |

## Zoom

### Zoom Session Lifecycle

The Scribe API manages the full Zoom capture lifecycle server-side. The browser provides a meeting link and disclosure preferences; it never handles Zoom access tokens or communicates with internal capture services.

#### Create a Zoom Session

Supply a canonical `visit_type` that resolves to a supported note template, and the provider's IANA `timezone`. If `note_template` is supplied, it must match that visit type. Missing or unsupported selections fail before a session is created, even though `visit_type` is optional in the generated request model. Template variants and `local_timezone` follow the rules in [Create an In-Person Session](#create-an-in-person-session).

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

Creates a Zoom capture session in one call. The server validates the meeting link, confirms the provider has a connected Zoom account, creates the session, and dispatches the capture bot.

**Error responses:**

| Status | Code                                                  | Description                                                                                                                                                                                                                                                           |
| ------ | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 422    | `validation_error`                                    | The meeting link is not a Zoom URL, or another field failed validation.                                                                                                                                                                                               |
| 422    | `note_template_required`, `unsupported_note_template` | Visit type and template rules from [Create an In-Person Session](#create-an-in-person-session).                                                                                                                                                                       |
| 409    | `active_zoom_session_exists`                          | The provider already has an active Zoom session.                                                                                                                                                                                                                      |
| 409    | `idempotency_key_conflict`                            | The `external_id` was reused with different parameters.                                                                                                                                                                                                               |
| 409    | `zoom_not_connected`                                  | The provider has not connected Zoom.                                                                                                                                                                                                                                  |
| 409    | `zoom_reconnect_required`                             | Zoom rejected the stored connection; the provider must reconnect Zoom.                                                                                                                                                                                                |
| 409    | `zoom_meeting_not_started`                            | The meeting has not started. The session was removed, or cancelled if it could not be removed; start the meeting, then create a new session.                                                                                                                          |
| 409    | `zoom_meeting_not_found`                              | Zoom found no meeting for this link. The session was cancelled; check the meeting link and that the meeting has started, then create a new session.                                                                                                                   |
| 409    | `zoom_meeting_not_live`                               | The link is not the meeting currently running under the provider's Zoom account, or Zoom could not start capture for it. The session was cancelled; start the meeting as the host, or use the link of the meeting the provider is hosting, then create a new session. |
| 503    | `zoom_refresh_unavailable`                            | Refreshing the Zoom connection is temporarily unavailable. Retry.                                                                                                                                                                                                     |
| 503    | `bot_dispatch_failed`                                 | Bot dispatch failed; the session was automatically cancelled. Retryable.                                                                                                                                                                                              |

#### Pause Zoom Bot

{% openapi src="<https://scribe.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/sessions/{session\_id}/zoom/pause" method="post" %}
<https://scribe.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

Pauses the capture bot bound to the session.

**Error responses:**

| Status | Code                 | Description                           |
| ------ | -------------------- | ------------------------------------- |
| 409    | `bot_not_active`     | No live bot is bound to this session. |
| 502    | `bot_command_failed` | The command could not be delivered.   |

#### Resume Zoom Bot

{% openapi src="<https://scribe.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/sessions/{session\_id}/zoom/resume" method="post" %}
<https://scribe.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

Resumes a paused capture bot. Same response and error contract as pause.

#### End Zoom Session

{% openapi src="<https://scribe.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/sessions/{session\_id}/zoom" method="delete" %}
<https://scribe.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

Ends a Zoom session. Returns immediately; the platform stops the bot and finalizes the transcript in the background. Inspect the session and artifact state for the eventual result; the 202 acknowledgement does not establish that finalization has completed.

Idempotent - a second delete returns 202 and is a no-op.

**Error responses:**

| Status | Code               | Description                        |
| ------ | ------------------ | ---------------------------------- |
| 409    | `not_zoom_session` | The session is not a Zoom session. |

### Zoom Connection

Providers can connect their Zoom account through the Scribe API. The platform manages the OAuth lifecycle server-side - the browser initiates the flow and reads connection status, but never handles token material.

### Endpoints

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

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

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

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

The callback is the authorization redirect target and should not be called directly.

### Authorize Response

Navigate to the authorization URL returned by the [Zoom authorize operation](#endpoints) while it remains valid. The browser follows the consent flow; it does not handle provider token material.

### Connection Response

Read the [Zoom connection operation](#endpoints) to confirm the provider connection before creating a capture session. A connected account does not establish that a particular meeting is ready to capture.

### Error Handling

| Status | Condition                                                                                                                      |
| ------ | ------------------------------------------------------------------------------------------------------------------------------ |
| `401`  | Missing or invalid bearer token.                                                                                               |
| `403`  | Insufficient scope or provider mismatch.                                                                                       |
| `503`  | Zoom connect is temporarily unavailable (the authorize endpoint could not mint a verifiable state). Retry after a brief delay. |

The callback endpoint never returns JSON. Every outcome - success or failure - results in a 302 redirect to the configured web settings page with a `zoom_connected=true` or `zoom_error=<reason>` query parameter.


---

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

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

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

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

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

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

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