> This page is for Expression Measurement.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://dev.hume.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://dev.hume.ai/_mcp/server.

# Errors

**The upload and run endpoints return errors as HTTP responses, and realtime sessions send them as `error` messages.** Both include a stable `code` to handle in your application and a `message` for logs and debugging. Some codes, such as `config_invalid`, `message_invalid`, and `internal_error`, appear in both with the same meaning.

## HTTP errors

The [audio upload](/expression-measurement/docs/audio/upload), [image upload](/expression-measurement/docs/video/upload), and [run](/expression-measurement/docs/runs) endpoints return an error as a JSON body with a `4xx` or `5xx` status. The status says how the request failed, and `code` says why.

Each of these endpoints also documents its errors in its own guide: [audio upload errors](/expression-measurement/docs/audio/upload#errors), [image upload errors](/expression-measurement/docs/video/upload#errors), and [run errors](/expression-measurement/docs/runs#errors).

### Response format

#### 400 Bad Request

```json
{
  "type": "error",
  "code": "config_invalid",
  "message": "the config part is not valid: unknown field `timer_ms`, expected `measurement_timer_ms` at line 1 column 11",
  "request_id": "3f9a1c07e2b84d56a0c7e19b5d2f8a64"
}
```

**`type`**

Always `error`.

---

**`code`**

A stable string to branch on. See [HTTP codes](#http-codes).

---

**`message`**

A description for logs and debugging. The wording may change without notice.

---

**`request_id`**

Identifies the request in Hume's records. Quote it when contacting support.

---

A `401` is answered before the request reaches the service, so its body has no `type` or `code`. It carries only a `message`, and a `request_id` when an API key was rejected. See [Authentication](/expression-measurement/docs/authentication).

### HTTP codes

<table>
  <thead>
    <tr>
      <th width="10%">
        Status
      </th>

      <th>
        Code
      </th>

      <th width="12%">
        Endpoints
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        400
      </td>

      <td>
        `message_invalid`
      </td>

      <td>
        Upload
      </td>

      <td>
        The multipart body could not be read, is missing its 

        `file`

         part, or carries a part with another name. Audio upload takes exactly one 

        `file`

         part; image upload takes one or more.
      </td>
    </tr>

    <tr>
      <td>
        400
      </td>

      <td>
        `config_invalid`
      </td>

      <td>
        Upload
      </td>

      <td>
        The 

        `config`

         part is not valid JSON, names an unrecognized field, holds a value outside its range, or appears more than once.
      </td>
    </tr>

    <tr>
      <td>
        400
      </td>

      <td>
        `audio_decode_failed`
      </td>

      <td>
        Audio upload
      </td>

      <td>
        The audio could not be decoded: a WAV file that is truncated or malformed, is at another sample rate, channel count, or sample format, or uses the extensible WAV header, or a file that is empty or is not a whole number of 16-bit samples.
      </td>
    </tr>

    <tr>
      <td>
        400
      </td>

      <td>
        `image_decode_failed`
      </td>

      <td>
        Image upload
      </td>

      <td>
        An image could not be decoded.
      </td>
    </tr>

    <tr>
      <td>
        400
      </td>

      <td>
        `invalid_request`
      </td>

      <td>
        Runs
      </td>

      <td>
        A query parameter has a value the endpoint does not accept, or 

        `cursor`

         is not one the endpoint issued.
      </td>
    </tr>

    <tr>
      <td>
        401
      </td>

      <td>
        None
      </td>

      <td>
        All
      </td>

      <td>
        The request carried no valid API key or access token. See 

        [Authentication](/expression-measurement/docs/authentication)

        .
      </td>
    </tr>

    <tr>
      <td>
        403
      </td>

      <td>
        `forbidden`
      </td>

      <td>
        Upload
      </td>

      <td>
        Your organization is not permitted to use this endpoint. Contact support.
      </td>
    </tr>

    <tr>
      <td>
        404
      </td>

      <td>
        `not_found`
      </td>

      <td>
        All
      </td>

      <td>
        No run with this ID exists in your organization, or no endpoint exists at this path.
      </td>
    </tr>

    <tr>
      <td>
        405
      </td>

      <td>
        `method_not_allowed`
      </td>

      <td>
        All
      </td>

      <td>
        The endpoint does not accept this HTTP method. The 

        `Allow`

         header lists the methods it does accept.
      </td>
    </tr>

    <tr>
      <td>
        413
      </td>

      <td>
        `audio_too_long`
      </td>

      <td>
        Audio upload
      </td>

      <td>
        The request body is over the size limit.
      </td>
    </tr>

    <tr>
      <td>
        413
      </td>

      <td>
        `image_too_large`
      </td>

      <td>
        Image upload
      </td>

      <td>
        The request body or one image is over the size limit, or an image has more pixels than the endpoint accepts.
      </td>
    </tr>

    <tr>
      <td>
        415
      </td>

      <td>
        `unsupported_media_type`
      </td>

      <td>
        Upload
      </td>

      <td>
        The audio is in a format the server recognizes but does not accept, such as MP3 or FLAC, or an image is not a JPEG.
      </td>
    </tr>

    <tr>
      <td>
        500
      </td>

      <td>
        `internal_error`
      </td>

      <td>
        All
      </td>

      <td>
        The server failed while serving the request.
      </td>
    </tr>

    <tr>
      <td>
        503
      </td>

      <td>
        `server_busy`
      </td>

      <td>
        All
      </td>

      <td>
        The service is too busy to serve the request now.
      </td>
    </tr>
  </tbody>
</table>

### Retrying

1. **A request that failed with a `4xx` status fails again if sent unchanged.** Correct what `code` names first. A `403` is not caused by the request, so contact support instead.
2. **After a `500`, retry the request.** If it keeps failing, contact Hume with the `request_id`.
3. **After a `503`, wait before retrying.** The response carries a `Retry-After` header giving the number of seconds to wait.

## Realtime errors

A realtime session reports a problem as an `error` message. After a recoverable error, the session continues. After an unrecoverable error, it sends `session.closed` and closes the connection.

Codes that only one endpoint sends are documented in that endpoint's guide: [audio errors](/expression-measurement/docs/audio/realtime#errors) and [video errors](/expression-measurement/docs/video/realtime#errors).

### Message format

#### error

```json
{
  "type": "error",
  "code": "rate_limited",
  "message": "audio ingest is outpacing the realtime limit; frame dropped",
  "retryable": true
}
```

**`type`**

Always `error`.

---

**`code`**

A stable string to branch on.

---

**`message`**

A description for logs and debugging. The wording may change without notice.

---

**`retryable`**

`false` for every error that ends the session, and `true` for every error the session survives. The exception is `internal_error` on the video endpoint, where it says whether the failure was temporary and the frame is worth resending.

---

On the audio endpoint, the server replies only to a rejected frame, with an `error`. On the video endpoint, it replies to every frame, and [Matching results to frames](/expression-measurement/docs/video/realtime#matching-results-to-frames) explains how to pair each reply with the frame it answers.

### Shared codes

<table>
  <thead>
    <tr>
      <th>
        Code
      </th>

      <th width="12%">
        Session
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        `message_invalid`
      </td>

      <td>
        Continues
      </td>

      <td>
        A text message was not valid JSON, had no 

        `type`

         field, or had a 

        `type`

         the endpoint does not recognize. Media must be sent as binary frames, not as text.
      </td>
    </tr>

    <tr>
      <td>
        `rate_limited`
      </td>

      <td>
        Continues
      </td>

      <td>
        Media arrived faster than the endpoint allows, and the frame was not processed. Slow down, and resend the frame if the stream must be continuous.
      </td>
    </tr>

    <tr>
      <td>
        `config_invalid`
      </td>

      <td>
        Ends
      </td>

      <td>
        A 

        `session.update`

         contained an unrecognized field or an unsupported value, or arrived after the configuration locked at the first media frame. See 

        [Configuration](/expression-measurement/docs/sessions#configuration)

        .
      </td>
    </tr>

    <tr>
      <td>
        `message_too_large`
      </td>

      <td>
        Ends
      </td>

      <td>
        A WebSocket message exceeded the endpoint's size limit: 1 MiB on the audio endpoint, 2 MB on the video endpoint. Send smaller messages.
      </td>
    </tr>

    <tr>
      <td>
        `internal_error`
      </td>

      <td>
        Varies
      </td>

      <td>
        Processing failed unexpectedly. On the audio endpoint the session ends. On the video endpoint the frame is skipped and the session continues, until five consecutive frames fail.
      </td>
    </tr>
  </tbody>
</table>

When an error ends the session, the `error` is followed by `session.closed` with `reason` set to `error`, and the connection closes with code 1008. Open a new connection to start another session.

The problems a session reports, such as rejected frames and failed measurements, are also recorded in its event log. [Getting events](/expression-measurement/docs/runs#getting-events) describes the endpoint that returns the log and every kind of event in it.

### Close codes

When the server ends a session, it closes the WebSocket with one of these codes. The code shows whether `session.closed` was sent first. Close frames carry a code and no reason text, so the reason is in `session.closed`. [Closing](/expression-measurement/docs/sessions#closing) lists the code for each reason.

<table>
  <thead>
    <tr>
      <th width="12%">
        Close code
      </th>

      <th>
        Preceded by
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        1000
      </td>

      <td>
        `session.closed`

         with 

        `reason`

         set to 

        `client_request`

         or 

        `idle_timeout`
      </td>

      <td>
        The session ended normally: the client sent 

        `session.close`

        , or the session was idle.
      </td>
    </tr>

    <tr>
      <td>
        1008
      </td>

      <td>
        `error`

        , then 

        `session.closed`

         with 

        `reason`

         set to 

        `error`
      </td>

      <td>
        An error ended the session. Open a new connection to start another session.
      </td>
    </tr>

    <tr>
      <td>
        1011
      </td>

      <td>
        Nothing
      </td>

      <td>
        The server could not continue the session. Open a new connection to start another session. See 

        [Connection failures](#connection-failures)

        .
      </td>
    </tr>

    <tr>
      <td>
        1012
      </td>

      <td>
        `session.closed`

         with 

        `reason`

         set to 

        `server_shutdown`
      </td>

      <td>
        The server is restarting. Open a new connection right away to start another session.
      </td>
    </tr>
  </tbody>
</table>

### Handshake failures

A handshake with a missing or invalid credential is rejected with `401 Unauthorized`, and a handshake from an organization that is not permitted to use the endpoint is rejected with `403 Forbidden`. Both are rejected before a WebSocket exists, so no `error` message can be sent; read the status from your client library's handshake result. See [Authentication](/expression-measurement/docs/authentication).

### Connection failures

If the server cannot continue a session, it closes the connection with code 1011 without sending `session.closed`. Treat a close that was not preceded by `session.closed` as a lost session and open a new connection.

1. **If the connection drops without a close frame, the session has ended.** Client libraries report this as close code 1006. Open a new connection.
2. **A text message that is not valid UTF-8 ends the session with 1011.**

## Reporting a problem

When contacting Hume, include the identifier of the request or session:

1. **For an upload or run request**, the `request_id` from the response or the error body.
2. **For a realtime session**, the `session_id` from `session.created`.

## Next steps

#### [Limits](/expression-measurement/docs/limits)

The limits of every endpoint, and what happens when a request exceeds one.

#### [Runs](/expression-measurement/docs/runs)

The problems each request and session reported, after the fact.