# Routines

Make, list, read, change, pause, run and archive routines, and read each run of a routine.

A routine is work that repeats by itself. It names a worker, the events each new task starts with, and what makes it run: a schedule, another start such as an email or an app event, or only your call. Each time it runs, the worker gets a task, so you read the work with the [task routes](/api/tasks). [Routines](/concepts/routines) explains what starts one and what each run records.

Each time a routine runs, or fails to, a routine run records it: the task it made, or why it made none. A routine belongs to one worker for good, and archiving a worker archives its routines. A workspace holds up to 1,000 routines, and a worker up to 50.

> [!NOTE]
> A routine is a deployment in Claude Managed Agents, and a routine run is a deployment run. There is no way to delete a routine: archive it, and it stays readable but never runs again.

## The routine object

Every route on this page but the run routes answers a routine, or a list of them. Times are RFC 3339 in UTC, to the millisecond. The runs of a routine are described under [List routine runs](#list-routine-runs).

- `type` (string): Always `routine`.

- `id` (routine id): The routine's id: `routine_` and 32 hex digits.

- `worker` (object): The worker that gets a task each time the routine runs. A routine keeps its worker for good.

  - `type` (string): Always `worker`.

  - `id` (worker id): The worker.

  - `version` (integer): The worker version each task runs: the version the routine pins, or the worker's current version when it pins none.

- `name` (string): The routine's name, 1 to 256 characters.

- `description` (string, or null): What the routine is for, up to 2,048 characters.

- `metadata` (object): Your own labels for the routine: up to 16 keys, each 1 to 64 characters, each with a string value of up to 512 characters.

- `initial_events` (array of objects): The events each new task starts with, in order: one message or more, and at most one outcome. A routine made in the app reads as one `user.message`.

  - `type` (string): `user.message` is a message each new task starts with. `user.define_outcome` is what each task's work is checked against; send at most one. One of `user.message`, `user.define_outcome`.

  - `content` (array of text blocks): With `user.message`: one text block or more, each `{ "type": "text", "text": "..." }`. The messages' text, joined by blank lines, is the task's ask, up to 100,000 characters.

  - `description` (string): With `user.define_outcome`: what done looks like.

  - `rubric` (object): With `user.define_outcome`: `{ "type": "text", "content": "..." }`. Each line of the text is one of each task's Done Means: up to 8 lines of up to 200 characters.

  - `max_iterations` (integer, or null): With `user.define_outcome`: how many check rounds before it gives up.

- `schedule` (object, or null): When the routine runs by itself. `null` when it runs only by hand or from `triggers`. A daily schedule made in the app reads as cron.

  - `type` (string): Always `cron`.

  - `expression` (string): A cron expression of five fields, such as `0 8 * * 1-5` (8:00 on weekdays), or an interval such as `@every 2h`, up to 256 characters. Runs are at least 5 minutes apart; a tighter expression is refused. A hosted plan may set a longer gap.

  - `timezone` (string): An IANA time zone name, such as `America/Chicago`, up to 64 characters.

  - `last_run_at` (time, or null): When the routine last ran on its schedule. `null` before the first time.

  - `upcoming_runs_at` (array of times): The next 5 exact times it will run, as planned. Each run starts a fixed moment later, up to 15 percent of the gap between runs (5 seconds to 9 minutes), the same for every run of one routine. Empty while the routine is paused or archived.

- `triggers` (array of objects): The other ways the routine starts, beside its schedule. Empty when it has none.

  - `type` (string): `webhook` starts the routine when its webhook is called; a person makes the webhook's key in the app, because this API does not. `email` starts it when a message arrives at one of the workspace's inboxes. `event` starts it when an app reports an event. Each type may appear once. One of `webhook`, `email`, `event`.

  - `inbox` (string): With `email`: the inbox's address, up to 254 characters. It must be one of the workspace's own.

  - `app` (string): With `event`: the app, 1 to 64 characters.

  - `event` (string): With `event`: the app's name for the event, 1 to 120 characters.

- `budget` (object, or null): The most each task may cost. `null` gives each task the default budget, $5.00.

  - `type` (string): Always `limit`.

  - `max_list_cost` (money): The most one task's work may cost at list price, from 1 cent to $1,000.

    - `amount` (string): Whole US cents, as a string: `"250"` is $2.50.

    - `currency` (string): Always `USD`.

- `resources` (array of objects): The files, repositories and memory stores each task is given. A repository's token is never answered.

  - `type` (string): The kind of resource. One of `file`, `github_repository`, `memory_store`.

  - `file_id` (file id): With `file`: a file you uploaded. List it once only.

  - `url` (string): With `github_repository`: a `https://github.com/owner/repository` address.

  - `checkout` (object, or null): With `github_repository`: what to check out, `{ "type": "branch", "name" }` or `{ "type": "commit", "sha" }` with a full 40-character SHA.

  - `memory_store_id` (memory store id): With `memory_store`: the store. A routine names at most 8, none archived or a worker's own.

  - `access` (string): With `memory_store`: `read_write` or `read_only`. One of `read_write`, `read_only`.

  - `instructions` (string, or null): With `memory_store`: how a task should use the store, up to 4,096 characters.

  - `mount_path` (string, or null): Always `null`: a routine has no folder of its own. Each task it makes gets its own.

- `password_set_ids` (array of password set ids): The password sets each task signs in with, in order. Unless the routine named others, the worker's own set, then the workspace's shared set.

- `status` (string): `active`, or `paused` when it does not start by itself. An archived routine reads `active`, with `archived_at` set. One of `active`, `paused`.

- `paused_reason` (object, or null): Why the routine is paused. `null` while it is not.

  - `type` (string): `manual` when a person paused it. `error` when a start failed in a way that will not clear. One of `manual`, `error`.

  - `error` (object): With `error`: its `type` is `worker_archived_error` (the worker was archived), `password_set_not_found_error` (a set the routine names was deleted) or `password_set_archived_error` (a set it names was archived).

- `version` (integer): Counts the changes made to the routine, starting at 1. Send it back as `version` when you change the routine, and a change someone else made first is refused instead of overwritten.

- `created_at` (time): When the routine was made.

- `updated_at` (time): When anything about the routine last changed.

- `archived_at` (time, or null): When the routine was archived. `null` while it is not.

```json
{
  "type": "routine",
  "id": "routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d",
  "worker": {
    "type": "worker",
    "id": "worker_8c1f4e2a9b7d4f0e8a6c3b5d1e9f7a20",
    "version": 1
  },
  "name": "Weekday opening email",
  "description": null,
  "metadata": {},
  "initial_events": [
    {
      "type": "user.message",
      "content": [
        {
          "type": "text",
          "text": "Write three subject lines for today's email that says the shop opens at 7 am."
        }
      ]
    }
  ],
  "schedule": {
    "type": "cron",
    "expression": "0 8 * * 1-5",
    "timezone": "America/Chicago",
    "last_run_at": "2026-10-23T13:03:12.408Z",
    "upcoming_runs_at": [
      "2026-10-26T13:00:00.000Z",
      "2026-10-27T13:00:00.000Z",
      "2026-10-28T13:00:00.000Z",
      "2026-10-29T13:00:00.000Z",
      "2026-10-30T13:00:00.000Z"
    ]
  },
  "triggers": [],
  "budget": {
    "type": "limit",
    "max_list_cost": {
      "amount": "300",
      "currency": "USD"
    }
  },
  "resources": [],
  "password_set_ids": [
    "password_set_597a64d52e7f4e84ac5d95929bf4cbae",
    "password_set_37cbbff4bc744ede8217773f8a942448"
  ],
  "status": "active",
  "paused_reason": null,
  "version": 3,
  "created_at": "2026-10-20T15:12:40.206Z",
  "updated_at": "2026-10-22T18:41:09.733Z",
  "archived_at": null
}
```

## Make a routine

`POST /routines`

Make a routine for a worker. Say what each task starts with in `initial_events`, written as you would to a person, and when it runs in `schedule`, `triggers` or both. With neither, the routine runs only when you run it.

The routine is active at once, and its first run is planned from now. The same `Idempotency-Key` gives the same routine.

### Headers

- `Idempotency-Key` (UUID): Send one so a retry does the work once. A second call with the same key and the same content answers what the first call did. The same key with other content is `409` with `error_code` `idempotency_conflict`. Kept for 7 days.

### Body

- `worker` (worker id or object, required): The worker to give a task each time: its id, or `{ "type": "worker", "id", "version" }` to pin one version of it. It must be an active worker in the key's workspace.
- `name` (string, required): The routine's name, 1 to 256 characters.
- `initial_events` (array of objects, required): The events each new task starts with, in order: 1 to 50. At least one is a `user.message`, and at most one is a `user.define_outcome`.
  - `type` (string, required): `user.message` is a message each new task starts with. `user.define_outcome` is what each task's work is checked against; send at most one. One of `user.message`, `user.define_outcome`.
  - `content` (array of text blocks, required): With `user.message`: one text block or more, each `{ "type": "text", "text": "..." }`. The messages' text, joined by blank lines, is the task's ask, up to 100,000 characters.
  - `description` (string, required): With `user.define_outcome`: what done looks like.
  - `rubric` (object, required): With `user.define_outcome`: `{ "type": "text", "content": "..." }`. Each line of the text is one of each task's Done Means: up to 8 lines of up to 200 characters.
  - `max_iterations` (integer, or null): With `user.define_outcome`: how many check rounds before it gives up.
- `description` (string, or null): What the routine is for, up to 2,048 characters.
- `metadata` (object): Your own labels: up to 16 keys, each 1 to 64 characters, each with a string value of up to 512 characters.
- `schedule` (object, or null): When it runs by itself. A clock time that a time change skips is recorded as missed, and a repeated one runs once. A run missed while Little Worker was down runs once, and runs missed while the routine is paused are not made up.
  - `type` (string, required): Always `cron`.
  - `expression` (string, required): A cron expression of five fields, such as `0 8 * * 1-5` (8:00 on weekdays), or an interval such as `@every 2h`, up to 256 characters. Runs are at least 5 minutes apart; a tighter expression is refused. A hosted plan may set a longer gap.
  - `timezone` (string, required): An IANA time zone name, such as `America/Chicago`, up to 64 characters.
- `triggers` (array of objects): Other ways it starts, beside the schedule.
  - `type` (string): `webhook` starts the routine when its webhook is called; a person makes the webhook's key in the app, because this API does not. `email` starts it when a message arrives at one of the workspace's inboxes. `event` starts it when an app reports an event. Each type may appear once. One of `webhook`, `email`, `event`.
  - `inbox` (string): With `email`: the inbox's address, up to 254 characters. It must be one of the workspace's own.
  - `app` (string): With `event`: the app, 1 to 64 characters.
  - `event` (string): With `event`: the app's name for the event, 1 to 120 characters.
- `budget` (object, or null): The most each task may cost. Left out or `null`, each task gets the default, $5.00.
  - `type` (string): Always `limit`.
  - `max_list_cost` (money): The most one task's work may cost at list price, from 1 cent to $1,000.
    - `amount` (string): Whole US cents, as a string: `"250"` is $2.50.
    - `currency` (string): Always `USD`.
- `resources` (array of objects): Up to 100 files, repositories and memory stores each task is given, in the form a task is given them in [Give a task](/api/tasks#give-a-task).
  - `type` (string, required): The kind of resource. One of `file`, `github_repository`, `memory_store`.
  - `file_id` (file id): With `file`: a file you uploaded. List it once only.
  - `url` (string): With `github_repository`: a `https://github.com/owner/repository` address.
  - `authorization_token` (string): With `github_repository`: a token that may clone it, 1 to 4,096 characters. It is kept sealed and never answered.
  - `checkout` (object): With `github_repository`: what to check out, `{ "type": "branch", "name" }` or `{ "type": "commit", "sha" }` with a full 40-character SHA.
  - `memory_store_id` (memory store id): With `memory_store`: the store. A routine names at most 8, none archived or a worker's own.
  - `access` (string): With `memory_store`: `read_write` or `read_only`. Left out, it is `read_write`. One of `read_write`, `read_only`.
  - `instructions` (string): With `memory_store`: how a task should use the store, up to 4,096 characters.
- `password_set_ids` (array of password set ids): Up to 20 password sets each task signs in with, in place of the worker's own set and the shared set.

### Returns

`200` and the routine.

### Example

```bash
curl "https://core.thelittleworker.com/api/v1/routines" \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19" \
  -H "Idempotency-Key: c3a5e7b9-1d2f-4a6c-8e0b-2d4f6a8c1e3b" \
  -H "content-type: application/json" \
  -d '{
  "worker": "worker_8c1f4e2a9b7d4f0e8a6c3b5d1e9f7a20",
  "name": "Weekday opening email",
  "initial_events": [
    {
      "type": "user.message",
      "content": [
        {
          "type": "text",
          "text": "Write three subject lines for today'\''s email that says the shop opens at 7 am."
        }
      ]
    }
  ],
  "schedule": {
    "type": "cron",
    "expression": "0 8 * * 1-5",
    "timezone": "America/Chicago"
  },
  "budget": {
    "type": "limit",
    "max_list_cost": {
      "amount": "300",
      "currency": "USD"
    }
  }
}'
```

```json
{
  "type": "routine",
  "id": "routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d",
  "worker": {
    "type": "worker",
    "id": "worker_8c1f4e2a9b7d4f0e8a6c3b5d1e9f7a20",
    "version": 1
  },
  "name": "Weekday opening email",
  "description": null,
  "metadata": {},
  "initial_events": [
    {
      "type": "user.message",
      "content": [
        {
          "type": "text",
          "text": "Write three subject lines for today's email that says the shop opens at 7 am."
        }
      ]
    }
  ],
  "schedule": {
    "type": "cron",
    "expression": "0 8 * * 1-5",
    "timezone": "America/Chicago",
    "last_run_at": null,
    "upcoming_runs_at": [
      "2026-10-26T13:00:00.000Z",
      "2026-10-27T13:00:00.000Z",
      "2026-10-28T13:00:00.000Z",
      "2026-10-29T13:00:00.000Z",
      "2026-10-30T13:00:00.000Z"
    ]
  },
  "triggers": [],
  "budget": {
    "type": "limit",
    "max_list_cost": {
      "amount": "300",
      "currency": "USD"
    }
  },
  "resources": [],
  "password_set_ids": [
    "password_set_597a64d52e7f4e84ac5d95929bf4cbae",
    "password_set_37cbbff4bc744ede8217773f8a942448"
  ],
  "status": "active",
  "paused_reason": null,
  "version": 1,
  "created_at": "2026-10-25T17:20:14.581Z",
  "updated_at": "2026-10-25T17:20:14.581Z",
  "archived_at": null
}
```

### Errors

- 400 `invalid_request_error`: A field is missing or not valid: an id of the wrong kind, a field the route does not take, a `name` over 256 characters, messages over 100,000 characters, an outcome rubric that is not 1 to 8 lines of up to 200 characters, a trigger type given twice, an `Idempotency-Key` that is not a UUID, or a list a task could not take (a file listed twice or expired, more than 8 memory stores, one listed twice, archived, or a worker's own). `message` says which. `error_code` is `validation_error`.
- 400 `invalid_request_error`: `schedule.timezone` is not a time zone (`error_code` `invalid_timezone`). Use a name like `America/Chicago`.
- 400 `invalid_request_error`: `schedule.expression` is not valid (`Enter a valid schedule.`), or runs more often than every 5 minutes (`Routine schedules must be at least 5 minutes apart.`). On a hosted plan with a longer gap, the message says how often a task may repeat. `error_code` is `validation_error`.
- 401 `authentication_error`: The key is missing, removed, or not a key. Send it in `x-api-key`.
- 404 `not_found_error`: The worker is not one of the key's (`Worker not found.`), the pinned version does not exist (`Worker version not found.`), an `email` trigger names an inbox that is not the workspace's, or a password set, file or memory store does not exist.
- 409 `conflict_error`: The worker is archived, or the limit is reached: 1,000 routines in the workspace, or 50 for the worker. Archive one first. A hosted plan may allow fewer.
- 409 `conflict_error`: The `Idempotency-Key` was already used with other content (`error_code` `idempotency_conflict`). Use a new key for new work.
- 429 `rate_limit_error`: The workspace made too many requests this minute. Wait the seconds in `Retry-After`, then try again.

## List routines

`GET /routines`

List the routines of the key's workers, newest first. An archived routine is left out unless you ask for `include_archived=true`.

### Query parameters

- `worker_id` (worker id): Only this worker's routines.
- `include_archived` (string): `true` lists archived routines too. One of `true`, `false`.
- `limit` (integer): How many to answer, 1 to 100. The default is 20.
- `page` (string): The `next_page` of the answer before, to read the next page.
- `order` (string): `desc`, newest first (the default), or `asc`, oldest first. One of `desc`, `asc`.
- `created_at[gt]` (time): Only those made after this time. `created_at[gte]`, `created_at[lt]` and `created_at[lte]` work the same way.

### Returns

A list: `data` holds the routines, and `next_page` is the `page` to send for the next ones (`null` on the last page).

### Example

```bash
curl "https://core.thelittleworker.com/api/v1/routines?worker_id=worker_8c1f4e2a9b7d4f0e8a6c3b5d1e9f7a20&limit=2" \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19"
```

```json
{
  "data": [
    {
      "type": "routine",
      "id": "routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d",
      "worker": {
        "type": "worker",
        "id": "worker_8c1f4e2a9b7d4f0e8a6c3b5d1e9f7a20",
        "version": 1
      },
      "name": "Weekday opening email",
      "description": null,
      "metadata": {},
      "initial_events": [
        {
          "type": "user.message",
          "content": [
            {
              "type": "text",
              "text": "Write three subject lines for today's email that says the shop opens at 7 am."
            }
          ]
        }
      ],
      "schedule": {
        "type": "cron",
        "expression": "0 8 * * 1-5",
        "timezone": "America/Chicago",
        "last_run_at": "2026-10-23T13:03:12.408Z",
        "upcoming_runs_at": [
          "2026-10-26T13:00:00.000Z",
          "2026-10-27T13:00:00.000Z",
          "2026-10-28T13:00:00.000Z",
          "2026-10-29T13:00:00.000Z",
          "2026-10-30T13:00:00.000Z"
        ]
      },
      "triggers": [],
      "budget": {
        "type": "limit",
        "max_list_cost": {
          "amount": "300",
          "currency": "USD"
        }
      },
      "resources": [],
      "password_set_ids": [
        "password_set_597a64d52e7f4e84ac5d95929bf4cbae",
        "password_set_37cbbff4bc744ede8217773f8a942448"
      ],
      "status": "active",
      "paused_reason": null,
      "version": 3,
      "created_at": "2026-10-20T15:12:40.206Z",
      "updated_at": "2026-10-22T18:41:09.733Z",
      "archived_at": null
    }
  ],
  "next_page": null
}
```

### Errors

- 400 `invalid_request_error`: A filter is not valid, such as a `worker_id` of the wrong kind or a `limit` outside 1 to 100 (`error_code` `validation_error`), or `page` is not a page this list gave with the same `order` and `created_at` filters (`error_code` `invalid_cursor`). Read the first page again.
- 401 `authentication_error`: The key is missing, removed, or not a key. Send it in `x-api-key`.
- 404 `not_found_error`: `worker_id` names no worker of the key's (`Worker not found.`).
- 429 `rate_limit_error`: The workspace made too many requests this minute. Wait the seconds in `Retry-After`, then try again.

## Read a routine

`GET /routines/{routine_id}`

Read one routine as it stands now, archived or not.

### Path parameters

- `routine_id` (routine id, required): The routine, such as `routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d`.

### Returns

The routine.

### Example

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

```json
{
  "type": "routine",
  "id": "routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d",
  "worker": {
    "type": "worker",
    "id": "worker_8c1f4e2a9b7d4f0e8a6c3b5d1e9f7a20",
    "version": 1
  },
  "name": "Weekday opening email",
  "description": null,
  "metadata": {},
  "initial_events": [
    {
      "type": "user.message",
      "content": [
        {
          "type": "text",
          "text": "Write three subject lines for today's email that says the shop opens at 7 am."
        }
      ]
    }
  ],
  "schedule": {
    "type": "cron",
    "expression": "0 8 * * 1-5",
    "timezone": "America/Chicago",
    "last_run_at": "2026-10-23T13:03:12.408Z",
    "upcoming_runs_at": [
      "2026-10-26T13:00:00.000Z",
      "2026-10-27T13:00:00.000Z",
      "2026-10-28T13:00:00.000Z",
      "2026-10-29T13:00:00.000Z",
      "2026-10-30T13:00:00.000Z"
    ]
  },
  "triggers": [],
  "budget": {
    "type": "limit",
    "max_list_cost": {
      "amount": "300",
      "currency": "USD"
    }
  },
  "resources": [],
  "password_set_ids": [
    "password_set_597a64d52e7f4e84ac5d95929bf4cbae",
    "password_set_37cbbff4bc744ede8217773f8a942448"
  ],
  "status": "active",
  "paused_reason": null,
  "version": 3,
  "created_at": "2026-10-20T15:12:40.206Z",
  "updated_at": "2026-10-22T18:41:09.733Z",
  "archived_at": null
}
```

### Errors

- 400 `invalid_request_error`: `routine_id` is not a routine id. `error_code` is `validation_error`.
- 401 `authentication_error`: The key is missing, removed, or not a key. Send it in `x-api-key`.
- 404 `not_found_error`: No routine of one of the key's workers has this id (`Routine not found.`).
- 429 `rate_limit_error`: The workspace made too many requests this minute. Wait the seconds in `Retry-After`, then try again.

## Change a routine

`POST /routines/{routine_id}`

Change a routine. A field left out stays as it was. A list you send replaces the routine's list in full, and `metadata` is merged key by key.

To change a routine only if nobody changed it since you read it, send its `version`. A routine changed since is refused with `409`, and the error carries `current_version`. Without `version`, the last write wins.

### Path parameters

- `routine_id` (routine id, required): The routine, such as `routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d`.

### Headers

- `Idempotency-Key` (UUID): Send one so a retry does the work once. A second call with the same key and the same content answers what the first call did. The same key with other content is `409` with `error_code` `idempotency_conflict`. Kept for 7 days.

### Body

- `version` (integer): The `version` of the routine you read, 1 or more. The change is refused if the routine has changed since.
- `worker` (worker id or object): The routine's own worker, by its id or as `{ "type": "worker", "id", "version" }` to pin a version. An id alone makes the routine follow the worker's current version. Any other worker is refused: a routine keeps its worker.
- `name` (string): A new name, 1 to 256 characters.
- `description` (string, or null): A new description, up to 2,048 characters. `null` clears it.
- `metadata` (object, or null): Labels to set. A key with a string value is set, a key set to `null` is removed, and keys left out stay. `null` for the whole object clears every label.
- `initial_events` (array of objects): The events each new task starts with. The list replaces the routine's, in full.
  - `type` (string, required): `user.message` is a message each new task starts with. `user.define_outcome` is what each task's work is checked against; send at most one. One of `user.message`, `user.define_outcome`.
  - `content` (array of text blocks, required): With `user.message`: one text block or more, each `{ "type": "text", "text": "..." }`. The messages' text, joined by blank lines, is the task's ask, up to 100,000 characters.
  - `description` (string, required): With `user.define_outcome`: what done looks like.
  - `rubric` (object, required): With `user.define_outcome`: `{ "type": "text", "content": "..." }`. Each line of the text is one of each task's Done Means: up to 8 lines of up to 200 characters.
  - `max_iterations` (integer, or null): With `user.define_outcome`: how many check rounds before it gives up.
- `schedule` (object, or null): A new schedule. `null` clears it, and the routine then runs only by hand or from `triggers`.
  - `type` (string, required): Always `cron`.
  - `expression` (string, required): A cron expression of five fields, such as `0 8 * * 1-5` (8:00 on weekdays), or an interval such as `@every 2h`, up to 256 characters. Runs are at least 5 minutes apart; a tighter expression is refused. A hosted plan may set a longer gap.
  - `timezone` (string, required): An IANA time zone name, such as `America/Chicago`, up to 64 characters.
- `triggers` (array of objects): The other starts beside the schedule. The list replaces the routine's, in full.
  - `type` (string): `webhook` starts the routine when its webhook is called; a person makes the webhook's key in the app, because this API does not. `email` starts it when a message arrives at one of the workspace's inboxes. `event` starts it when an app reports an event. Each type may appear once. One of `webhook`, `email`, `event`.
  - `inbox` (string): With `email`: the inbox's address, up to 254 characters. It must be one of the workspace's own.
  - `app` (string): With `event`: the app, 1 to 64 characters.
  - `event` (string): With `event`: the app's name for the event, 1 to 120 characters.
- `budget` (object, or null): A new budget for each task. `null` goes back to the default, $5.00.
  - `type` (string): Always `limit`.
  - `max_list_cost` (money): The most one task's work may cost at list price, from 1 cent to $1,000.
    - `amount` (string): Whole US cents, as a string: `"250"` is $2.50.
    - `currency` (string): Always `USD`.
- `resources` (array of objects, or null): The whole list of resources each task is given. `[]` or `null` clears it.
  - `type` (string, required): The kind of resource. One of `file`, `github_repository`, `memory_store`.
  - `file_id` (file id): With `file`: a file you uploaded. List it once only.
  - `url` (string): With `github_repository`: a `https://github.com/owner/repository` address.
  - `authorization_token` (string): With `github_repository`: a token that may clone it, 1 to 4,096 characters. It is kept sealed and never answered.
  - `checkout` (object): With `github_repository`: what to check out, `{ "type": "branch", "name" }` or `{ "type": "commit", "sha" }` with a full 40-character SHA.
  - `memory_store_id` (memory store id): With `memory_store`: the store. A routine names at most 8, none archived or a worker's own.
  - `access` (string): With `memory_store`: `read_write` or `read_only`. Left out, it is `read_write`. One of `read_write`, `read_only`.
  - `instructions` (string): With `memory_store`: how a task should use the store, up to 4,096 characters.
- `password_set_ids` (array of password set ids, or null): The whole list of password sets each task signs in with, up to 20. `null` goes back to the worker's own set and the workspace's shared set, and `[]` names none.

### Returns

`200` and the routine, changed.

### Example

```bash
curl "https://core.thelittleworker.com/api/v1/routines/routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d" \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19" \
  -H "Idempotency-Key: 5e7a9c1b-3d4f-4b6e-a0c2-4f6b8d0a2c5e" \
  -H "content-type: application/json" \
  -d '{
  "version": 3,
  "schedule": {
    "type": "cron",
    "expression": "0 9 * * 1-5",
    "timezone": "America/Chicago"
  }
}'
```

```json
{
  "type": "routine",
  "id": "routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d",
  "worker": {
    "type": "worker",
    "id": "worker_8c1f4e2a9b7d4f0e8a6c3b5d1e9f7a20",
    "version": 1
  },
  "name": "Weekday opening email",
  "description": null,
  "metadata": {},
  "initial_events": [
    {
      "type": "user.message",
      "content": [
        {
          "type": "text",
          "text": "Write three subject lines for today's email that says the shop opens at 7 am."
        }
      ]
    }
  ],
  "schedule": {
    "type": "cron",
    "expression": "0 9 * * 1-5",
    "timezone": "America/Chicago",
    "last_run_at": "2026-10-23T13:03:12.408Z",
    "upcoming_runs_at": [
      "2026-10-26T14:00:00.000Z",
      "2026-10-27T14:00:00.000Z",
      "2026-10-28T14:00:00.000Z",
      "2026-10-29T14:00:00.000Z",
      "2026-10-30T14:00:00.000Z"
    ]
  },
  "triggers": [],
  "budget": {
    "type": "limit",
    "max_list_cost": {
      "amount": "300",
      "currency": "USD"
    }
  },
  "resources": [],
  "password_set_ids": [
    "password_set_597a64d52e7f4e84ac5d95929bf4cbae",
    "password_set_37cbbff4bc744ede8217773f8a942448"
  ],
  "status": "active",
  "paused_reason": null,
  "version": 4,
  "created_at": "2026-10-20T15:12:40.206Z",
  "updated_at": "2026-10-25T17:31:50.092Z",
  "archived_at": null
}
```

### Errors

- 400 `invalid_request_error`: `routine_id` is not a routine id. `error_code` is `validation_error`.
- 400 `invalid_request_error`: A field is missing or not valid: an id of the wrong kind, a field the route does not take, a `name` over 256 characters, messages over 100,000 characters, an outcome rubric that is not 1 to 8 lines of up to 200 characters, a trigger type given twice, an `Idempotency-Key` that is not a UUID, or a list a task could not take (a file listed twice or expired, more than 8 memory stores, one listed twice, archived, or a worker's own). `message` says which. `error_code` is `validation_error`.
- 400 `invalid_request_error`: `schedule.timezone` is not a time zone (`error_code` `invalid_timezone`). Use a name like `America/Chicago`.
- 400 `invalid_request_error`: `schedule.expression` is not valid (`Enter a valid schedule.`), or runs more often than every 5 minutes (`Routine schedules must be at least 5 minutes apart.`). On a hosted plan with a longer gap, the message says how often a task may repeat. `error_code` is `validation_error`.
- 400 `invalid_request_error`: `worker` names a worker other than the routine's own. Make a new routine for another worker.
- 401 `authentication_error`: The key is missing, removed, or not a key. Send it in `x-api-key`.
- 404 `not_found_error`: No routine of one of the key's workers has this id (`Routine not found.`), the pinned version does not exist, or a password set, file or memory store does not exist.
- 409 `conflict_error`: The routine is archived and cannot be changed.
- 409 `conflict_error`: The routine changed since the `version` you sent (`error_code` `schedule_conflict`). Nothing was changed. The error carries `current_version`: read the routine, apply your change to it, and send it again.
- 409 `conflict_error`: The `Idempotency-Key` was already used with other content (`error_code` `idempotency_conflict`). Use a new key for new work.
- 429 `rate_limit_error`: The workspace made too many requests this minute. Wait the seconds in `Retry-After`, then try again.

## Pause a routine

`POST /routines/{routine_id}/pause`

Pause a routine. A paused routine starts by itself no more, from its schedule or any other start, but you can still run it. Its `paused_reason` is `manual`. Pausing a routine that is paused changes nothing.

### Path parameters

- `routine_id` (routine id, required): The routine, such as `routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d`.

### Headers

- `Idempotency-Key` (UUID): Send one so a retry does the work once. Kept for 7 days.

### Returns

`200` and the routine, paused.

### Example

```bash
curl "https://core.thelittleworker.com/api/v1/routines/routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d/pause" \
  -X POST \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19" \
  -H "Idempotency-Key: 8b0d2f4a-6c1e-4e3a-9b5d-7f1a3c5e9b2d"
```

```json
{
  "type": "routine",
  "id": "routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d",
  "worker": {
    "type": "worker",
    "id": "worker_8c1f4e2a9b7d4f0e8a6c3b5d1e9f7a20",
    "version": 1
  },
  "name": "Weekday opening email",
  "description": null,
  "metadata": {},
  "initial_events": [
    {
      "type": "user.message",
      "content": [
        {
          "type": "text",
          "text": "Write three subject lines for today's email that says the shop opens at 7 am."
        }
      ]
    }
  ],
  "schedule": {
    "type": "cron",
    "expression": "0 8 * * 1-5",
    "timezone": "America/Chicago",
    "last_run_at": "2026-10-23T13:03:12.408Z",
    "upcoming_runs_at": []
  },
  "triggers": [],
  "budget": {
    "type": "limit",
    "max_list_cost": {
      "amount": "300",
      "currency": "USD"
    }
  },
  "resources": [],
  "password_set_ids": [
    "password_set_597a64d52e7f4e84ac5d95929bf4cbae",
    "password_set_37cbbff4bc744ede8217773f8a942448"
  ],
  "status": "paused",
  "paused_reason": {
    "type": "manual"
  },
  "version": 4,
  "created_at": "2026-10-20T15:12:40.206Z",
  "updated_at": "2026-10-25T17:34:22.870Z",
  "archived_at": null
}
```

### Errors

- 400 `invalid_request_error`: `routine_id` is not a routine id. `error_code` is `validation_error`.
- 401 `authentication_error`: The key is missing, removed, or not a key. Send it in `x-api-key`.
- 404 `not_found_error`: No routine of one of the key's workers has this id (`Routine not found.`).
- 409 `conflict_error`: The routine is archived and cannot be paused.
- 429 `rate_limit_error`: The workspace made too many requests this minute. Wait the seconds in `Retry-After`, then try again.

## Unpause a routine

`POST /routines/{routine_id}/unpause`

Let a paused routine start by itself again. Its next run is planned from now, so the times it missed while paused are not made up. Unpausing a routine that is active changes nothing. A routine that names an archived password set cannot be unpaused: change its `password_set_ids` first.

### Path parameters

- `routine_id` (routine id, required): The routine, such as `routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d`.

### Headers

- `Idempotency-Key` (UUID): Send one so a retry does the work once. Kept for 7 days.

### Returns

`200` and the routine, active.

### Example

```bash
curl "https://core.thelittleworker.com/api/v1/routines/routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d/unpause" \
  -X POST \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19" \
  -H "Idempotency-Key: 1f3b5d7a-9c2e-4a4c-b6e8-0a2c4e6b8d1f"
```

```json
{
  "type": "routine",
  "id": "routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d",
  "worker": {
    "type": "worker",
    "id": "worker_8c1f4e2a9b7d4f0e8a6c3b5d1e9f7a20",
    "version": 1
  },
  "name": "Weekday opening email",
  "description": null,
  "metadata": {},
  "initial_events": [
    {
      "type": "user.message",
      "content": [
        {
          "type": "text",
          "text": "Write three subject lines for today's email that says the shop opens at 7 am."
        }
      ]
    }
  ],
  "schedule": {
    "type": "cron",
    "expression": "0 8 * * 1-5",
    "timezone": "America/Chicago",
    "last_run_at": "2026-10-23T13:03:12.408Z",
    "upcoming_runs_at": [
      "2026-10-26T13:00:00.000Z",
      "2026-10-27T13:00:00.000Z",
      "2026-10-28T13:00:00.000Z",
      "2026-10-29T13:00:00.000Z",
      "2026-10-30T13:00:00.000Z"
    ]
  },
  "triggers": [],
  "budget": {
    "type": "limit",
    "max_list_cost": {
      "amount": "300",
      "currency": "USD"
    }
  },
  "resources": [],
  "password_set_ids": [
    "password_set_597a64d52e7f4e84ac5d95929bf4cbae",
    "password_set_37cbbff4bc744ede8217773f8a942448"
  ],
  "status": "active",
  "paused_reason": null,
  "version": 5,
  "created_at": "2026-10-20T15:12:40.206Z",
  "updated_at": "2026-10-25T17:36:05.319Z",
  "archived_at": null
}
```

### Errors

- 400 `invalid_request_error`: `routine_id` is not a routine id. `error_code` is `validation_error`.
- 400 `invalid_request_error`: The routine names a password set that is archived (`error_code` `validation_error`). Change its `password_set_ids`, then unpause it.
- 401 `authentication_error`: The key is missing, removed, or not a key. Send it in `x-api-key`.
- 404 `not_found_error`: No routine of one of the key's workers has this id (`Routine not found.`).
- 409 `conflict_error`: The routine is archived and cannot be unpaused.
- 429 `rate_limit_error`: The workspace made too many requests this minute. Wait the seconds in `Retry-After`, then try again.

## Run a routine

`POST /routines/{routine_id}/run`

Run a routine now, whatever its schedule says, and even while it is paused. The worker gets a task as the routine describes, and the answer is the routine run that recorded it. Its `task_id` is the task: read its work with the [task routes](/api/tasks).

A worker does one task at a time, so a worker already on a task refuses the run. Run it again when the worker is done.

### Path parameters

- `routine_id` (routine id, required): The routine, such as `routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d`.

### Headers

- `Idempotency-Key` (UUID): Send one so a retry does the work once. A second call with the same key and the same content answers what the first call did. The same key with other content is `409` with `error_code` `idempotency_conflict`. Kept for 7 days.

### Body

- `expected_configuration_revision` (integer): The `version` of the routine you expect, 1 or more. The run is refused if the routine changed since. Left out, the routine runs as it is now.

### Returns

`200` and the routine run it recorded.

- `type` (string): Always `routine_run`.
- `id` (routine run id): The run's id: `routine_run_` and 32 hex digits.
- `routine_id` (routine id): The routine.
- `worker` (object): The worker that was to do the work.
  - `type` (string): Always `worker`.
  - `id` (worker id): The worker.
- `trigger_context` (object): What started it, in its `type`.
  - `type` (string): `schedule` when the schedule matched, `manual` when someone ran the routine, and `webhook`, `email` or `event` for the other starts. One of `schedule`, `manual`, `webhook`, `email`, `event`.
  - `scheduled_at` (time): With `schedule`: the instant the schedule matched.
  - `inbox` (string): With `email`: the inbox the message came to.
  - `app` (string, or null): With `event`: the app. `null` for a run from before it was recorded.
  - `event` (string, or null): With `event`: the event. `null` for a run from before it was recorded.
- `task_id` (task id, or null): The task the run made. Exactly one of `task_id` and `error` is set.
- `error` (object, or null): Why the run made no task. `null` when it made one.
  - `type` (string): `worker_archived_error`: the worker was archived. `worker_busy_error`: the worker was busy with other work. `nonexistent_local_time_error`: the local time did not exist that day. `more_than_one_hour_late_error`: Little Worker was down for more than an hour past the scheduled time. `routine_paused_error`: the routine was paused. `superseded_edit_error`: an edit replaced the scheduled time. `unknown_error`: something else. One of `worker_archived_error`, `worker_busy_error`, `nonexistent_local_time_error`, `more_than_one_hour_late_error`, `routine_paused_error`, `superseded_edit_error`, `unknown_error`.
  - `message` (string): A sentence a person can read.
- `created_at` (time): When the run was recorded.

### Example

```bash
curl "https://core.thelittleworker.com/api/v1/routines/routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d/run" \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19" \
  -H "Idempotency-Key: 4d6f8a0c-2b3e-4c5a-8d7f-9b1d3f5a7c0e" \
  -H "content-type: application/json" \
  -d '{
  "expected_configuration_revision": 3
}'
```

```json
{
  "type": "routine_run",
  "id": "routine_run_2e4a6c8b0d1f4a3e9c5b7d9f1a3c5e7b",
  "routine_id": "routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d",
  "worker": {
    "type": "worker",
    "id": "worker_8c1f4e2a9b7d4f0e8a6c3b5d1e9f7a20"
  },
  "trigger_context": {
    "type": "manual"
  },
  "task_id": "task_01JB2M4Z8H6R3S5T7V9W1X0Y2A",
  "error": null,
  "created_at": "2026-10-25T17:38:47.215Z"
}
```

### Errors

- 400 `invalid_request_error`: `routine_id` is not a routine id. `error_code` is `validation_error`.
- 400 `invalid_request_error`: `expected_configuration_revision` is not a whole number of 1 or more (`error_code` `validation_error`).
- 401 `authentication_error`: The key is missing, removed, or not a key. Send it in `x-api-key`.
- 404 `not_found_error`: No routine of one of the key's workers has this id (`Routine not found.`).
- 409 `conflict_error`: The routine is archived and cannot be run.
- 409 `conflict_error`: The worker is archived or already working on a task. Run the routine again when the worker is done. Or the routine changed since the `expected_configuration_revision` you sent (`error_code` `schedule_conflict`), and nothing ran.
- 409 `conflict_error`: The `Idempotency-Key` was already used with other content (`error_code` `idempotency_conflict`). Use a new key for new work.
- 429 `rate_limit_error`: The workspace made too many requests this minute. Wait the seconds in `Retry-After`, then try again.

## Archive a routine

`POST /routines/{routine_id}/archive`

Archive a routine you no longer need. It stays readable, with its runs, but it never starts again, and it cannot be changed, paused or run. Archiving cannot be undone, and a routine cannot be deleted. Archiving an archived routine changes nothing.

### Path parameters

- `routine_id` (routine id, required): The routine, such as `routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d`.

### Headers

- `Idempotency-Key` (UUID): Send one so a retry does the work once. Kept for 7 days.

### Returns

`200` and the routine, with `archived_at` set.

### Example

```bash
curl "https://core.thelittleworker.com/api/v1/routines/routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d/archive" \
  -X POST \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19" \
  -H "Idempotency-Key: 6a8c0e2b-4d5f-4e7b-a9c1-1e3a5c7e9b0d"
```

```json
{
  "type": "routine",
  "id": "routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d",
  "worker": {
    "type": "worker",
    "id": "worker_8c1f4e2a9b7d4f0e8a6c3b5d1e9f7a20",
    "version": 1
  },
  "name": "Weekday opening email",
  "description": null,
  "metadata": {},
  "initial_events": [
    {
      "type": "user.message",
      "content": [
        {
          "type": "text",
          "text": "Write three subject lines for today's email that says the shop opens at 7 am."
        }
      ]
    }
  ],
  "schedule": {
    "type": "cron",
    "expression": "0 8 * * 1-5",
    "timezone": "America/Chicago",
    "last_run_at": "2026-10-23T13:03:12.408Z",
    "upcoming_runs_at": []
  },
  "triggers": [],
  "budget": {
    "type": "limit",
    "max_list_cost": {
      "amount": "300",
      "currency": "USD"
    }
  },
  "resources": [],
  "password_set_ids": [
    "password_set_597a64d52e7f4e84ac5d95929bf4cbae",
    "password_set_37cbbff4bc744ede8217773f8a942448"
  ],
  "status": "active",
  "paused_reason": null,
  "version": 4,
  "created_at": "2026-10-20T15:12:40.206Z",
  "updated_at": "2026-10-25T17:40:31.447Z",
  "archived_at": "2026-10-25T17:40:31.447Z"
}
```

### Errors

- 400 `invalid_request_error`: `routine_id` is not a routine id. `error_code` is `validation_error`.
- 401 `authentication_error`: The key is missing, removed, or not a key. Send it in `x-api-key`.
- 404 `not_found_error`: No routine of one of the key's workers has this id (`Routine not found.`).
- 429 `rate_limit_error`: The workspace made too many requests this minute. Wait the seconds in `Retry-After`, then try again.

## List routine runs

`GET /routine_runs`

List the runs of routines, newest first: every time a routine ran and made a task, and every time it was due and made none. A run of a routine that is not the key's is not listed. A run stays here after its routine is archived.

### Query parameters

- `routine_id` (routine id): Only this routine's runs.
- `has_error` (string): `true` lists only runs that made no task. `false` lists only runs that made one. One of `true`, `false`.
- `trigger_type` (string): Only runs that something of this kind started. One of `schedule`, `manual`, `webhook`, `email`, `event`.
- `limit` (integer): How many to answer, 1 to 1,000. The default is 20.
- `page` (string): The `next_page` of the answer before, to read the next page.
- `order` (string): `desc`, newest first (the default), or `asc`, oldest first. One of `desc`, `asc`.
- `created_at[gt]` (time): Only those made after this time. `created_at[gte]`, `created_at[lt]` and `created_at[lte]` work the same way.

### Returns

A list: `data` holds the routine runs, and `next_page` is the `page` to send for the next ones (`null` on the last page). Each run has these fields.

- `type` (string): Always `routine_run`.
- `id` (routine run id): The run's id: `routine_run_` and 32 hex digits.
- `routine_id` (routine id): The routine.
- `worker` (object): The worker that was to do the work.
  - `type` (string): Always `worker`.
  - `id` (worker id): The worker.
- `trigger_context` (object): What started it, in its `type`.
  - `type` (string): `schedule` when the schedule matched, `manual` when someone ran the routine, and `webhook`, `email` or `event` for the other starts. One of `schedule`, `manual`, `webhook`, `email`, `event`.
  - `scheduled_at` (time): With `schedule`: the instant the schedule matched.
  - `inbox` (string): With `email`: the inbox the message came to.
  - `app` (string, or null): With `event`: the app. `null` for a run from before it was recorded.
  - `event` (string, or null): With `event`: the event. `null` for a run from before it was recorded.
- `task_id` (task id, or null): The task the run made. Exactly one of `task_id` and `error` is set.
- `error` (object, or null): Why the run made no task. `null` when it made one.
  - `type` (string): `worker_archived_error`: the worker was archived. `worker_busy_error`: the worker was busy with other work. `nonexistent_local_time_error`: the local time did not exist that day. `more_than_one_hour_late_error`: Little Worker was down for more than an hour past the scheduled time. `routine_paused_error`: the routine was paused. `superseded_edit_error`: an edit replaced the scheduled time. `unknown_error`: something else. One of `worker_archived_error`, `worker_busy_error`, `nonexistent_local_time_error`, `more_than_one_hour_late_error`, `routine_paused_error`, `superseded_edit_error`, `unknown_error`.
  - `message` (string): A sentence a person can read.
- `created_at` (time): When the run was recorded.

### Example

```bash
curl "https://core.thelittleworker.com/api/v1/routine_runs?routine_id=routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d&limit=2" \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19"
```

```json
{
  "data": [
    {
      "type": "routine_run",
      "id": "routine_run_2e4a6c8b0d1f4a3e9c5b7d9f1a3c5e7b",
      "routine_id": "routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d",
      "worker": {
        "type": "worker",
        "id": "worker_8c1f4e2a9b7d4f0e8a6c3b5d1e9f7a20"
      },
      "trigger_context": {
        "type": "manual"
      },
      "task_id": "task_01JB2M4Z8H6R3S5T7V9W1X0Y2A",
      "error": null,
      "created_at": "2026-10-25T17:38:47.215Z"
    },
    {
      "type": "routine_run",
      "id": "routine_run_9c7a5e3b1d2f4b6a8c0e2d4f6a8b1c3e",
      "routine_id": "routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d",
      "worker": {
        "type": "worker",
        "id": "worker_8c1f4e2a9b7d4f0e8a6c3b5d1e9f7a20"
      },
      "trigger_context": {
        "type": "schedule",
        "scheduled_at": "2026-10-22T13:00:00.000Z"
      },
      "task_id": null,
      "error": {
        "type": "worker_busy_error",
        "message": "The worker was busy with other work."
      },
      "created_at": "2026-10-22T13:00:04.771Z"
    }
  ],
  "next_page": null
}
```

### Errors

- 400 `invalid_request_error`: A filter is not valid, such as a `routine_id` of the wrong kind, a `trigger_type` that is not one of the five, or a `limit` outside 1 to 1,000 (`error_code` `validation_error`), or `page` is not a page this list gave with the same `order` and `created_at` filters (`error_code` `invalid_cursor`). Read the first page again.
- 401 `authentication_error`: The key is missing, removed, or not a key. Send it in `x-api-key`.
- 429 `rate_limit_error`: The workspace made too many requests this minute. Wait the seconds in `Retry-After`, then try again.

## Read a routine run

`GET /routine_runs/{routine_run_id}`

Read one routine run.

### Path parameters

- `routine_run_id` (routine run id, required): The run, such as `routine_run_7a1c3e5b9d2f4e6a8c0b1d3f5e7a9c2b`.

### Returns

The routine run.

- `type` (string): Always `routine_run`.
- `id` (routine run id): The run's id: `routine_run_` and 32 hex digits.
- `routine_id` (routine id): The routine.
- `worker` (object): The worker that was to do the work.
  - `type` (string): Always `worker`.
  - `id` (worker id): The worker.
- `trigger_context` (object): What started it, in its `type`.
  - `type` (string): `schedule` when the schedule matched, `manual` when someone ran the routine, and `webhook`, `email` or `event` for the other starts. One of `schedule`, `manual`, `webhook`, `email`, `event`.
  - `scheduled_at` (time): With `schedule`: the instant the schedule matched.
  - `inbox` (string): With `email`: the inbox the message came to.
  - `app` (string, or null): With `event`: the app. `null` for a run from before it was recorded.
  - `event` (string, or null): With `event`: the event. `null` for a run from before it was recorded.
- `task_id` (task id, or null): The task the run made. Exactly one of `task_id` and `error` is set.
- `error` (object, or null): Why the run made no task. `null` when it made one.
  - `type` (string): `worker_archived_error`: the worker was archived. `worker_busy_error`: the worker was busy with other work. `nonexistent_local_time_error`: the local time did not exist that day. `more_than_one_hour_late_error`: Little Worker was down for more than an hour past the scheduled time. `routine_paused_error`: the routine was paused. `superseded_edit_error`: an edit replaced the scheduled time. `unknown_error`: something else. One of `worker_archived_error`, `worker_busy_error`, `nonexistent_local_time_error`, `more_than_one_hour_late_error`, `routine_paused_error`, `superseded_edit_error`, `unknown_error`.
  - `message` (string): A sentence a person can read.
- `created_at` (time): When the run was recorded.

### Example

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

```json
{
  "type": "routine_run",
  "id": "routine_run_7a1c3e5b9d2f4e6a8c0b1d3f5e7a9c2b",
  "routine_id": "routine_5d3b8a1e7c2f4a9b8e0d6c4a2f1b3e5d",
  "worker": {
    "type": "worker",
    "id": "worker_8c1f4e2a9b7d4f0e8a6c3b5d1e9f7a20"
  },
  "trigger_context": {
    "type": "schedule",
    "scheduled_at": "2026-10-23T13:00:00.000Z"
  },
  "task_id": "task_01JB2K9X7F4N6P3Q8R5S1T0V2W",
  "error": null,
  "created_at": "2026-10-23T13:03:12.408Z"
}
```

### Errors

- 400 `invalid_request_error`: `routine_run_id` is not a routine run id. `error_code` is `validation_error`.
- 401 `authentication_error`: The key is missing, removed, or not a key. Send it in `x-api-key`.
- 404 `not_found_error`: No routine run in the key's workspace has this id (`Routine run not found.`).
- 429 `rate_limit_error`: The workspace made too many requests this minute. Wait the seconds in `Retry-After`, then try again.
