> ## Documentation Index
> Fetch the complete documentation index at: https://docs.corti.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Real-time stateless dictation

> WebSocket Secure (WSS) API Documentation for /transcribe endpoint

## Overview

The WebSocket Secure (WSS) `/transcribe` API enables real-time, bidirectional communication with the Corti system for stateless speech to text. Clients can send and receive structured data, including transcripts and detected commands.

This documentation provides a comprehensive guide for integrating these capabilities.

<Info>
  This `/transcribe` endpoint supports real-time stateless dictation.

  * If you are looking for real-time ambient documentation interactions, you should use the [/streams WSS](/api-reference/streams)
  * If you are looking for transcript generation based on a pre-recorded audio file, then please refer to the [/transcripts endpoint](/api-reference/transcripts/create-transcript)
</Info>

***

## 1. Establishing a Connection

Clients must initiate a WebSocket connection using the `wss://` scheme.

<Note>The authentication for the WSS streams requires in addition to the `tenant-name` parameter a `token` parameter to pass in the Bearer access token.</Note>

### Query Parameters

<ParamField query="environment" type="enum" required>`eu` or `us`</ParamField>

<ParamField query="tenant-name" type="string" required>
  Specifies the tenant context
</ParamField>

<ParamField query="token" type="string" required>Bearer \$token</ParamField>

{/* Create a Corti SDK client using a Bearer access token */}

<CodeGroup>
  ```ts title="JavaScript" theme={null}
  import { CortiClient } from "@corti/sdk";

  // Replace these with your values
  const ACCESS_TOKEN = "<your-access-token>";

  const client = new CortiClient({
    auth: {
      accessToken: ACCESS_TOKEN,
    },
  });
  ```

  ```csharp title="C# .NET" theme={null}
  using Corti;

  // Replace these with your values
  const string ACCESS_TOKEN = "<your-access-token>";

  var client = new CortiClient(
      auth: CortiClientAuth.Bearer(accessToken: ACCESS_TOKEN)
  );
  ```
</CodeGroup>

<CodeGroup>
  ```bash title="cURL" theme={null}
  # Replace these with your values
  ACCESS_TOKEN="<your-access-token>"
  ENVIRONMENT="<eu-or-us>"
  TENANT="<your-tenant-name>"

  curl --request GET \
    --url "wss://api.${ENVIRONMENT}.corti.app/audio-bridge/v2/transcribe?tenant-name=${TENANT}&token=Bearer%20${ACCESS_TOKEN}"
  ```

  ```ts title="JavaScript" theme={null}
  const transcribeSocket = await client.transcribe.connect();
  ```

  ```csharp title="C# .NET" theme={null}
  var transcribe = await client.CreateTranscribeApiAsync();
  await transcribe.ConnectAsync();
  ```
</CodeGroup>

***

## 2. Handshake Response

### 101 Switching Protocols

Indicates a successful WebSocket connection.

Upon successful connection, send a `config` message to define the configuration: Specify the input language and expected output preferences.

<Info>The config message must be sent within 10 seconds of the web socket being opened to prevent `CONFIG-TIMEOUT`, which will require establishing a new wss connection.</Info>

***

## 3. Sending Messages

### Configuration

Declare your `/transcribe` configuration using the message `"type": "config"` followed by defining the `"configuration": {<config details, per options below>}`.

Defining the type is required along with the `primaryLanguage` configuration parameter. The other parameters are optional for use, depending on your need and workflow.

<Info>
  Configuration notes:

  * Clients must send a streams configuration message and wait for a response of type `CONFIG_ACCEPTED` before transmitting other data.
  * If the configuration is not valid it will return `CONFIG_DENIED`.
  * The configuration must be committed within 10 seconds of opening the WebSocket, else it will time-out with `CONFIG_TIMEOUT`.
</Info>

<ParamField body="type" type="string" default="config" required />

