# Quickstart

Make a key and a worker, give the worker a task, and read the result, in about five minutes.

This guide gives a worker its first task from your own code. You make a key, make a worker, give it a task, follow it until the worker stops, and read what it wrote.

Each step shows curl, TypeScript and Python. Pick one and the page keeps it picked. The TypeScript and Python steps build one small program, so add each step's code to the same file.

## Before you start

- A Little Worker account. Sign up free at [thelittleworker.com](https://thelittleworker.com).
- For curl: curl, and jq to read JSON in step 5.
- For TypeScript: Node 22.18 or later, which runs a `.ts` file as it is. Save the program as `quickstart.ts` and run `node quickstart.ts`.
- For Python: Python 3 and the requests library (`pip install requests`). Save the program as `quickstart.py` and run `python3 quickstart.py`.

> [!NOTE]
> There is no Little Worker SDK yet, so the examples call the API over plain HTTPS. They read your key from the `LITTLE_WORKER_API_KEY` environment variable.

## 1. Make a key

Sign in at [app.thelittleworker.com](https://app.thelittleworker.com), open **Settings**, then **API Keys**, and choose **Create Key**. Name it after your program, leave **Can allow actions** off, and choose **Create**. Copy the key: it is shown once.

Keep it in your shell, where the examples read it:

```bash
export LITTLE_WORKER_API_KEY="lw_key_..."
```

A key acts as you, in your workspace, so anyone who has it can give your workers tasks. Keep it out of your code, and remove it in Settings when a program no longer needs it.

## 2. Make a worker

A worker is who does the work. Give it a `name` and, in `system`, how it should work, in plain words. A worker you already have works too: `GET /workers` lists them.

```bash title="curl"
curl https://core.thelittleworker.com/api/v1/workers \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19" \
  -H "content-type: application/json" \
  -d '{
    "name": "Copywriter",
    "system": "You write short, clear copy for a small shop. Answer with a numbered list."
  }'
```

```ts title="TypeScript"
const api = "https://core.thelittleworker.com/api/v1"
const headers = {
  "x-api-key": process.env.LITTLE_WORKER_API_KEY ?? "",
  "Little-Worker-Version": "2026-10-19",
  "content-type": "application/json",
}

const worker = await fetch(`${api}/workers`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    name: "Copywriter",
    system:
      "You write short, clear copy for a small shop. Answer with a numbered list.",
  }),
}).then((response) => response.json())
console.log(worker.id)
```

```python title="Python"
import os
import time

import requests

api = "https://core.thelittleworker.com/api/v1"
headers = {
    "x-api-key": os.environ["LITTLE_WORKER_API_KEY"],
    "Little-Worker-Version": "2026-10-19",
}

worker = requests.post(
    f"{api}/workers",
    headers=headers,
    json={
        "name": "Copywriter",
        "system": "You write short, clear copy for a small shop. Answer with a numbered list.",
    },
).json()
print(worker["id"])
```

The answer is the new worker. Its `id` is how every other call names it. A worker has more fields than this, such as its tools and its model; shortened, the answer is:

```json
{
  "type": "worker",
  "id": "worker_bf5ff997fca044b38dc1cc9ea5b60100",
  "version": 1,
  "name": "Copywriter",
  "description": null,
  "system": "You write short, clear copy for a small shop. Answer with a numbered list.",
  "quality": "standard",
  "model_source": "quality",
  "status": "active",
  "archived_at": null,
  "created_at": "2026-10-25T22:06:53.046Z",
  "updated_at": "2026-10-25T22:06:53.046Z"
}
```

Left without a `model`, a worker uses the model its Quality picks. With curl, keep the id for the next steps:

```bash
export WORKER_ID="worker_bf5ff997fca044b38dc1cc9ea5b60100"
```

## 3. Give it a task

A task is one piece of work by one worker. Say what you want in a `user.message`, the way you would say it to a person: what to do, and what done looks like. Sent in `initial_events`, the message starts the work as soon as the task is made.

```bash title="curl"
curl https://core.thelittleworker.com/api/v1/tasks \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19" \
  -H "content-type: application/json" \
  -d '{
    "worker": "'"$WORKER_ID"'",
    "title": "Subject lines",
    "initial_events": [
      {
        "type": "user.message",
        "content": [{ "type": "text", "text": "Write three subject lines for an email that says the shop now opens at 7 am." }]
      }
    ]
  }'
```

```ts title="TypeScript"
const task = await fetch(`${api}/tasks`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    worker: worker.id,
    title: "Subject lines",
    initial_events: [
      {
        type: "user.message",
        content: [
          {
            type: "text",
            text: "Write three subject lines for an email that says the shop now opens at 7 am.",
          },
        ],
      },
    ],
  }),
}).then((response) => response.json())
console.log(task.id, task.status)
```

```python title="Python"
task = requests.post(
    f"{api}/tasks",
    headers=headers,
    json={
        "worker": worker["id"],
        "title": "Subject lines",
        "initial_events": [
            {
                "type": "user.message",
                "content": [
                    {
                        "type": "text",
                        "text": "Write three subject lines for an email that says the shop now opens at 7 am.",
                    }
                ],
            }
        ],
    },
).json()
print(task["id"], task["status"])
```

The answer is `200` and the task. Its `status` is `running`. `state` says the same in the words the app shows: `not_started` for a moment, until the worker picks it up, and then `working`. Shortened, it is:

```json
{
  "type": "task",
  "id": "task_01M5TTRJKWYAK3XHFX432K5MBV",
  "worker": {
    "type": "worker",
    "id": "worker_bf5ff997fca044b38dc1cc9ea5b60100",
    "version": 1,
    "name": "Copywriter"
  },
  "chat_id": "chat_d0605ba7bcba441384d781ea56d8bf04",
  "created_by": "person",
  "title": "Subject lines",
  "ask": "Write three subject lines for an email that says the shop now opens at 7 am.",
  "status": "running",
  "state": { "status": "not_started", "needs": null, "waiting_reason": null },
  "stop_reason": null,
  "result_summary": null,
  "sent_back_count": 0,
  "budget": {
    "type": "limit",
    "max_list_cost": { "amount": "500", "currency": "USD" }
  },
  "created_at": "2026-10-25T22:06:59.561Z",
  "updated_at": "2026-10-25T22:06:59.561Z"
}
```

Every task has a budget, the most its work may cost at list price. Left out, it is $5.00. A task made without `initial_events` waits as `idle`, and nothing runs until you send it a message (step 5 shows how).

The task is also in the app, in a new chat with the worker, so a person can follow it there too. With curl, keep its id:

```bash
export TASK_ID="task_01M5TTRJKWYAK3XHFX432K5MBV"
```

## 4. Follow the work

The task is `running` until the worker stops, and then it is `idle`. The plainest way to know when it stops is to read the task every few seconds:

```bash title="curl"
curl https://core.thelittleworker.com/api/v1/tasks/$TASK_ID \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19"
```

```ts title="TypeScript"
let current = task
while (current.status === "running" || current.status === "rescheduling") {
  await new Promise((resolve) => setTimeout(resolve, 2000))
  current = await fetch(`${api}/tasks/${task.id}`, { headers }).then(
    (response) => response.json(),
  )
}
console.log(current.state.status, current.stop_reason?.type)
```

```python title="Python"
current = task
while current["status"] in ("running", "rescheduling"):
    time.sleep(2)
    current = requests.get(f"{api}/tasks/{task['id']}", headers=headers).json()
print(current["state"]["status"], (current["stop_reason"] or {}).get("type"))
```

A task that is `rescheduling` is held by a plan or spending cap, and goes on by itself when there is room.

To see each step as it happens, follow the task's events instead. The answer stays open and sends each event as it comes, as Server-Sent Events. `after=0` starts from the first event; without it, the stream sends only what happens after it opens.

```bash
curl -N "https://core.thelittleworker.com/api/v1/tasks/$TASK_ID/events/stream?after=0" \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19"
```

```text
id: 1
event: user.message
data: {"type":"user.message","sequence":"1","content":[{"type":"text","text":"Write three subject lines..."}],...}

id: 2
event: task.status_running
data: {"type":"task.status_running","sequence":"2",...}

id: 10
event: worker.message
data: {"type":"worker.message","sequence":"10","content":[{"type":"text","text":"1. Open at 7 am, starting Monday..."}],"sources":[],...}

id: 11
event: task.status_idle
data: {"type":"task.status_idle","sequence":"11","run_id":"run_488241351b7a4b528b8bf6df21c7b7a8","stop_reason":{"type":"end_turn"},"stop_details":null,...}
```

Other events come in between, such as `worker.tool_use` when the worker uses a tool and `task.usage` right before each stop. `task.status_idle` means the worker stopped, and its `stop_reason` says why: `end_turn` when its turn is over. The stream stays open after it, so stop curl with Ctrl+C. If the connection drops, connect again with the last `id` you saw in a `Last-Event-ID` header, and the stream sends only what came after it.

## 5. Read the result

When `state.status` is `done`, `result_summary` holds one line about the result. The worker's full answer is its last `worker.message` event, as `content` blocks:

```bash title="curl"
curl -s -g "https://core.thelittleworker.com/api/v1/tasks/$TASK_ID/events?types[]=worker.message&order=desc&limit=1" \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19" \
  | jq -r '.data[0].content[] | select(.type == "text") | .text'
```

```ts title="TypeScript"
const answers = await fetch(
  `${api}/tasks/${task.id}/events?types[]=worker.message&order=desc&limit=1`,
  { headers },
).then((response) => response.json())
const blocks: { type: string; text?: string }[] =
  answers.data[0]?.content ?? []
console.log(current.result_summary)
console.log(
  blocks
    .filter((block) => block.type === "text")
    .map((block) => block.text)
    .join("\n"),
)
```

```python title="Python"
answers = requests.get(
    f"{api}/tasks/{task['id']}/events",
    headers=headers,
    params={"types[]": "worker.message", "order": "desc", "limit": 1},
).json()
blocks = answers["data"][0]["content"] if answers["data"] else []
print(current["result_summary"])
print("\n".join(block["text"] for block in blocks if block["type"] == "text"))
```

`types[]` keeps only the events of that type, and `order=desc` lists the newest first. Without them, events come oldest first, up to 100 a page: follow `next_page` until it is `null`.

If the task is `needs_you` instead, `state.needs` says what it waits for:

| `state.needs` | What to do |
| --- | --- |
| `allow` | The worker waits to take an action that asks first, and `stop_reason` is `requires_action`. Allow or deny it in the app. A key made with **Can allow actions** may answer too, with a `user.approval` event. |
| `answer` | Read the worker's question, its last `worker.message`, and send your answer as a `user.message` event. |
| `help` | The work failed, or it stopped at a limit, such as the task's budget. `stop_reason` says which. Read its last events, then send a message or give a new task. |
| `look` | The worker's own check still found the work short. Accept it as it is with a `user.accept` event, or send it back. |

A message goes to the task's events. A message to a task that is `done` opens it again, the same as sending it back.

```bash
curl https://core.thelittleworker.com/api/v1/tasks/$TASK_ID/events \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19" \
  -H "content-type: application/json" \
  -d '{ "events": [{ "type": "user.message", "content": [{ "type": "text", "text": "The shop is called Field Day." }] }] }'
```

## 6. Send it back if it is not right

Finished work is done by itself: there is nothing to accept. If the result is not what you wanted, send it back with what to change, and the worker works on it again:

```bash title="curl"
curl https://core.thelittleworker.com/api/v1/tasks/$TASK_ID/events \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19" \
  -H "content-type: application/json" \
  -d '{ "events": [{ "type": "user.send_back", "reason": "Make each one shorter than 40 characters." }] }'
```

```ts title="TypeScript"
const sent = await fetch(`${api}/tasks/${task.id}/events`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    events: [
      {
        type: "user.send_back",
        reason: "Make each one shorter than 40 characters.",
      },
    ],
  }),
}).then((response) => response.json())
console.log(sent.data[0].task.state.status)
```

```python title="Python"
sent = requests.post(
    f"{api}/tasks/{task['id']}/events",
    headers=headers,
    json={
        "events": [
            {
                "type": "user.send_back",
                "reason": "Make each one shorter than 40 characters.",
            }
        ]
    },
).json()
print(sent["data"][0]["task"]["state"]["status"])
```

The answer is `200`, with one entry in `data` for the event you sent. Its `task` is `running` again, with `state.status` `working` and `sent_back_count` 1, and its `run_id` names the new stretch of work. Follow it as in step 4.

## Next steps

- [Tasks reference](/api/tasks): every field of a task, and the routes to list, read, change and take back tasks.
- [Overview](/get-started/overview): the main ideas, headers, errors and limits in one place.
