# 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 `images` 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 `error.code`, when there is one                                 |
| `message` | The body's `error.message`, or a generic fallback                          |
| `issues`  | One `{ path, message }` per invalid field, on a `validation_error`         |
| `taskId`  | The task the create made before it failed, on `public_access_token_failed` |

`message` says what to change, so it is safe to show as it is. When a field
failed the schema, `issues` has the same problems one by one, with `path`
pointing into the request body:

```ts
try {
  await mynth.image.generate({
    model: "black-forest-labs/flux.2-pro",
    prompt: "A lighthouse at dusk",
    count: 50,
  });
} catch (error) {
  if (error instanceof MynthAPIError && error.code === "validation_error") {
    for (const issue of error.issues ?? []) {
      console.error(issue.path.join("."), issue.message);
      // count count must be at most 20 (was 50)
    }
  }
  throw error;
}
```

A validation error that is not about one field, such as a size the model
cannot produce, has no `issues`. See
[validation errors](https://mynth.io/docs/api-reference/errors.md#validation-errors).

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
`failures` for each `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.
