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#

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.
  • 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.

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:

// 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 });
// 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 or server-side polling for those.
  • Send "access": { "pat": { "enabled": false } } on the create request to skip issuing a token.

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#

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#