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

# Errors & Backpressure

> The error frame schema, the full error code table, fatal and close semantics, and backpressure handling

## Error frame

When something goes wrong after the socket is accepted, the server sends a JSON `error` frame:

```json theme={null}
{
  "type": "error",
  "code": "session_conflict",
  "message": "another active session exists for this user",
  "fatal": true
}
```

<ResponseField name="type" type="string">
  Always `"error"`.
</ResponseField>

<ResponseField name="code" type="string">
  A stable, machine-readable code string. Branch on this — see the [table](#error-codes) below.
</ResponseField>

<ResponseField name="message" type="string">
  A short, human-readable, content-free description. Safe to log. Never contains audio or transcript text.
</ResponseField>

<ResponseField name="fatal" type="boolean">
  When `true`, the session cannot continue: the server closes the socket immediately after this frame. When `false`, the session continues and you may keep streaming.
</ResponseField>

<Warning>
  Always branch on the boolean **`fatal`** field, not on the code alone. Some codes can be delivered either fatally or non-fatally depending on context.
</Warning>

## Error codes

| Code               | Typically fatal    | Meaning                                                                                                                             | What to do                                                                                                                                                       |
| ------------------ | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `auth_failed`      | yes (close `1008`) | The JWT was invalid, expired, identified no user, or lacked the `transcribe` permission.                                            | Obtain a fresh, valid token with the right permission and reconnect. Delivered as close code **`1008`** during the handshake — usually **without** a JSON frame. |
| `config_timeout`   | yes                | No `config` frame arrived within \~10 seconds of connecting.                                                                        | Send the `config` frame immediately on `open`.                                                                                                                   |
| `config_invalid`   | yes                | The first message was not a valid `config` — malformed JSON, wrong/missing `type`, unknown or extra fields, or out-of-range values. | Fix the config payload (send only documented fields, valid ranges) and reconnect.                                                                                |
| `session_conflict` | yes                | Another active session already exists for this user.                                                                                | Close the previous socket, then reconnect. See [one session per user](/websocket/reconnection#one-session-per-user).                                             |
| `overloaded`       | yes                | The service is momentarily at capacity.                                                                                             | Retry after a short backoff (with jitter).                                                                                                                       |
| `unavailable`      | yes                | The service is starting up or temporarily not ready.                                                                                | Retry shortly.                                                                                                                                                   |
| `asr_failed`       | usually no         | Transcription failed for a segment.                                                                                                 | If non-fatal, keep streaming — a single segment may be missing. If fatal, reconnect (resuming from `processed_until`).                                           |
| `internal_error`   | varies             | An unexpected server-side condition.                                                                                                | Check `fatal`. If fatal, reconnect after a short backoff.                                                                                                        |

<Note>
  `auth_failed` is special: authentication is checked **during the handshake**, so a failure is signaled by closing the socket with code **`1008`** rather than by an in-session JSON `error` frame. Detect it in your `onclose` handler.
</Note>

## Fatal & close semantics

<Steps>
  <Step title="Handshake rejection (auth)">
    An authentication failure closes the socket with code **`1008`** before acceptance. There is no `session_ready` and typically no JSON `error` frame.
  </Step>

  <Step title="In-session fatal error">
    After acceptance, a fatal condition is sent as an `error` frame with `"fatal": true`, and the server then closes the socket. Handle the frame, then treat the close as expected.
  </Step>

  <Step title="Non-fatal error">
    An `error` frame with `"fatal": false` is informational — the session stays open and you can keep streaming.
  </Step>

  <Step title="Clean close">
    You end a session with a `close` control frame (or by closing the socket). The server finalizes any buffered audio first.
  </Step>
</Steps>

```javascript Error handling theme={null}
ws.onmessage = (event) => {
  if (typeof event.data !== "string") return;
  const msg = JSON.parse(event.data);
  if (msg.type !== "error") return;

  console.warn("error:", msg.code, msg.message);

  switch (msg.code) {
    case "session_conflict":
      // A previous session is still open; close it, then retry.
      break;
    case "overloaded":
    case "unavailable":
      scheduleReconnect(backoffWithJitter());
      break;
    case "config_invalid":
    case "config_timeout":
      // Fix the config; do not blindly retry the same payload.
      break;
    default:
      if (!msg.fatal) return; // non-fatal: keep going
  }
  if (msg.fatal) ws.close();
};

ws.onclose = (e) => {
  if (e.code === 1008) {
    console.error("Authentication failed — refresh the token and reconnect");
  }
};
```

## Backpressure handling

Backpressure is **not** an error — it is a flow-control signal telling you that audio is arriving faster than the session can finalize it.

```json theme={null}
{ "type": "backpressure", "level": "warn", "dropped_segments": 1 }
```

<ResponseField name="level" type="string">
  Severity, for example `"warn"`.
</ResponseField>

<ResponseField name="dropped_segments" type="integer">
  How many buffered segments were dropped to keep the session live (`0` on an early warning, before anything is dropped).
</ResponseField>

**What to do:**

<Steps>
  <Step title="Send closer to real time">
    Pace your binary frames to the audio's own duration. The most common cause is streaming a file or backlog far ahead of real time.
  </Step>

  <Step title="Reduce burst size">
    Send smaller, more frequent frames rather than large bursts.
  </Step>

  <Step title="Recover dropped audio if needed">
    If `dropped_segments > 0` and you cannot tolerate the gap, use your buffer and the [reconnection](/websocket/reconnection) primitives to resend audio after the last `processed_until`.
  </Step>
</Steps>

<Note>
  A `backpressure` frame may include additional advisory numeric fields; treat them as opaque. The correct response is always to slow the send rate toward real time.
</Note>

## status frames

Not every notification is an error. The server may send a `status` frame for informational session-state changes:

```json theme={null}
{ "type": "status", "state": "idle_timeout" }
```

For example, `idle_timeout` means the session received no audio for an extended period (roughly two minutes) and is being closed. Keep a session alive by sending audio, or an `end_utterance`, periodically.

## Getting help

<Card title="Contact support" icon="life-ring" href="mailto:support@medisync.me">
  If you hit an error you cannot resolve, email **[support@medisync.me](mailto:support@medisync.me)** with the error `code`, `message`, and the time of the failure. Never include audio, transcript text, or your JWT.
</Card>

## Next steps

<CardGroup cols={2}>
  <Card title="Reconnection" icon="rotate" href="/websocket/reconnection">
    Recover from fatal drops with no lost transcript.
  </Card>

  <Card title="Connection" icon="plug" href="/websocket/connection">
    Close codes and the handshake sequence.
  </Card>
</CardGroup>
