# Poll for results

> Poll a task's status until it settles, then read the result, from a server with an API key or from a browser with the task's pat_ token.

A create call returns a task id, not the file. Poll `GET /tasks/{id}/status`
until `status` leaves `pending`, then read `GET /tasks/{id}/result`. Both are
served from a cache and are the calls meant for a loop. `GET /tasks/{id}` is
the full record and is not.

The SDK's `generate()` does this for you. Poll yourself when you want control
over the loop, or when a browser does the waiting.

## From a server

```ts
const API = "https://api.mynth.io";
const headers = { Authorization: `Bearer ${process.env.MYNTH_API_KEY}` };

async function waitForTask(taskId: string, { timeoutMs = 30 * 60_000 } = {}) {
  const deadline = Date.now() + timeoutMs;
  let delay = 2_000;

  while (Date.now() < deadline) {
    const res = await fetch(`${API}/tasks/${taskId}/status`, { headers });
    if (!res.ok && res.status < 500) throw new Error(`status ${res.status}`);

    if (res.ok) {
      const { data } = await res.json();
      if (data.status !== "pending") {
        const result = await fetch(`${API}/tasks/${taskId}/result`, { headers });
        return (await result.json()).data; // { id, type, status, result }
      }
    }

    await new Promise((resolve) => setTimeout(resolve, delay));
    delay = Math.min(delay * 1.5, 10_000);
  }

  throw new Error(`Task ${taskId} still pending after ${timeoutMs} ms`);
}
```

Start near 2 seconds and back off to about 10. Most images finish in seconds.
Video takes minutes, so give it a longer timeout. Retry `5xx` and network
errors. A `4xx` fails the same way every time.

When the loop returns:

- `status: "completed"`: check `status` on each item in `result.images` or
  `result.videos`, because a completed task can hold failed items. See
  [tasks](https://mynth.io/docs/concepts/tasks.md#completed-does-not-mean-every-item-worked).
- `status: "failed"`: `/result` does not include `errors`. Read
  `GET /tasks/{id}` for the codes.

Copy the file when you see `completed` if you need it for more than 7 days
and did not name a [destination](https://mynth.io/docs/concepts/destinations.md).

## From a browser

`POST /image/generate`, `POST /image/remove-background`, and
`POST /video/generate` return `data.access.publicAccessToken`, a `pat_` token
for that one task. It works as a bearer token on the status and result calls
for that task and nowhere else, and it expires one hour after it is issued.
Only those two paths accept cross-origin requests.

Create the task on your server and hand the browser only the id and the token:

```ts
// server
import Mynth 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",
});

return Response.json({ taskId: pending.id, token: pending.access.publicAccessToken });
```

```ts
// browser
async function pollInBrowser(taskId: string, token: string) {
  const headers = { Authorization: `Bearer ${token}` };

  for (;;) {
    const res = await fetch(`https://api.mynth.io/tasks/${taskId}/status`, { headers });
    if (res.status === 401) throw new Error("Token expired or invalid. Ask the server again.");

    const { data } = await res.json();
    if (data.status !== "pending") {
      const result = await fetch(`https://api.mynth.io/tasks/${taskId}/result`, { headers });
      return (await result.json()).data;
    }

    await new Promise((resolve) => setTimeout(resolve, 3_000));
  }
}
```

- Treat `access` as optional. If Mynth could not sign a token, the task is
  still created and `access` is missing. Fall back to polling from the server.
- An expired token answers `401 TOKEN_EXPIRED`. A long video render can
  outlive the hour, so use a [webhook](https://mynth.io/docs/concepts/webhooks.md) or server-side
  polling for those.
- Send `"access": { "pat": { "enabled": false } }` on the create request to
  skip issuing a token.

> **Warning**
>
> Never send the `mak_` key to the browser. The `pat_` token can read one task's status and result
> and do nothing else.

## From the CLI

```bash
npx @mynthio/cli task wait tsk_01KE7XWWEQ4MCGWKBQKJ1G47RP --json
```

`task wait` prints the finished task. A failed task prints too, with a
non-zero exit code: `5` for `RESTRICTED_CONTENT` and `1` for anything else.
The default timeout is 30 minutes. Change it with `--timeout <seconds>`.

## Next steps

- [Webhooks](https://mynth.io/docs/concepts/webhooks.md): get called instead of polling.
- [Tasks and polling](https://mynth.io/docs/sdks/typescript/tasks.md): how the SDK waits.
- [The task object](https://mynth.io/docs/api-reference/task-object.md#status-and-result): the status and result responses.
