> This page is for Audio Decisions.

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

# TypeScript quickstart

**Ask questions about a recording from TypeScript.** The script below asks one question of each primitive about the same audio and prints every answer.

<table>
  <tbody>
    <tr>
      <th width="20%">
        Package
      </th>

      <td>
        [`@humeai/audio-decisions`](https://www.npmjs.com/package/@humeai/audio-decisions)

         on npm
      </td>
    </tr>

    <tr>
      <th>
        Source
      </th>

      <td>
        [hume-audio-decisions-typescript-sdk](https://github.com/HumeAI/hume-audio-decisions-typescript-sdk)

         on GitHub
      </td>
    </tr>

    <tr>
      <th>
        Node.js
      </th>

      <td>
        18 or later, or 22.18 or later for TypeScript
      </td>
    </tr>
  </tbody>
</table>

The package ships TypeScript types for every request and answer. The script contains no type annotations, so on Node.js earlier than 22.18 it also runs as a `.js` file.

## Prerequisites

1. Node.js 22.18 or later, which runs TypeScript directly, or 18 or later to run the script as JavaScript.
2. An API key from the [API keys page](https://cloud.hume.ai/settings/organization/api-keys) of the Hume Platform. See [Authentication](/audio-decisions/docs/authentication).
3. A recording of speech 5 seconds to 5 minutes long, in MP3 or WAV.

## Install

Create a project and install the SDK in it. The script is an ES module, so the commands set `"type": "module"` in the project's `package.json`, and they install the Node.js type definitions, which the SDK's types refer to. To add the SDK to an existing project, run only the commands your project still needs.

#### bun

#### bun

```bash
mkdir decisions-quickstart
cd decisions-quickstart
bun init --yes --minimal
bun pm pkg set type=module
bun add @humeai/audio-decisions
bun add --dev @types/node
```

#### pnpm

#### pnpm

```bash
mkdir decisions-quickstart
cd decisions-quickstart
pnpm init
pnpm pkg set type=module
pnpm add @humeai/audio-decisions
pnpm add --save-dev @types/node
```

#### npm

#### npm

```bash
mkdir decisions-quickstart
cd decisions-quickstart
npm init --yes
npm pkg set type=module
npm install @humeai/audio-decisions
npm install --save-dev @types/node
```

#### yarn

#### yarn

```bash
mkdir decisions-quickstart
cd decisions-quickstart
yarn init -y
yarn config set nodeLinker node-modules
npm pkg set type=module
yarn add @humeai/audio-decisions
yarn add --dev @types/node
```

Yarn has no command to edit `package.json`, so the yarn commands set the module type with npm, which ships with Node.js. `nodeLinker node-modules` installs packages into `node_modules`, where `node` finds them.

## Set your API key

The client reads the key from the `HUME_API_KEY` environment variable and sends it in the `X-Hume-Api-Key` header of every request.

#### Shell

```bash
export HUME_API_KEY=your_api_key
```

To pass the key explicitly instead, construct the client with `new DecisionsClient({ apiKey: "..." })`.

## Ask questions about a recording

#### Create the client

Create `ask-questions.ts`. It asks four questions about `speech.wav` in one request and prints each answer.

`new DecisionsClient()` reads your API key from `HUME_API_KEY`. The script also imports `DecisionsError`, which a failed request throws.

**`ask-questions.ts (lines 1-3)`**

```typescript ask-questions.ts (lines 1-3)
import { DecisionsClient, DecisionsError } from "@humeai/audio-decisions";

const client = new DecisionsClient();
```

#### Send the recording

`client.decisions.create()` sends the questions and the audio as a `multipart/form-data` request and resolves to the parsed response. `contentType` labels the file part as WAV. To send an MP3 file, pass its path with `audio/mpeg`.

**`ask-questions.ts (lines 6-10)`**

```typescript ask-questions.ts (lines 6-10)
  const response = await client.decisions.create({
    file: {
      path: "speech.wav",
      contentType: "audio/wav",
    },
```

#### Write the questions

Each question is keyed by an ID you choose, and its `type` names its primitive. `noul` asks yes or no, `choice` picks from unordered options, `score` places the audio on ordered levels, and `span` finds when an event occurs. To describe the recording, such as the setting or who is speaking, add `context` to `request` alongside `questions`. [Primitives](/audio-decisions/docs/primitives) covers what each question takes.

**`ask-questions.ts (lines 11-36)`**

```typescript ask-questions.ts (lines 11-36)
    request: {
      questions: {
        hesitant: {
          type: "noul",
          instructions: "Does the speaker sound hesitant?",
        },
        tone: {
          type: "choice",
          instructions: "Which best describes the speaker's tone?",
          criteria: [
            { name: "warm" },
            { name: "neutral" },
            { name: "curt" },
          ],
        },
        naturalness: {
          type: "score",
          instructions: "How natural does the voice sound?",
          criteria: ["Not at all", "2", "3", "4", "Extremely"],
        },
        vocal_bursts: {
          type: "span",
          instructions: "When does the speaker laugh, sigh or make another vocal burst?",
        },
      },
    },
```

#### Read the answers

The response returns each answer under its question's ID, and the `runId` of the model run that answered them. Switching on `type` narrows each answer to its fields. A `noul` answer is the probability of yes, `choice` and `score` answers give the probability of each option or level, and a `span` answer lists the time spans where the event may occur.

**`ask-questions.ts (lines 39-72)`**

```typescript ask-questions.ts (lines 39-72)
  for (const [questionId, answer] of Object.entries(
    response.answers,
  )) {
    switch (answer.type) {
      case "noul":
        console.log(`${questionId}: yes ${answer.noul.toFixed(2)}`);
        break;
      case "choice":
        console.log(
          `${questionId}: ${answer.choice}, ` +
            `confidence ${answer.confidence.toFixed(2)}`,
        );
        break;
      case "score": {
        const [level] = Object.entries(answer.probabilities).reduce(
          (most, entry) => (entry[1] > most[1] ? entry : most),
        );
        console.log(
          `${questionId}: ${answer.score.toFixed(2)}, ` +
            `most likely "${answer.legend[level]}"`,
        );
        break;
      }
      case "span":
        for (const span of answer.spans) {
          console.log(
            `${questionId}: ${span.audioStartMs} to ` +
              `${span.audioEndMs} ms, ${span.probability.toFixed(2)}`,
          );
        }
        break;
    }
  }
  console.log(`run ${response.runId}`);
```

#### Handle errors

A failed request throws a `DecisionsError`. Its `message` includes the HTTP status and the error body, whose `code` says what went wrong and whose `param` says where in the request it was found. To branch on them, read `statusCode` and `body`. See [Errors](/audio-decisions/docs/errors).

**`ask-questions.ts (lines 73-76)`**

```typescript ask-questions.ts (lines 73-76)
} catch (error) {
  if (!(error instanceof DecisionsError)) throw error;
  console.error(error.message);
}
```

#### Complete files

**`ask-questions.ts`**

```typescript title="ask-questions.ts"
import { DecisionsClient, DecisionsError } from "@humeai/audio-decisions";

const client = new DecisionsClient();

try {
  const response = await client.decisions.create({
    file: {
      path: "speech.wav",
      contentType: "audio/wav",
    },
    request: {
      questions: {
        hesitant: {
          type: "noul",
          instructions: "Does the speaker sound hesitant?",
        },
        tone: {
          type: "choice",
          instructions: "Which best describes the speaker's tone?",
          criteria: [
            { name: "warm" },
            { name: "neutral" },
            { name: "curt" },
          ],
        },
        naturalness: {
          type: "score",
          instructions: "How natural does the voice sound?",
          criteria: ["Not at all", "2", "3", "4", "Extremely"],
        },
        vocal_bursts: {
          type: "span",
          instructions: "When does the speaker laugh, sigh or make another vocal burst?",
        },
      },
    },
  });

  for (const [questionId, answer] of Object.entries(
    response.answers,
  )) {
    switch (answer.type) {
      case "noul":
        console.log(`${questionId}: yes ${answer.noul.toFixed(2)}`);
        break;
      case "choice":
        console.log(
          `${questionId}: ${answer.choice}, ` +
            `confidence ${answer.confidence.toFixed(2)}`,
        );
        break;
      case "score": {
        const [level] = Object.entries(answer.probabilities).reduce(
          (most, entry) => (entry[1] > most[1] ? entry : most),
        );
        console.log(
          `${questionId}: ${answer.score.toFixed(2)}, ` +
            `most likely "${answer.legend[level]}"`,
        );
        break;
      }
      case "span":
        for (const span of answer.spans) {
          console.log(
            `${questionId}: ${span.audioStartMs} to ` +
              `${span.audioEndMs} ms, ${span.probability.toFixed(2)}`,
          );
        }
        break;
    }
  }
  console.log(`run ${response.runId}`);
} catch (error) {
  if (!(error instanceof DecisionsError)) throw error;
  console.error(error.message);
}
```

## Run

#### Shell

```bash
node ask-questions.ts
```

Each probability estimates the share of human raters who would give that answer, so a `noul` answer near 0.5 means raters would split on the audio, not that the model is unsure. Ask the same questions in the same order when you compare recordings, since an answer can change when other questions are added, removed or reordered.

## Audio helpers

`create` converts a WAV file to 16 kHz mono before sending it, because the model hears audio as one channel at 16 kHz. The converted file is smaller, so a WAV file over the size limit of 25,000,000 bytes can still fit in a request. Pass `downmixStrategy: "first_channel"` to keep only the first channel, such as to judge one speaker of a call apart from the other, or `downmixStrategy: "none"` to send the file as it is. MP3 files are always sent as they are. See [Preparing audio](https://github.com/HumeAI/hume-audio-decisions-typescript-sdk#preparing-audio) in the SDK README.

A request takes up to 5 minutes of audio. To send a longer recording, `createChunked` cuts it into chunks of up to `maxChunkMs` and sends each chunk in its own request.

#### long-recording.ts

```typescript
import { DecisionsClient } from "@humeai/audio-decisions";

const client = new DecisionsClient();

const result = await client.decisions.createChunked({
  file: {
    path: "long_call.wav",
    contentType: "audio/wav",
  },
  request: {
    questions: {
      vocal_bursts: {
        type: "span",
        instructions: "When does the speaker laugh, sigh or make another vocal burst?",
      },
    },
  },
  maxChunkMs: 300_000,
});

for (const chunk of result.chunks) {
  if (chunk.status !== "succeeded") {
    console.log(`${chunk.startMs} to ${chunk.endMs} ms: ${chunk.status}`);
    continue;
  }
  const answer = chunk.response.answers.vocal_bursts;
  if (answer.type === "span") {
    for (const span of answer.spans) {
      const startMs = chunk.startMs + span.audioStartMs;
      const endMs = chunk.startMs + span.audioEndMs;
      console.log(
        `vocal_bursts: ${startMs} to ${endMs} ms, ${span.probability.toFixed(2)}`,
      );
    }
  }
}
```

The model answers each chunk on its own, and nothing combines the answers across chunks. A span's times count from the start of its chunk, so the script adds the chunk's `startMs` to place each span in the recording. Most failed requests stop the job, and the chunks after one are not sent; `result.resumeMs` says where to continue. See [Long recordings](https://github.com/HumeAI/hume-audio-decisions-typescript-sdk#long-recordings) in the SDK README for choosing a chunk length, where the cuts fall and resuming.

## Next steps

#### [Writing questions](/audio-decisions/docs/writing-questions)

Word instructions and criteria so questions get useful answers.

#### [Interpreting answers](/audio-decisions/docs/interpreting-answers)

Read each answer and set thresholds for acting on it.