Skip to content
Get API Key

Concepts

Routines

A routine is work that repeats by itself, giving its worker a new task each time it runs.

Updated
View as Markdown

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.

JSON
{
  "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 with POST /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_reason is then { "type": "error", "error": { "type" } } with worker_archived_error, password_set_not_found_error (a set it names was deleted) or password_set_archived_error (a set it names was archived). When a person paused it, paused_reason is { "type": "manual" }. Unpausing a routine that names an archived set is 400 until you change its password_set_ids.
  • Change with POST /routines/{routine_id}. Each field you send replaces the routine's. null clears description, schedule and budget, and metadata merges key by key. To avoid overwriting another change, send the version you read. If the routine changed since, the answer is 409 with error_code schedule_conflict and a current_version. Without version, 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 is 409 for 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.

Type a few words, like “give a task” or “errors”.