<ParamField body="configuration" type="object" required>
  <Expandable defaultOpen="true">
    <ParamField body="primaryLanguage" type="string" required>
      The locale of the primary spoken language. See supported languages codes and more information [here](/about/languages).
    </ParamField>

    <ParamField body="interimResults" type="bool">
      When true, interim (preview) transcript results (`"isFinal"=false`) will be returned with lower latency than final transcript results.
    </ParamField>

    <ParamField body="spokenPunctuation" type="bool">
      When true, converts spoken punctuation such as `period` or `slash` into `.`or `/`. Read more about supported punctuation [here](/stt/punctuation).
    </ParamField>

    <ParamField body="automaticPunctuation" type="bool">
      When true, automatically punctuates and capitalizes in the final transcript.

      <Info>
        Spoken and Automatic Punctuation are mutually exclusive - only one should be set to true in a given configuration request. If both are included and set to `true`, then `spokenPunctuation` will take precedence and override `automaticPunctuation`.
      </Info>
    </ParamField>

    <ParamField body="commands" type="object">
      Provide the commands that should be registered and detected - Read more about commands [here](/stt/commands).

      <Expandable>
        <ParamField body="id" type="string" required>
          Unique value to identify the command. This, along with the command phrase, will be returned by the API when the command is recognized during dictation.
        </ParamField>

        <ParamField body="phrases" type="string[]" required>
          One or more word sequence(s) that can be spoken to trigger the command. At least one phrase is required per command.
        </ParamField>

        <ParamField body="variables" type="object">
          Placeholders that can (optionally) be added in `phrases` to define multiple words that should trigger the command.

          <Expandable>
            <ParamField body="key" type="string" required>
              Define the variable used in command phrase here.
            </ParamField>

            <ParamField body="type" type="enum" required>
              The only variable type supported at this time is `enum`.
            </ParamField>

            <ParamField body="enum" type="string[]" required>
              List of values that should be recognized for the defined variable.
            </ParamField>
          </Expandable>
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="formatting" type="object">
      Define each type of formatting preferences using the `enum` options described below. Formatting configuration is `optional`, and when no properties are configured, the values listed as `default`<Icon icon="circle-check" /> will be applied automatically.
      <Note>Read more about formatting options, including how localization is handled with detailed examples per language, [here](/stt/formatting).</Note>

      <Expandable>
        <ParamField body="dates" type="enum">
          | Option          | Format      | Example                                                                                      |            Default           |
          | :-------------- | :---------- | :------------------------------------------------------------------------------------------- | :--------------------------: |
          | `as_dictated`   | As dictated | "February third twenty twenty five"<br />"February third"<br />"February twenty twenty five" |                              |
          | `locale:long`   | Long date   | "February 3, 2025"<br />"February 3"<br />"February 2025"                                    | <Icon icon="circle-check" /> |
          | `locale:medium` | Medium date | "Feb 3, 2025"<br />"Feb 3"<br />"Feb 2025"                                                   |                              |
          | `locale:short`  | Short date  | "2/3/25"<br />"2/3"<br />"2/2025"                                                            |                              |
          | `iso`           | ISO 8601    | "2025-02-03"<br />"02-03"<br />"2025-02"                                                     |                              |
        </ParamField>

        <ParamField body="times" type="enum">
          | Option        | Format                                                          | Example                                                                  |            Default           |
          | :------------ | :-------------------------------------------------------------- | :----------------------------------------------------------------------- | :--------------------------: |
          | `as_dictated` | As dictated                                                     | "Eight o'clock"<br />"half past eight"<br />"eight thirty six PM"        |                              |
          | `locale`      | 12 or 24-hour, depending on `primaryLanguage` defined in config | "8:00 AM" or "08:00"<br />"8:30 AM" or "08:30"<br />"8:36 PM" or "20:36" | <Icon icon="circle-check" /> |
          | `h24`         | 24-hour                                                         | "08:00"<br />"08:30"<br />"20:36"                                        |                              |
          | `h12`         | 12-hour                                                         | "8:00 AM"<br />"8:30 AM"<br />"8:36 PM"                                  |                              |
        </ParamField>

        <ParamField body="numbers" type="enum">
          | Option                | Format                                       | Example                                  |            Default           |
          | :-------------------- | :------------------------------------------- | :--------------------------------------- | :--------------------------: |
          | `as_dictated`         | As dictated                                  | "one, two, ... nine, ten, eleven, twelve |                              |
          | `numerals_above_nine` | Single digit as words, multi-digit as number | "One, two ... nine, 10, 11, 12"          | <Icon icon="circle-check" /> |
          | `numerals`            | Numbers only                                 | "1, 2, ... 9, 10, 11, 12"                |                              |

          <Info>Localization of numbers applied automatically for thousands and decimal separators</Info>
        </ParamField>

        <ParamField body="measurements" type="enum">
          | Option        | Format      | Example                                                                   |            Default           |
          | :------------ | :---------- | :------------------------------------------------------------------------ | :--------------------------: |
          | `as_dictated` | As dictated | "Millimeters, centimeters, inches; Blood pressure one twenty over eighty" |                              |
          | `abbreviated` | Abbreviated | "mm, cm, in; BP 120/80"                                                   | <Icon icon="circle-check" /> |

          <Info>[Click here](/stt/formatting#units-and-measurements) to see a full list of supported units and measurements</Info>
        </ParamField>

        <ParamField body="numericRanges" type="enum">
          | Option        | Format      | Example      |            Default           |
          | :------------ | :---------- | :----------- | :--------------------------: |
          | `as_dictated` | As dictated | "one to ten" |                              |
          | `numerals`    | As numbers  | "1-10"       | <Icon icon="circle-check" /> |
        </ParamField>

        <ParamField body="ordinals" type="enum">
          | Option                | Format                                              | Example                                     |            Default           |
          | :-------------------- | :-------------------------------------------------- | :------------------------------------------ | :--------------------------: |
          | `as_dictated`         | As dictated                                         | "First, second, third"                      |                              |
          | `numerals_above_nine` | First through ninth as words, multi-digit as number | "First, second ... ninth, 10th, 11th, 12th" | <Icon icon="circle-check" /> |
          | `numerals`            | Abbreviated                                         | "1st, 2nd, 3rd"                             |                              |
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="audioFormat" type="string">
      Define the audio format of the incoming audio stream - optional but recommended.

      * When omitted, the server auto-detects the format from the first audio chunk using ffprobe. Supported audio will be processed. Unsupported return an error but might in some cases error silently.
      * If provided (recommended), the provided MIME type must be supported and the audio must match the MIME type. An unsupported MIME type results in `CONFIG_REJECTED`. Audio that differs from the MIME type will return audio validation errors on the socket.

      See more about supported formats [here](/stt/audio).
    </ParamField>

    <ParamField body="audioEvents" type="object">
      When true, enables audio quality and speech activity events to be sent over the WebSocket. Disabled by default.

      <Expandable>
        <ParamField body="enabled" type="boolean" required>
          The following events are supported:

          * Speech quality issue detected / recovered
          * Long silence detected / recovered
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

#### Example

Here is an example configuration for transcription of dictated audio in English with spoken punctuation enabled, two commands defined, and (default) formatting options defined:

<CodeGroup>
  ```json title="wss:/transcribe configuration example" theme={null}
  {
    "type": "config",
    "configuration":{
      "primaryLanguage": "en",
      "interimResults": true,
      "spokenPunctuation": true, 
      "commands": [
        {
          "id": "next_section",
          "phrases": [
            "next section", "go to next section"
          ]
        },
        {
          "id": "insert_template",
          "phrases": [
            "insert my {template_name} template", "insert {template_name} template"
          ],
          "variables": [
            {
              "key": "template_name",
              "type": "enum",
              "enum": [
                "soap", "radiology", "referral"
              ]
            }
          ]
        }
      ],
      "formatting": {
        "dates": "locale:long",
        "times": "locale",
        "numbers": "numerals_above_nine",
        "measurements": "abbreviated",
        "numericRanges": "numerals",
        "ordinals": "numerals_above_nine"
      },
      "audioFormat": "audio/ogg",
      "audioEvents":{
        "enabled": true
      }
    }
  }
  ```

  ```ts title="JavaScript" theme={null}
  const configuration = {
      primaryLanguage: "en",
      spokenPunctuation: true,
      commands: [
        {
          id: "next_section",
          phrases: ["next section", "go to next section"]
        },
      ]
    };

  const transcribeSocket = await client.transcribe.connect(
    { configuration }
  );
  ```

  {/* Configure /transcribe via ConnectAsync — waits for CONFIG_ACCEPTED */}

  ```csharp title="C# .NET" theme={null}
  var transcribe = await client.CreateTranscribeApiAsync();

  await transcribe.ConnectAsync(new TranscribeConfig
  {
      PrimaryLanguage = "en",
      SpokenPunctuation = true,
      Commands = new List<TranscribeCommand>
      {
          new()
          {
              Id = "next_section",
              Phrases = new List<string> { "next section", "go to next section" },
          },
      },
  });
  ```
</CodeGroup>

### Sending Audio

<Info>
  Ensure that your configuration was accepted before sending audio, and that the initial audio chunk is not too small as it needs to contain the headers to properly decode the audio.

  We recommend sending audio in chunks of 250-500ms. In terms of buffering, the limit is 64000 bytes per chunk.

  Audio data should be sent as raw binary without JSON wrapping.
</Info>

A variety of common audio formats are supported; audio will be passed through a transcoder before speech-to-text processing. Similarly, specification of sample rate, depth or other audio settings is not required at this time.

See more details on supported audio formats [here](/stt/audio).

<CodeGroup>
  ```ts title="JavaScript" theme={null}
  transcribeSocket.sendAudio(audioChunk); // method doesn't do the chunking
  ```

  {/* Send binary audio over a transcribe socket */}

  ```csharp title="C# .NET" theme={null}
  await transcribe.Send(audioChunkBytes); // method doesn't do the chunking
  ```
</CodeGroup>

### Flush the Audio Buffer

To flush the audio buffer, forcing transcript segments and detected commands to be returned over the web socket (e.g., when turning off or muting the microphone in a "hold-to-talk" dictation workflow, or in applications that support mic "go to sleep"), send a message -

<CodeGroup>
  ```json title="Raw message" theme={null}
  {
    "type":"flush"
  }
  ```

  ```ts title="JavaScript" theme={null}
  transcribeSocket.sendFlush({ type: "flush" });
  ```

  {/* Send flush request to force processing of buffered audio */}

  ```csharp title="C# .NET" theme={null}
  await transcribe.Send(new TranscribeFlushMessage());
  ```
</CodeGroup>

The server will return text/commands for audio sent before the `flush` message and then respond with messages -

```json theme={null}
{
  "type": "flushed"
}
```

```json theme={null}
{
  "type": "delta_usage",
  "credits": 0.00116
}
```

`Delta usage` represents incremental credit consumption between recording initiation and `flush` events. Delta usage is approximate and may differ slightly from final `usage` sent after `end` message is processed (see [below](/api-reference/transcribe#usage)). Final, end session usage will be reflected in API billing.

The web socket will remain open after `flush` processing so dictation can continue.

<Tip>
  Client side considerations:

  <sup>1</sup> If you rely on a `flush` event to separate data (e.g., for different sections in an EHR template), then be sure to receive the `flushed` event before moving on to the next data field.

  <sup>2</sup> When using a web browser `MediaRecorder` API, audio is buffered and only emitted at the configured timeslice interval. Therefore, *before* sending a `flush` message, call `MediaRecorder.requestData()` to force any remaining buffered audio on the client to be transmitted to the server. This ensures all audio reaches the server before the `flush` is processed.
</Tip>

### Ending the Session

To end the `/transcribe` session, send a message -

<CodeGroup>
  ```json title="Raw message" theme={null}
  {
    "type": "end"
  }
  ```

  ```ts title="JavaScript" theme={null}
  transcribeSocket.sendEnd({ type: "end" });
  ```

  {/* Send end message to signal no more audio */}

  ```csharp title="C# .NET" theme={null}
  await transcribe.Send(new TranscribeEndMessage());
  ```
</CodeGroup>

This will signal the server to send any remaining transcript segments and/or detected commands. Then, the server will send two messages -

```json theme={null}
{
  "type": "usage",
  "credits": 0.1
}
```

```json theme={null}
{
  "type": "ended"
}
```

Following the message type `ended`, the server will close the web socket.

***

## 4. Responses

### Configuration

<ResponseField name="type" type="string" required default="CONFIG_ACCEPTED">
  Returned when sending a valid configuration.
</ResponseField>

<ResponseField name="sessionId" type="uuid" required>
  Returned when sending a valid configuration.
</ResponseField>

<ResponseField name="configuration" type="object">
  The resolved configuration, including accepted client-defined values and server-applied defaults for parameters not defined in client configuration.
</ResponseField>

### Transcripts

<ResponseField name="type" type="string" required default="transcript">Server message indicated recognized speech to text</ResponseField>

<ResponseField name="data" type="object" required>
  <Expandable>
    <ResponseField name="text" type="string" required>
      Transcript segment with punctuations applied and command phrases removed
    </ResponseField>

    <ResponseField name="rawTranscriptText" type="string">
      The raw transcript without spoken punctuation applied and without command phrases removed
    </ResponseField>

    <ResponseField name="start" type="float64">
      Start time of the transcript segment in seconds
    </ResponseField>

    <ResponseField name="end" type="float64">
      End time of the transcript segment in seconds
    </ResponseField>

    <ResponseField name="isFinal" type="bool" required>
      If false, then interim transcript result
    </ResponseField>
  </Expandable>
</ResponseField>

```json Transcript response  theme={null}
{
    "type": "transcript",
    "data": {
        "text": "patient reports mild chest pain.",
        "rawTranscriptText": "patient reports mild chest pain period",
        "start": 0.0,
        "end": 3.2,
        "isFinal": true
    }
}
```

<Callout icon="arrow-right">**[Click here](/stt/best-practices-transcribe)** for detailed guide on how to properly insert transcript segments with proper handling of whitespace, interim vs. final results, and `text` vs. `rawTranscriptText` fields. </Callout>

### Commands

<ResponseField name="type" type="string" required default="command">Server message indicating a recognized Command</ResponseField>

<ResponseField name="data" type="object" required>
  <Expandable>
    <ResponseField name="id" type="string" required>
      To identify the command when it gets detected and returned over the WebSocket
    </ResponseField>

    <ResponseField name="variables" type="string[]">
      The variables identified
    </ResponseField>

    <ResponseField name="rawTranscriptText" type="string">
      The raw transcript without spoken punctuation applied and without command phrases removed
    </ResponseField>

    <ResponseField name="start" type="float64">
      Start time of the transcript segment in seconds
    </ResponseField>

    <ResponseField name="end" type="float64">
      End time of the transcript segment in seconds
    </ResponseField>
  </Expandable>
</ResponseField>

```json Command response theme={null}
{
  "type": "command",
  "data": {
    "id": "insert_template",
    "variables": {
      "template_name": "radiology"
    },
    "rawTranscriptText": "insert my radiology template",
    "start": 2.3,
    "end": 2.9
  }
}
```

### Audio Events

<ResponseField name="type" default="audioEvent" type="string">Server message indicating an audio quality or speech activity event</ResponseField>

<ResponseField name="data" type="object">
  <Expandable>
    <ResponseField name="event" type="string" required>
      The type of audio quality or speech activity event.

      Possible values: `speechQualityIssueDetected`, `speechQualityIssueRecovered`, `longSilenceDetected`, `longSilenceRecovered`
    </ResponseField>

    <ResponseField name="channel" type="integer" required>
      Audio channel identifier
    </ResponseField>

    <ResponseField name="startTimeMs" type="integer" required>
      Start time of the event in milliseconds
    </ResponseField>
  </Expandable>
</ResponseField>

```json Audio Event response theme={null}
{
  "type": "audioEvent",
  "data": {
    "event": "speechQualityIssueDetected",
    "channel": 0,
    "startTimeMs": 4200
  }
}
```

### Flushed

<ResponseField name="type" type="string" default="flushed">
  Returned by server, after processing `flush` event from client, to return transcript segments/ detected commands
</ResponseField>

```json theme={null}
{
  "type": "flushed"
}
```

### Usage

<ResponseField name="type" type="string" default="delta-usage">
  Returned by server, after processing `flush` event from client, to convey amount of credits consumed since recording started. Delta usage is approximate and may differ slightly from final `usage` sent after `end` message is processed.
</ResponseField>

```json theme={null}
{
  "type": "delta_usage",
  "credits": 0.00116
}
```

<ResponseField name="type" type="string" default="usage">
  Returned by server, after processing `end` event from client, to convey amount of credits consumed
</ResponseField>

```json theme={null}
{
  "type": "usage",
  "credits": 0.1
}
```

### Ended

<ResponseField name="type" type="string" required default="ended">
  Returned by server, after processing `end` event from client, before closing the web socket
</ResponseField>

```json theme={null}
{
  "type": "ended"
}
```

### Subscribe to messages in SDK

<CodeGroup>
  {/* Handle all incoming transcribe message types */}

  ```ts expandable theme={null}
  socket.on("message", (msg) => {
      switch (msg.type) {
          case "transcript":
              console.log("Transcript:", msg.data.text, "final:", msg.data.isFinal);
              break;
          case "command":
              console.log("Command:", msg.data.id, msg.data.variables);
              break;
          case "flushed":
              console.log("Flush complete");
              break;
          case "ended":
              console.log("Session ended");
              socket.close();
              break;
          case "usage":
              console.log("Credits used:", msg.credits);
              break;
          case "error":
              console.error("Server error:", msg.error);
              break;
      }
  });
  ```

  {/* Subscribe to all transcribe message types — register before ConnectAsync */}

  ```csharp title="C# .NET" expandable theme={null}
  transcribe.TranscribeTranscriptMessage.Subscribe(message =>
  {
      Console.WriteLine($"Transcript: {message.Data.Text}");
  });

  transcribe.TranscribeCommandMessage.Subscribe(message =>
  {
      Console.WriteLine($"Command: {message.Data.Id}");
  });

  transcribe.TranscribeFlushedMessage.Subscribe(_ =>
  {
      Console.WriteLine("Flush complete");
  });

  transcribe.TranscribeEndedMessage.Subscribe(_ =>
  {
      Console.WriteLine("Session ended");
  });

  transcribe.TranscribeUsageMessage.Subscribe(message =>
  {
      Console.WriteLine($"Credits used: {message.Credits}");
  });

  transcribe.TranscribeErrorMessage.Subscribe(message =>
  {
      Console.Error.WriteLine($"Server error: {message.Error.Title} ({message.Error.Status})");
  });
  ```
</CodeGroup>

***

## 5. Error Handling

<ResponseField name="type" type="string" required>
  Returned when sending an invalid configuration.

  Possible errors: `CONFIG_DENIED`, `CONFIG_TIMEOUT`, `CONFIG_ALREADY_RECEIVED`, `CONFIG_MISSING`
</ResponseField>

<ResponseField name="reason" type="string">
  The reason the configuration is invalid.
</ResponseField>

<ResponseField name="sessionId" type="uuid" required>
  The session ID.
</ResponseField>

Once configuration has been accepted and the session is running, you may encounter runtime or application-level errors. These are sent as JSON objects with the following structure:

```json theme={null}
{
  "type": "error",
  "error": {
    "id": "error id",
    "title": "error title",
    "status": 400,
    "details": "error details",
    "doc":"link to documentation"
  }
}
```

### Handle errors in SDK

With the recommended approach (passing configuration as part of `connect`), configuration errors are raised during `connect` (the call fails). Runtime errors are emitted via the <code>error</code> event; you can also inspect the original message in the <code>message</code> handler.

<CodeGroup>
  {/* Transcribe error handling: connect rejection and runtime errors */}

  ```ts expandable theme={null}
  try {
      const socket = await client.transcribe.connect({
          configuration: {
              primaryLanguage: "en",
              automaticPunctuation: true,
          },
      });

      socket.on("error", (err) => {
          // Network errors and reconnect failures
          console.error("Socket error:", err.message);
      });

      socket.on("message", (msg) => {
          if (msg.type === "error") {
              // Server-sent runtime error
              console.error("Server error:", msg.error);
          }
      });
  } catch (err) {
      // CONFIG_DENIED, CONFIG_TIMEOUT, or connection failure
      console.error("Connect failed:", err.message);
  }
  ```

  {/* Transcribe error handling: subscribe before connecting, catch ConnectAsync rejection */}

  ```csharp title="C# .NET" expandable theme={null}
  var transcribe = await client.CreateTranscribeApiAsync();

  transcribe.ExceptionOccurred.Subscribe(ex =>
  {
      Console.Error.WriteLine($"Socket error: {ex.Message}");
  });

  transcribe.TranscribeErrorMessage.Subscribe(message =>
  {
      Console.Error.WriteLine($"Server error: {message.Error.Title} ({message.Error.Status})");
  });

  try
  {
      await transcribe.ConnectAsync(new TranscribeConfig
      {
          PrimaryLanguage = "en",
          AutomaticPunctuation = true,
      });
  }
  catch (InvalidOperationException ex)
  {
      // CONFIG_DENIED, CONFIG_TIMEOUT, or connection failure
      Console.Error.WriteLine($"Connect failed: {ex.Message}");
      throw;
  }
  ```
</CodeGroup>
