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

# Quickstart

> Get a task delivered to your dashboard in five minutes.

By the end of this page you'll have:

* A local awaithumans server running at `http://localhost:3001`
* An operator account
* A Python script that creates a task and waits for your decision
* That decision flowing back into the script as a typed value

## 1. Install

<CodeGroup>
  ```bash Python theme={"system"}
  pip install "awaithumans[server]"
  ```

  ```bash TypeScript theme={"system"}
  npm install awaithumans
  ```
</CodeGroup>

The `[server]` extra installs the FastAPI server, dashboard, and CLI alongside the SDK. The base `awaithumans` package is just the lightweight HTTP client.

## 2. Start the server

```bash theme={"system"}
awaithumans dev
```

You'll see something like:

```
✓ Server running at http://localhost:3001
✓ Database: ./.awaithumans/dev.db
✓ Open the dashboard at:

    http://localhost:3001/setup?token=8aF...

  (one-shot setup link — paste it into your browser)
```

Open the printed `/setup?token=...` URL, paste a password, and create your operator account. You're now logged into the dashboard at `http://localhost:3001`.

<Note>
  `awaithumans dev` auto-generates the encryption key, admin token, and SQLite database in `./.awaithumans/`. The CLI also writes `~/.awaithumans-dev.json` so the SDK can auto-discover the server URL and bearer token — you don't have to set env vars.
</Note>

## 3. Run your first task

<CodeGroup>
  ```python Python theme={"system"}
  # refund.py
  from pydantic import BaseModel
  from awaithumans import await_human_sync


  class RefundPayload(BaseModel):
      amount_usd: int
      customer_id: str
      reason: str


  class RefundDecision(BaseModel):
      approved: bool
      notes: str | None = None


  def main():
      decision = await_human_sync(
          task="Approve $250 refund?",
          payload_schema=RefundPayload,
          payload=RefundPayload(
              amount_usd=250,
              customer_id="cus_demo",
              reason="Duplicate charge",
          ),
          response_schema=RefundDecision,
          timeout_seconds=900,
          # In real code use a stable per-event ID — e.g. `f"refund:{order.id}"`.
          # Same key = same task, even if your script restarts mid-wait.
          idempotency_key="refund:order_demo",
      )

      if decision.approved:
          print(f"Refund approved: {decision.notes}")
      else:
          print(f"Refund rejected: {decision.notes}")


  if __name__ == "__main__":
      main()
  ```

  ```typescript TypeScript theme={"system"}
  // refund.ts
  import { awaitHuman } from "awaithumans";
  import { z } from "zod";

  const RefundPayload = z.object({
    amount_usd: z.number(),
    customer_id: z.string(),
    reason: z.string(),
  });

  const RefundDecision = z.object({
    approved: z.boolean(),
    notes: z.string().optional(),
  });

  async function main() {
    const decision = await awaitHuman({
      task: "Approve $250 refund?",
      payloadSchema: RefundPayload,
      payload: {
        amount_usd: 250,
        customer_id: "cus_demo",
        reason: "Duplicate charge",
      },
      responseSchema: RefundDecision,
      timeoutMs: 15 * 60 * 1000,
      // In real code use a stable per-event ID — e.g. `refund:${order.id}`.
      // Same key = same task, even if your script restarts mid-wait.
      idempotencyKey: "refund:order_demo",
    });

    if (decision.approved) {
      console.log(`Refund approved: ${decision.notes ?? ""}`);
    } else {
      console.log(`Refund rejected: ${decision.notes ?? ""}`);
    }
  }

  main();
  ```
</CodeGroup>

Run it:

<CodeGroup>
  ```bash Python theme={"system"}
  python refund.py
  ```

  ```bash TypeScript theme={"system"}
  npx tsx refund.ts
  ```
</CodeGroup>

The script prints:

```
✓ Task created: tsk_4f8a2c1e9b...
  Review at: http://localhost:3001/task?id=tsk_4f8a2c1e9b...
  Waiting for human (timeout: 900s). Ctrl-C to abort.
```

Then it blocks. The agent is now awaiting a human.

## 4. Approve it

Click the "Review at" URL (or refresh `http://localhost:3001`). You'll see a task on your queue. Click in, fill the form, hit Submit.

Within a second your script prints:

```
Refund approved: Duplicate charge confirmed by support team.
```

That's the whole loop. The agent's `await_human()` call blocked for as long as the human took to decide, then resumed with the typed `RefundDecision` instance.

## What just happened

```
┌─────────────────┐  POST /api/tasks   ┌──────────────────────┐
│ refund.py       │ ──────────────────► awaithumans server     │
│                 │                    │                      │
│ awaithumans.    │  long-poll status  │  notify human via:   │
│   await_human() │ ◄──────────────────┤  - dashboard         │
│   blocks        │                    │  - slack (optional)  │
│                 │                    │  - email (optional)  │
│                 │  human submits     │                      │
│ ◄── response ───┤                    │  store response      │
│                 │                    │                      │
│ continues       │                    │                      │
└─────────────────┘                    └──────────────────────┘
```

