# Tasks

> Every generation and analysis call creates a task. Its three statuses, why a completed task can hold failed items, the ways to collect a result, and how long files last.

Every generation and analysis request creates a task. Mynth validates the
request, holds the estimated cost on your balance, and returns a task id within
milliseconds. The work runs after that, and you collect the result separately.

The SDK's `generate()`, `rate()`, and similar methods look synchronous because
they create the task and poll it for you. The HTTP API has no synchronous
generate call.

## What creates a task

| Request                         | Task `type`               | `result` holds               |
| ------------------------------- | ------------------------- | ---------------------------- |
| `POST /image/generate`          | `image.generate`          | generated images             |
| `POST /image/remove-background` | `image.remove_background` | one cutout image             |
| `POST /image/rate`              | `image.rate`              | a content rating level       |
| `POST /image/alt`               | `image.alt`               | alt text                     |
| `POST /image/review`            | `image.review`            | a quality score and findings |
| `POST /video/generate`          | `video.generate`          | a generated video            |

Each answers `201` with the task id and the held estimate:

```json
{ "data": { "taskId": "tsk_01KE7XWWEQ4MCGWKBQKJ1G47RP", "estimatedCost": "0.03" } }
```

Task ids are `tsk_` followed by a ULID, so they sort by creation time.

## Statuses

There are three. `pending` covers both queued and running, so there is no
`running` status to wait for.

```text
201 { taskId, estimatedCost }      estimate held on your balance
  │
  ▼
pending ──┬──▶ completed   result set, successful outputs charged
          └──▶ failed      errors set, hold released, nothing charged
```

A task settles once. It never leaves `completed` or `failed`, and Mynth never
reruns a settled task. To try again, send a new request, which creates a new
task and a new hold.

## Completed does not mean every item worked

`image.generate` with `count: 4` runs four generations independently. When one
fails, the task still completes, and each entry in `result.images` carries its
own `status`:

```json
{
  "status": "completed",
  "result": {
    "model": "black-forest-labs/flux.2-pro",
    "images": [
      {
        "status": "success",
        "id": "img_V1StGXR8Z5jdHi6BmyT0sC1pQ2rN4wLk",
        "url": "https://cdn.mynth.io/images/img_V1StGXR8Z5jdHi6BmyT0sC1pQ2rN4wLk.webp"
      },
      {
        "status": "failed",
        "error": {
          "code": "RESTRICTED_CONTENT",
          "message": "The request was blocked by content moderation."
        }
      }
    ]
  }
}
```

`video.generate` works the same way under `result.videos`. A provider refusal
(`RESTRICTED_CONTENT`) or a model with no capacity (`PROVIDERS_BUSY`) fails the
item, not the task, so a completed generation task can hold no usable output
at all.

Always check the task status, then every item. Only failed items are free:
you pay for the images that succeeded.

The other task types (`image.remove_background`, `image.rate`, `image.alt`,
`image.review`) have no items. When their work fails, the task is `failed`.

> **Warning**
>
> In the SDK, `urls` and `getImages()` skip failed images without saying so. If a partial result is
> not acceptable, compare `getImages().length` with the `count` you sent.

## Collecting the result

Pick one of three:

| Approach                              | Use it when                                                     |
| ------------------------------------- | --------------------------------------------------------------- |
| Wait in the SDK or CLI                | The code can hold a connection: a script, a worker, a job queue |
| [Poll](https://mynth.io/docs/guides/poll-for-results.md) | You want control over the loop, or a browser does the waiting   |
| [Webhook](https://mynth.io/docs/concepts/webhooks.md)    | Nothing can wait: serverless handlers, long video renders       |

A [destination](https://mynth.io/docs/concepts/destinations.md) changes where the file is stored,
not how you learn the task finished, so combine it with one of the three.

The SDK polls on a fixed schedule and gives up after 30 minutes for images and
an hour for video. [Tasks and polling](https://mynth.io/docs/sdks/typescript/tasks.md) has the
exact intervals. `GET /tasks/{id}/status` and `GET /tasks/{id}/result` are
served from a cache, so point a polling loop at them rather than at
`GET /tasks/{id}`.

## Reading a task

| Call                     | Returns                                                          |
| ------------------------ | ---------------------------------------------------------------- |
| `GET /tasks/{id}/status` | `status` only                                                    |
| `GET /tasks/{id}/result` | `id`, `type`, `status`, `result`                                 |
| `GET /tasks/{id}`        | The full record: `request`, `result`, `errors`, `cost`, and more |
| `GET /tasks`             | Recent tasks, newest first, without `request` or `result`        |

`GET /tasks` with an API key lists only the tasks that key created. Any key on
your account with the `generate` scope can read any of your tasks by id.
[The task object](https://mynth.io/docs/api-reference/task-object.md) documents every field.

## Retries and timeouts

Mynth retries before a failure reaches you:

| Work      | Attempts | Time budget | Each attempt           |
| --------- | -------- | ----------- | ---------------------- |
| One image | 4        | 35 minutes  | picks a provider again |
| One video | 3        | 32 minutes  | up to 15 minutes       |

A failing provider therefore fails over rather than failing the image.
`RESTRICTED_CONTENT`, `INVALID_PROMPT`, `INVALID_INPUT`, and
`CAPABILITY_NOT_SUPPORTED` are not retried, because another attempt would fail
the same way. [Errors](https://mynth.io/docs/api-reference/errors.md#task-failures) lists every
code and whether retrying helps.

A task still `pending` 24 hours after its last update is failed with
`TASK_EXPIRED` by a daily sweep, and its hold is released.
[Pricing](https://mynth.io/docs/pricing.md#holds-and-charges) has the details.

## File lifetimes

| File                                   | Served for |
| -------------------------------------- | ---------- |
| A generated image or video             | 7 days     |
| An image uploaded with `/image/upload` | 1 day      |

Mynth hands you a URL because that is the practical way to use the file. The
id in it is long and random, but Mynth is not a file host. To keep a file,
name a [destination](https://mynth.io/docs/concepts/destinations.md) so Mynth writes it to your
own storage, or copy it when the webhook arrives or polling shows `completed`.

When copying on a machine with little memory, stream the download into the
upload instead of buffering the whole file.

## Next steps

- [Poll for results](https://mynth.io/docs/guides/poll-for-results.md): a polling loop, from a server or a browser.
- [Webhooks](https://mynth.io/docs/concepts/webhooks.md): get called when the task settles.
- [The task object](https://mynth.io/docs/api-reference/task-object.md): every field of the record.
- [Pricing](https://mynth.io/docs/pricing.md): the hold, the charge, and `cost`.
