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

# Errors and retries

> Error types, billing-safe retries, timeouts and request IDs.

## Handling errors

Every error derives from `FinesseError`. HTTP errors carry the status, the message, a
machine-readable `code` (branch on this, not on the message), how long to wait when the service
asks you to, the remaining billing balance, and the request ID.

<CodeGroup>
  ```ts TypeScript theme={null}
  import {
    APIConnectionError,
    APIStatusError,
    PaymentRequiredError,
    RateLimitError,
  } from "@flamai/finesse";

  try {
    await client.speech.create({ voice_id: voice.id, text: "Hello!" });
  } catch (err) {
    if (err instanceof PaymentRequiredError)
      console.log("Out of credits:", err.code, err.billingRemaining);
    else if (err instanceof RateLimitError) console.log("Slow down and retry.");
    else if (err instanceof APIStatusError) console.log(err.status, err.message);
    else if (err instanceof APIConnectionError) console.log("Could not reach Finesse.");
    else throw err;
  }
  ```
</CodeGroup>

| Error | Status | When |
| - | - | - |
| `BadRequestError` | 400 | Invalid input, text over 10,000 characters, voice not enrolled |
| `AuthenticationError` | 401 | Missing, unknown or revoked key; `key_expired`; `token_invalid` |
| `PaymentRequiredError` | 402 | `insufficient_credits`, `quota_exhausted` or `payment_required` |
| `PermissionDeniedError` | 403 | `insufficient_scope` (the key lacks the route's scope), `voice_not_allowed` (token), `feature_disabled`, or `plan_limit_reached` (see `.feature`) |
| `NotFoundError` | 404 | No such voice or job, or not visible to your workspace |
| `ConflictError` | 409 | A request with the same `Idempotency-Key` is still running |
| `PayloadTooLargeError` | 413 | Request body over 21 MiB |
| `UnprocessableEntityError` | 422 | `invalid_input` (the engine could not use the text), or `idempotency_key_reused` |
| `RateLimitError` | 429 | `rate_limited` / `concurrency_limited` (your plan's limits; see `retryAfter`), or `too_many_sessions` |
| `InternalServerError` | 5xx | `upstream_error` (502), `upstream_busy` / `unavailable` (503, see `retryAfter`) or `internal_error`: retry |
| `APIConnectionError` | | No response; `APITimeoutError` when it timed out |
| `StreamError` | | A speech stream failed after it started |
| `RealtimeError` | | A realtime context or session failed |
| `CloneFailedError`, `CloneTimeoutError` | | Waiting for a clone |

| Field | Meaning |
| - | - |
| `status` | HTTP status |
| `code` | The API's machine-readable error code, when it sends one |
| `retryAfter` | Seconds to wait before retrying |
| `billingRemaining` | Billing balance |
| `requestId` | Request ID, to quote to support |

The API's own error codes are listed in [Errors](/voice-api/errors).

## Retries

Failed requests are retried twice with exponential backoff, honouring `Retry-After`: connection
errors, timeouts, 408, 429 and 5xx.

Calls that create something or cost credits (`speech.create`, `voices.create`, `voices.clone`,
`voices.enroll`) send an `Idempotency-Key`, the same one on every retry, so the service runs them at
most once. A retry of a clone that already succeeded gets the first result back, and a retried
synthesis is served from the cache at no charge. If the first attempt is still running, the SDK
waits for it (up to the request timeout) without using up a retry.

`speech.stream` has no key, so it is only retried when the server certainly did no work: the
connection never opened, 429, 502 or 503.

<CodeGroup>
  ```ts TypeScript theme={null}
  const client = new Finesse({ maxRetries: 5 }); // every request
  await client.speech.create({ voice_id, text: "Hello!" }, { maxRetries: 0 }); // one request
  ```
</CodeGroup>

To choose the idempotency key yourself, pass an `Idempotency-Key` header on the call:
`{ headers: { "Idempotency-Key": "..." } }`.

## Timeouts

Requests time out after 120 seconds. Synthesis on a cold engine can take a while to start, so keep
this generous. For streams, the timeout is the longest wait between chunks, not the whole stream. A
timeout throws `APITimeoutError`.

<CodeGroup>
  ```ts TypeScript theme={null}
  const client = new Finesse({ timeout: 60_000 }); // ms, every request
  await client.voices.get(voiceId, { timeout: 10_000 }); // one request

  // Cancel a request, its retries, and any waiting:
  const controller = new AbortController();
  setTimeout(() => controller.abort(), 5_000);
  await client.speech.create({ voice_id, text: "Hello!" }, { signal: controller.signal });
  ```
</CodeGroup>

## Request IDs

Every response has an `X-Request-Id`, and errors carry it. Quote it when you contact support.

<CodeGroup>
  ```ts TypeScript theme={null}
  try {
    await client.voices.get("665f1a2b3c4d5e6f7a8b9c0d");
  } catch (err) {
    if (err instanceof NotFoundError) console.log(err.requestId); // "req_3f9c..."
  }
  ```
</CodeGroup>


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