The SDK long-polls until the task reaches a terminal state. For agents that can park for hours/days (Temporal, LangGraph workflows), [the durable adapters](/adapters/temporal) replace polling with workflow-engine signals so you spend zero compute while waiting.

## Add a notification channel

Right now the only place a human sees the task is the dashboard. Add Slack with one extra kwarg — the rest of the call is identical to the script above:

```python theme={"system"}
decision = await_human_sync(
    task="Approve $250 refund?",
    payload_schema=RefundPayload,
    payload=RefundPayload(
        amount_usd=250, customer_id="cus_demo", reason="Duplicate charge"
    ),
    response_schema=RefundDecision,
    timeout_seconds=900,
    notify=["slack:#approvals"],   # ← post to this channel
)
```

<Warning>
  **Set up the Slack channel first**, otherwise the `notify=` line is silently dropped and the task only appears in the dashboard — the snippet above won't post to any Slack workspace until you've configured the bot.

  You need three things on the **awaithumans server** (the process running `awaithumans dev`):

  1. A Slack app installed in your workspace with the right scopes ([app manifest in the Slack docs](/channels/slack#1-create-a-slack-app)).
  2. `AWAITHUMANS_SLACK_BOT_TOKEN` (`xoxb-...`) and `AWAITHUMANS_SLACK_SIGNING_SECRET` exported in the server's environment.
  3. The bot invited to the channel you're posting to (`/invite @Await Humans` in `#approvals`).

  Restart `awaithumans dev` after setting the env vars. Without this, the SDK call still succeeds — Slack delivery happens in a background task and only logs the failure server-side.
</Warning>

See [Channels: Slack](/channels/slack) for the full setup walkthrough (static-token mode is the fastest path; OAuth multi-workspace mode is also documented).

Email works the same way — `notify=["email:approvals@acme.com"]` — and has the same prerequisite: configure a transport (Resend or SMTP) on the server first. See [Channels: Email](/channels/email).

## Add AI verification

Have an LLM gut-check the human's decision before it lands.

<Note>
  **Bring your own key.** The verifier runs **server-side**, not in your agent process. That means your LLM provider API key must be exported on the awaithumans server's environment (the same shell that ran `awaithumans dev`), **not** the shell running your agent script. If you skip this step, the task fails with `VERIFIER_API_KEY_MISSING` at submit time.
</Note>

In the terminal that's running `awaithumans dev` (or in your `.env`), set the key for whichever provider you'll use:

<CodeGroup>
  ```bash Claude (Anthropic) theme={"system"}
  export ANTHROPIC_API_KEY=sk-ant-...
  # then restart: awaithumans dev
  ```

  ```bash OpenAI theme={"system"}
  export OPENAI_API_KEY=sk-...
  ```

  ```bash Gemini (Google) theme={"system"}
  export GEMINI_API_KEY=AIza...
  ```

  ```bash Azure OpenAI theme={"system"}
  export AZURE_OPENAI_API_KEY=...
  export AZURE_OPENAI_ENDPOINT=https://my-resource.openai.azure.com
  ```
</CodeGroup>

You also need the provider's SDK installed in the awaithumans server's Python environment:

```bash theme={"system"}
pip install "awaithumans[verifier-claude]"   # for Claude — pick the one matching your provider
```

Then attach the verifier to your `await_human` call:

```python theme={"system"}
from awaithumans.verifiers.claude import claude_verifier

decision = await_human_sync(
    task="Approve $250 refund?",
    payload_schema=RefundPayload,
    payload=RefundPayload(
        amount_usd=250, customer_id="cus_demo", reason="Duplicate charge"
    ),
    response_schema=RefundDecision,
    timeout_seconds=900,
    verifier=claude_verifier(
        instructions=(
            "Reject if the notes contradict the decision, or if "
            "the amount is over $1000 and notes are empty."
        ),
        max_attempts=3,
    ),
)
```

Your agent code doesn't carry any API key — it just declares the verifier config; the server does the LLM call.

See [Verifier](/adapters/verifier) for the full provider list, custom env var names, and NL-parsing tricks.

## Next steps

<CardGroup cols={2}>
  <Card title="Task lifecycle" icon="diagram-project" href="/concepts/task-lifecycle">
    The state machine your task moves through.
  </Card>

  <Card title="Idempotency" icon="rotate" href="/concepts/idempotency">
    How the same key recovers the same task across crashes.
  </Card>

  <Card title="Temporal" icon="repeat" href="/adapters/temporal">
    Park a workflow for hours/days while waiting. Zero compute idle.
  </Card>

  <Card title="LangGraph" icon="diagram-project" href="/adapters/langgraph">
    Interrupt-based HITL in a LangGraph node.
  </Card>
</CardGroup>
