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

# Browser Polling

> Build an async flow that starts a task on the server and polls safely from the browser.

This tutorial shows a production-friendly pattern for interactive apps:

1. Your server creates a Mynth task.
2. Mynth returns a task ID and a short-lived `publicAccessToken`.
3. Your browser polls task status and task results without ever seeing your API key.

## Why this pattern exists

`GET /tasks/:id/status` and `GET /tasks/:id/result` accept a Public Access Token, but task creation does not. That lets you keep generation on the server and still build responsive browser UIs.

## Step 1. Create the task on your server

```ts theme={"theme":"kanagawa-dragon"}
import Mynth from "@mynthio/sdk";

const mynth = new Mynth({
  apiKey: process.env.MYNTH_API_KEY,
});

export async function createGeneration() {
  const task = await mynth.image.generateAsync({
    prompt: "A clean landing page hero illustration for a fintech startup",
    model: "google/gemini-3.1-flash-image",
  });

  return {
    taskId: task.id,
    publicAccessToken: task.access.publicAccessToken,
  };
}
```

The `publicAccessToken` is returned by default from `generateAsync()`. You can disable it with `access: { pat: { enabled: false } }` if you only plan to poll from the server with your API key.

The token is a JWT prefixed with `pat_`, scoped to this single task, and valid for 1 hour.

<Tip>
  Keep task creation on the server. Return only the task ID and `publicAccessToken` to the browser.
</Tip>

## Step 2. Return only safe data to the browser

Your API response should include:

* `taskId`
* `publicAccessToken`

Do not return your Mynth API key.

## Step 3. Poll task status from the browser

```ts theme={"theme":"kanagawa-dragon"}
async function getTaskStatus(taskId: string, publicAccessToken: string) {
  const response = await fetch(`https://api.mynth.io/tasks/${taskId}/status`, {
    headers: {
      Authorization: `Bearer ${publicAccessToken}`,
    },
  });

  if (!response.ok) {
    throw new Error(`Status request failed: ${response.status}`);
  }

  return response.json() as Promise<{
    data: { status: "pending" | "completed" | "failed" };
  }>;
}
```

Poll until the task reaches `completed` or `failed`.

## Step 4. Fetch the images

```ts theme={"theme":"kanagawa-dragon"}
async function getTaskResults(taskId: string, publicAccessToken: string) {
  const response = await fetch(`https://api.mynth.io/tasks/${taskId}/result`, {
    headers: {
      Authorization: `Bearer ${publicAccessToken}`,
    },
  });

  if (!response.ok) {
    throw new Error(`Results request failed: ${response.status}`);
  }

  return response.json() as Promise<{
    data: {
      id: string;
      type: "image.generate";
      status: "pending" | "completed" | "failed";
      result?: {
        images: Array<
          | {
              status: "success";
              id: string;
              url: string | null;
              mynth_url: string;
              cost: string;
              size: string;
            }
          | { status: "failed"; error: { code: string; message?: string } }
        >;
      };
    };
  }>;
}
```

## Step 5. Show progress in the UI

Use these states in your interface:

* `pending`: show a loading state
* `completed`: fetch and render images
* `failed`: show retry guidance

If you need richer state for internal operators, your backend can also fetch `GET /tasks/:id` with the original API key.

## When to use webhooks instead

Polling is a strong fit for:

* interactive product flows
* browser previews
* short-lived user sessions

Webhooks are a better fit for:

* backend processing
* durable pipelines
* cross-system integrations

Read [Use webhooks](/guides/use-webhooks) when the result should trigger server-side work.
