Concepts
Routines
A routine is work that repeats by itself, giving its worker a new task each time it runs.
A routine is work you want done again and again, without giving the task each time. It names a worker, says what each new task starts with, and says what makes it run: a schedule, another start, or you by hand. Each time it runs, the worker gets a task. To read the work, use the task routes.
A routine is a deployment in Claude Managed Agents. This page explains what a routine holds, what starts it, what each run records, and the limits.
What a routine holds
| Field | What it is |
|---|---|
worker |
The worker that gets each task. Give its id, or { "type": "worker", "id", "version" } to pin a version. Read back, it shows the version. |
name |
1 to 256 characters. |
description |
Up to 2,048 characters, or null. |
initial_events |
What each new task starts with: one or more user.message events with text blocks, and at most one user.define_outcome. The lines of a text rubric become each task's Done Means, up to 8 lines of up to 200 characters. A routine holds 1 to 50 events. |
schedule |
When it runs on its own, or null. |
triggers |
Other starts, beside the schedule. |
budget |
The budget copied onto each task. null gives each task the default of $5.00. |
resources |
Files, repositories and memory stores copied onto each task. They follow the rules of a task's resources. |
password_set_ids |
The password sets each task signs in with, in order. Left out, a routine reads back the worker's own set and the workspace's shared set. |
metadata |
Your own labels. |
status |
active or paused. |
paused_reason |
Why it is paused, or null. |
version |
A whole number that goes up with each change. |
archived_at |
When it was archived, or null. An archived routine reads active with this set. |
What makes it run
A routine runs from a schedule, from a trigger, or when you run it by hand with POST /routines/{routine_id}/run. A routine with no schedule and no triggers runs only by hand.
A schedule
A schedule is { "type": "cron", "expression", "timezone" }: a five-field cron expression and an IANA time zone, such as America/Chicago. @every 2h works too. An expression holds up to 256 characters. A time zone that does not exist is 400 with error_code invalid_timezone.
Runs must be at least 5 minutes apart. A tighter expression is 400.
A schedule reads back with last_run_at and upcoming_runs_at, the next 5 exact times it will run. Each start waits a fixed delay, the same for every run of one routine. The delay is somewhere from 0 up to 15 percent of the gap between runs, in a window of at least 5 seconds and at most 9 minutes. So a daily routine can start a few minutes after its time.
Some times do not go as planned:
- A time the clock skips. When a time change skips a clock time, nothing runs. The firing is recorded with
nonexistent_local_time_error. - A time the clock repeats. It runs once.
- Little Worker was down. The run starts when it is back, if that is within an hour of the planned time. Later than that, nothing runs, and the firing is recorded with
more_than_one_hour_late_error. - The worker is busy. If the worker has other work in hand or waiting, nothing runs, and the firing is recorded with
worker_busy_error. The next planned time runs as usual. - The routine is paused. Runs missed while it was paused are not made up.
Triggers
triggers is a list of other starts. Each type may appear once.
| Trigger | When it starts the routine |
|---|---|
{ "type": "webhook" } |
A request arrives at the routine's webhook. A person makes the webhook's key in the app, because this API has no key for it yet. |
{ "type": "email", "inbox" } |
An email arrives at one of the workspace's inboxes. |
{ "type": "event", "app", "event" } |
An app sends the named event. |
What each run records
Every firing is a routine run, whether it made a task or not. A routine run has an id that starts with routine_run_, and exactly one of task_id and error is set.
{
"type": "routine_run",
"id": "routine_run_0e5a9c3d7f1b4d28a6c4b0e9d3f7a512",
"routine_id": "routine_6d2b8f4a1c7e4a39b5d0e8f3a2c91b67",
"worker": { "type": "worker", "id": "worker_bf5ff997fca044b38dc1cc9ea5b60100" },
"trigger_context": { "type": "schedule", "scheduled_at": "2026-10-26T13:00:00.000Z" },
"task_id": "task_01M5TTRJKWYAK3XHFX432K5MBV",
"error": null,
"created_at": "2026-10-26T13:03:12.418Z"
}trigger_context says what started it: schedule with scheduled_at, manual, webhook, email with the inbox, or event with the app and event.
When a firing made no task, error is { "type", "message" }. type is one of these:
error.type |
When it happens |
|---|---|
worker_archived_error |
The worker was archived. |
worker_busy_error |
The worker was busy with other work, or its queue was full. |
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 planned time. |
routine_paused_error |
The routine was paused. |
superseded_edit_error |
An edit replaced the planned time. |
unknown_error |
Something else failed. |
A firing that made a task gives the worker a task like any other. The task's routine_id names the routine. Read and follow it with the task routes, which Tasks explains. It carries the routine's budget, so one task never spends more than that.
GET /routine_runs lists the runs of all your routines, newest first, up to 1,000 a page. Filter by routine_id, by has_error (true or false), or by trigger_type (schedule, manual, webhook, email or event). GET /routine_runs/{routine_run_id} reads one.
Pause, change and archive
- Pause with
POST /routines/{routine_id}/pause. A paused routine stops its schedule and its triggers, but you can still run it by hand. Unpause withPOST /routines/{routine_id}/unpause. The next run is planned from that moment, so missed times are not made up. Pausing does not stop a task the routine already started. Stop that task like any other. - Pause by itself. A routine pauses on its own when a firing fails in a way that will not clear.
paused_reasonis then{ "type": "error", "error": { "type" } }withworker_archived_error,password_set_not_found_error(a set it names was deleted) orpassword_set_archived_error(a set it names was archived). When a person paused it,paused_reasonis{ "type": "manual" }. Unpausing a routine that names an archived set is400until you change itspassword_set_ids. - Change with
POST /routines/{routine_id}. Each field you send replaces the routine's.nullclearsdescription,scheduleandbudget, andmetadatamerges key by key. To avoid overwriting another change, send theversionyou read. If the routine changed since, the answer is409witherror_codeschedule_conflictand acurrent_version. Withoutversion, the last write wins. - Archive with
POST /routines/{routine_id}/archive. It is one way: the routine stays readable and never fires again. An archived routine is409for a change, a pause, an unpause or a run. There is no routine delete. Archiving a worker archives its routines.
Send an Idempotency-Key with a create, a change, an archive, a pause, an unpause or a run, and a retry does the work once.
Limits
| What | Limit |
|---|---|
| Routines in a workspace | 1,000. A hosted plan may allow fewer, or ask for a longer gap between runs. Archived routines do not count. |
| Routines for one worker | 50 |
| Gap between runs | At least 5 minutes |
| Name | 256 characters |
| Description | 2,048 characters |
| Schedule expression | 256 characters |
initial_events |
1 to 50 events |
| Budget for each task | $0.01 to $1,000.00 |
A routine past the workspace limit or the worker limit is 409 with error.type conflict_error. Archive a routine to make room.
Next steps
- Tasks: what each task a routine makes does, and how to read its result.
- Password sets: the sets a routine's tasks sign in with, and what happens when one is archived.
- Memory: stores a routine can hand to each task, so work builds on the last run.