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

# Async tasks and polling

> Every agent run is asynchronous. Here's how to wait for results without hammering the API.

When you call `POST /v1/agents/{agent_id}/run`, the response returns immediately with a
`task_id` and `status: "running"`. The agent is still working in the background. To get
the final result you poll one of the task-read endpoints until `status` is terminal.

## Pick the right endpoint to poll

| Endpoint                          | Use when                                                                                        |
| --------------------------------- | ----------------------------------------------------------------------------------------------- |
| `GET /v1/tasks/{task_id}`         | **Polling.** Returns status + a few timestamps. Cheapest call.                                  |
| `GET /v1/tasks/{task_id}/summary` | The task is `completed` and you only want the final answer.                                     |
| `GET /v1/tasks/{task_id}/result`  | Debugging or audit. Returns the full reasoning feed (every tool call and intermediate message). |

A typical production loop looks like: poll `/v1/tasks/{task_id}` on a backoff schedule;
once `status` hits a terminal value, call `/summary` once.

## Terminal vs non-terminal statuses

| Status              | Terminal? | What to do                                                                                                                                   |
| ------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `pending`           | No        | Keep polling; the orchestrator hasn't picked it up yet.                                                                                      |
| `queued`            | No        | Keep polling; the agent is at its concurrent-task limit.                                                                                     |
| `running`           | No        | Keep polling.                                                                                                                                |
| `waiting_for_input` | No        | The agent called `ask_user`. Reply via `POST /v1/tasks/{task_id}/message`, then resume polling. See [Interactive tasks](/docs/interactive-tasks). |
| `awaiting_review`   | No        | A human in the dashboard needs to approve the next step.                                                                                     |
| `completed`         | Yes       | Done. Fetch `/summary`.                                                                                                                      |
| `failed`            | Yes       | Errored. Fetch `/result` for the reasoning trail.                                                                                            |
| `stopped`           | Yes       | Cancelled (by you or by the system).                                                                                                         |

## A real polling loop

<CodeGroup>
  ```python Python theme={null}
  import os, time, requests

  BASE = "https://agents.nanonets.com"
  HEADERS = {"Authorization": f"Bearer {os.environ['NANONETS_API_KEY']}"}
  TERMINAL = {"completed", "failed", "stopped"}

  def wait_for_task(task_id: str, timeout_s: int = 600) -> dict:
      deadline = time.time() + timeout_s
      delay = 1.0
      while time.time() < deadline:
          r = requests.get(f"{BASE}/api/v1/tasks/{task_id}", headers=HEADERS, timeout=10)
          r.raise_for_status()
          body = r.json()
          if body["status"] in TERMINAL:
              return body
          if body["status"] == "waiting_for_input":
              raise RuntimeError(f"Task {task_id} needs a reply; see /interactive-tasks")
          time.sleep(delay)
          delay = min(delay * 1.5, 10.0)  # gentle exponential backoff, cap at 10s
      raise TimeoutError(f"Task {task_id} did not finish within {timeout_s}s")

  final = wait_for_task("f1c2a3b4-...")
  if final["status"] == "completed":
      summary = requests.get(
          f"{BASE}/api/v1/tasks/{final['task_id']}/summary", headers=HEADERS
      ).json()
      print(summary)
  ```

  ```javascript Node.js theme={null}
  const BASE = "https://agents.nanonets.com";
  const headers = { Authorization: `Bearer ${process.env.NANONETS_API_KEY}` };
  const TERMINAL = new Set(["completed", "failed", "stopped"]);

  async function waitForTask(taskId, timeoutMs = 600_000) {
    const deadline = Date.now() + timeoutMs;
    let delay = 1000;
    while (Date.now() < deadline) {
      const res = await fetch(`${BASE}/api/v1/tasks/${taskId}`, { headers });
      if (!res.ok) throw new Error(`status ${res.status}`);
      const body = await res.json();
      if (TERMINAL.has(body.status)) return body;
      if (body.status === "waiting_for_input") {
        throw new Error(`Task ${taskId} needs a reply; see /interactive-tasks`);
      }
      await new Promise((r) => setTimeout(r, delay));
      delay = Math.min(delay * 1.5, 10_000);
    }
    throw new Error(`Task ${taskId} timed out`);
  }
  ```

  ```bash cURL theme={null}
  TASK_ID="f1c2a3b4-..."
  while :; do
    STATUS=$(curl -s "https://agents.nanonets.com/api/v1/tasks/$TASK_ID" \
      -H "Authorization: Bearer $NANONETS_API_KEY" | jq -r .status)
    echo "$STATUS"
    case "$STATUS" in
      completed|failed|stopped) break ;;
      waiting_for_input) echo "Needs a reply"; exit 1 ;;
    esac
    sleep 2
  done
  curl -s "https://agents.nanonets.com/api/v1/tasks/$TASK_ID/summary" \
    -H "Authorization: Bearer $NANONETS_API_KEY"
  ```
</CodeGroup>

<Tip>
  Start your polling interval at 1–2 seconds and back off to \~10 seconds. The status
  endpoint is cheap, but tight loops still burn through rate limit budget for no benefit.
  Most tasks take seconds to minutes.
</Tip>

## Cancelling

If you no longer want the result, send `POST /v1/tasks/{task_id}/cancel`. The task moves
to `stopped`. The step currently executing on the worker is **not** interrupted; it
finishes naturally, but no further steps are scheduled. Cancel is idempotent: calling on
an already-terminal task returns the current state with `200`.

## Tasks that legitimately take a long time

Some agents do work that takes minutes (multi-step research, large document extraction).
A few rules of thumb:

* Keep your client-side timeout generous (≥10 minutes) for those agents.
* Don't poll faster than every couple of seconds; you won't get the answer any sooner.
* For very long jobs, persist the `task_id` and poll from a background worker, not a
  user-facing HTTP request.
