> ## Documentation Index
> Fetch the complete documentation index at: https://supertone-b89202c8.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript SDK

> Install, authenticate, and use the Supertone TypeScript SDK — promise-based, streaming, auto-chunking, and runs in Node and Bun.

The official TypeScript SDK is published as [`@supertone/supertone`](https://www.npmjs.com/package/@supertone/supertone) on npm. Source: [supertone-inc/supertone-ts](https://github.com/supertone-inc/supertone-ts).

## At a glance

| | |
| - | - |
| **Package** | [`@supertone/supertone`](https://www.npmjs.com/package/@supertone/supertone) on npm |
| **Repo** | [supertone-inc/supertone-ts](https://github.com/supertone-inc/supertone-ts) |
| **Languages** | TypeScript 5+ / Node 18+ |
| **Module formats** | ESM + CommonJS |
| **Auth** | `new Supertone({ apiKey: ... })` |
| **Sync API** | n/a |
| **Async API** | All methods (Promises) |
| **Streaming** | `ReadableStream` reader |
| **Auto-chunk long text** | ✅ 300 chars, sequential |
| **Custom retries** | ✅ via `retryConfig` |
| **HTTP backend** | `fetch` |

## Installation

**npm**

```bash theme={"dark"}
npm add @supertone/supertone
```

**pnpm**

```bash theme={"dark"}
pnpm add @supertone/supertone
```

**bun**

```bash theme={"dark"}
bun add @supertone/supertone
```

**yarn**

Yarn requires installing the peer dependency manually:

```bash theme={"dark"}
yarn add @supertone/supertone zod
```

The package is published with both **ESM** and **CommonJS** entry points and ships with TypeScript types. Node 18+ is required (for the global `fetch` and `ReadableStream`). Works in Bun and Deno; not designed for the browser (your API key should never live client-side).

## Set your API key

```bash theme={"dark"}
export SUPERTONE_API_KEY="Kp9mZ3xQ7v..."
```

```typescript theme={"dark"}
import { Supertone } from "@supertone/supertone";

const client = new Supertone({ apiKey: process.env.SUPERTONE_API_KEY });
```

Voice IDs are not environment variables — they change per use case, so keep them as plain strings in your code (or pass them from your request payload).

## Generate speech

```typescript theme={"dark"}
import { Supertone } from "@supertone/supertone";
import * as fs from "node:fs";

const VOICE_ID = "20160a4c5ba38967330c84"; // replace with your voice ID

const client = new Supertone({ apiKey: process.env.SUPERTONE_API_KEY });

const response = await client.textToSpeech.createSpeech({
  voiceId: VOICE_ID,
  apiConvertTextToSpeechUsingCharacterRequest: {
    text: "Hello from the TypeScript SDK.",
    language: "en",
    model: "sona_speech_1",
    outputFormat: "wav",
  },
});

if (response.result instanceof Uint8Array) {
  fs.writeFileSync("speech.wav", response.result);
} else if (response.result && "getReader" in response.result) {
  const reader = (response.result as ReadableStream<Uint8Array>).getReader();
  const chunks: Uint8Array[] = [];
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    if (value) chunks.push(value);
  }
  fs.writeFileSync("speech.wav", Buffer.concat(chunks));
}
```

Every method is async and returns a Promise. There is no sync alternative.

## Stream speech

```typescript theme={"dark"}
import { Supertone } from "@supertone/supertone";
import * as fs from "node:fs";

const VOICE_ID = "20160a4c5ba38967330c84"; // replace with your voice ID

const client = new Supertone({ apiKey: process.env.SUPERTONE_API_KEY });

const response = await client.textToSpeech.streamSpeech({
  voiceId: VOICE_ID,
  apiConvertTextToSpeechUsingCharacterRequest: {
    text: "This response is streamed chunk by chunk.",
    language: "en",
    model: "sona_speech_1",
  },
});

if (response.result && typeof response.result === "object" && "getReader" in response.result) {
  const reader = (response.result as ReadableStream<Uint8Array>).getReader();
  const chunks: Uint8Array[] = [];
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    if (value) chunks.push(value);
  }
  fs.writeFileSync("streamed.wav", Buffer.concat(chunks));
}
```

Streaming is currently supported on `sona_speech_1` only.

## Long text auto-chunking

`createSpeech` and `streamSpeech` both auto-chunk text longer than 300 characters. Pass the text as-is — the SDK splits it, generates each segment, and merges (or streams) the result.

```typescript theme={"dark"}
const response = await client.textToSpeech.createSpeech(
  {
    voiceId: VOICE_ID,
    apiConvertTextToSpeechUsingCharacterRequest: { text: longText, language: "en" },
  },
  {
    maxTextLength: 300, // optional override (default 300)
  },
);
```

`predictDuration` does **not** auto-chunk — its 300-character limit is enforced.

See [Long text](/en/docs/text-to-speech/long-text) for details.

## Common operations

```typescript theme={"dark"}
// List voices with pagination
const list = await client.voices.listVoices({ pageSize: 20 });

// Search voices
const search = await client.voices.searchVoices({
  language: "ko,en",
  style: "happy",
});

// Get a single voice
const voice = await client.voices.getVoice({ voiceId: VOICE_ID });

// Predict duration (no credits deducted)
const { duration } = await client.textToSpeech.predictDuration({
  voiceId: VOICE_ID,
  predictTTSDurationRequest: {
    text: "How long will this be?",
    language: "en",
  },
});

// Get credit balance
const balance = await client.usage.getCreditBalance();
```

## Type-safe enums (optional)

For type safety, the SDK exposes enum constants in `@supertone/supertone/models`:

```typescript theme={"dark"}
import * as models from "@supertone/supertone/models";

models.APIConvertTextToSpeechUsingCharacterRequestLanguage.En;     // "en"
models.APIConvertTextToSpeechUsingCharacterRequestModel.SonaSpeech1; // "sona_speech_1"
```

Plain string literals (`"en"`, `"sona_speech_1"`) work too — use whichever style you prefer.

## Error handling

Errors live in `@supertone/supertone/models/errors` and all extend `SupertoneError`:

```typescript theme={"dark"}
import { Supertone } from "@supertone/supertone";
import * as errors from "@supertone/supertone/models/errors";

const client = new Supertone({ apiKey: process.env.SUPERTONE_API_KEY });

try {
  const response = await client.textToSpeech.createSpeech({ /* ... */ });
} catch (err) {
  if (err instanceof errors.TooManyRequestsErrorResponse) {
    console.log("Rate limited:", err.message);
  } else if (err instanceof errors.UnauthorizedErrorResponse) {
    console.log("Auth failed:", err.message);
  } else if (err instanceof errors.PaymentRequiredErrorResponse) {
    console.log("Out of credits:", err.message);
  } else if (err instanceof errors.SupertoneError) {
    console.log(`HTTP ${err.statusCode}: ${err.message}`);
  } else {
    throw err;
  }
}
```

| Error class | HTTP status |
| - | - |
| `BadRequestErrorResponse` | 400 |
| `UnauthorizedErrorResponse` | 401 |
| `PaymentRequiredErrorResponse` | 402 |
| `ForbiddenErrorResponse` | 403 |
| `NotFoundErrorResponse` | 404 |
| `RequestTimeoutErrorResponse` | 408 |
| `PayloadTooLargeErrorResponse` | 413 |
| `UnsupportedMediaTypeErrorResponse` | 415 |
| `TooManyRequestsErrorResponse` | 429 |
| `InternalServerErrorResponse` | 500 |

Network-layer errors (`ConnectionError`, `RequestTimeoutError`, `RequestAbortedError`, etc.) extend `HTTPClientError`, not `SupertoneError`.

## Configuration

```typescript theme={"dark"}
const client = new Supertone({
  apiKey: process.env.SUPERTONE_API_KEY,
  timeoutMs: 30_000,
  retryConfig: {
    strategy: "backoff",
    backoff: { initialInterval: 500, maxInterval: 60_000, exponent: 1.5, maxElapsedTime: 3_600_000 },
    retryConnectionErrors: true,
  },
});
```

## Related

> **[Python SDK](/en/docs/developer-tools/python)**
>
> The equivalent SDK for Python.

> **[Examples](/en/docs/examples/llm-streaming-tts)**
>
> Recipes for common workflows.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.