# Errors

> MynthAPIError for a rejected request, the TaskAsync errors a wait can throw, and how to read the codes of a task that failed.

The SDK surfaces failures three ways:

| What failed                                     | What you get                                            |
| ----------------------------------------------- | ------------------------------------------------------- |
| The HTTP request was rejected                   | `MynthAPIError` is thrown                               |
| The task settled as `failed`, or the wait broke | A `TaskAsync*` error is thrown                          |
| Some items failed on a `completed` task         | Nothing is thrown. `urls` and `getImages()` are shorter |

The codes themselves are on [errors](https://mynth.io/docs/api-reference/errors.md). This page is
how the SDK reports them.

```ts
import Mynth, { MynthAPIError, TaskAsyncTaskFailedError } from "@mynthio/sdk";

const mynth = new Mynth();

const pending = await mynth.image
  .generateAsync({
    model: "black-forest-labs/flux.2-pro",
    prompt: "A lighthouse at dusk, film grain",
  })
  .catch((error: unknown) => {
    if (error instanceof MynthAPIError && error.code === "INSUFFICIENT_BALANCE") {
      // rejected at create: nothing queued, nothing held
    }
    throw error;
  });

try {
  const task = await pending.wait();
  console.log(task.urls[0]);
} catch (error) {
  if (error instanceof TaskAsyncTaskFailedError) {
    // read GET /tasks/{pending.id} for errors[].code
  }
  throw error;
}
```

Keep the id from `generateAsync()` before you wait. When `generate()` throws,
the task id is only in the error message, and the error classes have no
`taskId` property.

## `MynthAPIError`

Thrown for any non-2xx response on a create, upload, estimate, or models call.

| Property  | Holds                                       |
| --------- | ------------------------------------------- |
| `status`  | The HTTP status                             |
| `code`    | The body's `code`, when there is one        |
| `message` | The body's `message`, or a generic fallback |

A schema rejection is `status` `400` with `code` undefined and an unhelpful
`message`, because that body has an issue list instead of a `code`, and the
SDK does not keep the list. On `error.status === 400` without a code, repeat
the request with curl or `npx @mynthio/cli ... --json` to see which field
failed. See [schema rejections](https://mynth.io/docs/api-reference/errors.md#schema-rejections-use-a-different-shape).

Two failures are not `MynthAPIError`:

- A missing API key throws a plain `Error` when you first read `mynth.image` or
  `mynth.video`.
- A network failure on the create request throws whatever `fetch` throws. The
  create is not retried. There are no idempotency keys, so check
  `GET /tasks` before you resend a create that may have gone through.

## Errors while waiting

All are exported from `@mynthio/sdk`, and none carries a task error `code`.

| Class                        | Thrown when                                                                      |
| ---------------------------- | -------------------------------------------------------------------------------- |
| `TaskAsyncTaskFailedError`   | The task settled as `failed`. The message includes the task id                   |
| `TaskAsyncTimeoutError`      | The poll budget ran out: 30 minutes for images and tools, one hour for video     |
| `TaskAsyncUnauthorizedError` | Status or the task record answered `401` or `403`                                |
| `TaskAsyncFetchError`        | Status reads kept failing. `cause` is the last network error, when there was one |
| `TaskAsyncTaskFetchError`    | The task completed, but loading the record kept failing                          |

For `TaskAsyncTaskFailedError`, read `GET /tasks/{id}`. Its `errors` array is
`[{ code, message? }]`. Branch on `code`.

After `TaskAsyncTimeoutError` the task is still running on Mynth. Read it by
id later instead of creating it again.

## `RESTRICTED_CONTENT`

A provider refusal usually fails one output, not the task. `wait()` returns,
and `urls` is shorter than `count`. Read
`getImages({ includeFailed: true })` for each `error.code`. If the refusal
failed the whole task, `wait()` throws `TaskAsyncTaskFailedError`, and the
code is on `GET /tasks/{id}`. [Prompts and images](https://mynth.io/docs/concepts/prompts.md)
explains the refusal.

## Next steps

- [Tasks and polling](https://mynth.io/docs/sdks/typescript/tasks.md): the poll budget behind `TaskAsyncTimeoutError`.
- [Errors](https://mynth.io/docs/api-reference/errors.md): every HTTP and task code.
