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

# Publish a routine version

Compiles the definition and publishes it as a new immutable version that becomes active at once: triggers switch atomically, runs already in progress keep the version they started with. `{routine}` is an ID or a name; a name that does not exist creates the routine (`201`), which makes `b3os routine deploy` idempotent. The definition's `name` must match a name in the path. The publishing key becomes the routine's `run_as` identity and its scopes are snapshotted. Hook IDs stay the same across versions for the same trigger ID. Pack triggers (`type: action`) are subscriptions: publishing creates or updates one trigger subscription (`tsub_`, read-only at `GET /v1/actions/subscriptions`) per action trigger, and removes the subscriptions of action triggers the new version no longer has. 

` PUT /v1/routines/{routine} `

Publish a routine version

Compiles the definition and publishes it as a new immutable version that becomes active at once: triggers
switch atomically, runs already in progress keep the version they started with. `{routine}` is an ID or a
name; a name that does not exist creates the routine (`201`), which makes `b3os routine deploy` idempotent.
The definition's `name` must match a name in the path. The publishing key becomes the routine's `run_as`
identity and its scopes are snapshotted. Hook IDs stay the same across versions for the same trigger ID.
Pack triggers (`type: action`) are subscriptions: publishing creates or updates one trigger subscription
(`tsub_`, read-only at `GET /v1/actions/subscriptions`) per action trigger, and removes the subscriptions of
action triggers the new version no longer has.


## Servers

- ` https://beta-api.b3os.org ` — API (API keys and CLI tokens)

- ` https://beta.b3os.org ` — Dashboard (session cookie and CSRF header; same paths)

## Authentication

The operation accepts these alternatives. Schemes within one alternative are required together.

### Alternative 1

- ` apiKey `: ` http `

  - HTTP scheme: ` bearer ` (b3os_sk_ API key or b3os_pat_ token)

  - `Authorization: Bearer <credential>`. Two credential kinds are accepted:

- **API keys** `b3os_sk_<32 base32>_<crc32>`, created in the dashboard or with `POST /v1/keys`. A key belongs to
  one project, has scopes, an optional spend cap and expiry, and is shown only once.
- **CLI tokens** `b3os_pat_<32 base32>_<crc32>`, issued by the device login (`b3os login`). They act for one
  user in the organization and project chosen at approval.

Keys are stored as SHA-256 hashes. The edge exchanges them for a 60-second node token before forwarding
a request to a node, so nodes never see your key.


### Alternative 2

- ` session `: ` apiKey `

  - Credential: ` __Host-b3os_session ` in ` cookie `

  - The dashboard session cookie on `beta.b3os.org` (host-only, `Secure`, `HttpOnly`). Requests other than
`GET`/`HEAD` must also send `x-b3os-csrf: <csrf_token>` from `GET /v1/auth/session`; SameSite is not relied
on. The cookie is never sent to `beta-api.b3os.org` or `usercontent.b3os.org`.


## Parameters

### ` routine ` (path, required)

A routine ID (`rtn_…`) or name. Names are unique per project; `runs`, `hooks`, `validate` and `import` are
reserved and cannot be routine names.


- ` value `: type ` string `

  - pattern: ` "^(rtn_[0-9a-hjkmnp-tv-z]{26}|[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?)$" `

Example:

```json
"nightly-digest"
```

### ` Idempotency-Key ` (header, optional)

A unique value (for example a UUID) that makes this request safe to retry. Scoped to your project for 24
hours: a retry with the same key, method, path and body returns the stored response with
`Idempotent-Replayed: true`, and only to the same API key. Reusing the key for a different request returns
`409 idempotency_key_conflict`.


- ` value `: type ` string `

  - minLength: ` 1 `

  - maxLength: ` 255 `

## Request body (required)

Content type: ` application/json `

- ` value `: type ` object ` — A routine as sent to PUT /v1/routines/{id} (ARCHITECTURE 5.4). GitHub-Actions-shaped: triggers, defaults and an ordered list of steps; expressions are CEL inside ${{ }}. Steps run in array order unless a step lists needs. Contexts, status functions, template rules and the divergences from GitHub Actions are in packages/spec/schemas/README.md. Compile-time rules JSON Schema cannot express (unique ids, needs pointing at earlier steps, references only to ancestors, retry forbidden on irreversible steps) are listed there under 'Routine publish checks'. Owner after freeze: routines.

  - Title: B3OS routine definition, DSL v1

  - Required fields: ` name `, ` steps `

  - ` $schema `: type ` string `; format ` uri ` — Optional pointer to this schema for editors.

  - ` name `: type ` string ` — Routine slug, unique within the project. Used by the CLI (b3os routine deploy); an MCP server that lists routines as tools names the tool routine_<name> with - mapped to _. runs, hooks, validate and import are reserved: they are API paths under /v1/routines.

    - minLength: ` 1 `

    - maxLength: ` 64 `

    - pattern: ` "^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$" `

  - ` description `: type ` string ` — What the routine does; an MCP server that lists routines as tools uses it as the tool description.

    - maxLength: ` 2000 `

  - ` inputs `: type ` unknown ` — JSON Schema 2020-12 for the run input, available as inputs. Defaults declared here are applied before the run starts. Absent: the run takes no input ({}).

    - ` allOf constraint 1 `: type ` unknown `

      - ` allOf constraint 1 `: type ` unknown ` — A JSON Schema (draft 2020-12) document, such as a routine's `inputs` or an action's `inputSchema`. B3OS checks it against the meta-schema when it is published. References to the meta-schema in `schemas/*.json` resolve here. 

        - ` anyOf alternative 1 `: type ` object `

        - ` anyOf alternative 2 `: type ` boolean `

      - ` allOf constraint 2 `: type ` object `

        - Required fields: ` type `

        - ` type `: type ` unknown `

          - enum: ` ["object"] `

  - ` output `: type ` unknown ` — The run's result, evaluated once after every step has finished and the run succeeded. Absent: the result is {}. Context: inputs, vars, env, trigger, run, steps (all).

    - ` allOf constraint 1 `: type ` unknown ` — Any JSON value; strings inside it at any depth are template strings, object keys are literal.

  - ` env `: type ` object ` — Non-secret strings evaluated once at run start (context: inputs, vars, trigger, run) and exposed as env.<NAME>. Not passed to sandboxes implicitly; put what a step needs in its with.

  - ` vars `: type ` object ` — Read-only JSON constants exposed as vars.<name>. Values are literals; strings in them are not evaluated as templates.

  - ` triggers `: type ` array ` — How runs start besides POST /v1/routines/{id}/runs. Triggers switch atomically when a new version is published. Action triggers are trigger subscriptions (tsub_): publishing a version creates or updates them, and a rollback or delete removes them (README, 'Pack triggers').

    - ` array item `: type ` object ` — trigger context by type. cron and interval: {type, id, occurrence_id, scheduled_at, fired_at}. webhook: {type, id, hook_id, delivery_id, received_at, method, headers (lowercase map), query (map), body (parsed JSON when the request is JSON, else string)}. action: {type, id, subscription_id, event_id, event_type, source, time, data (the pack trigger's event data, valid against its eventSchema)}. Runs started through the API: {type: api}; sub-routine runs: {type: routine, parent_run_id, parent_step_id}.

      - Required fields: ` type `, ` id `

      - ` type `: type ` unknown `

        - enum: ` ["cron","interval","webhook","action"] `

      - ` id `: type ` string ` — A CEL identifier (so steps.<id>.outputs parses): lowercase letters, digits and _, not a CEL reserved word.

        - minLength: ` 1 `

        - maxLength: ` 64 `

        - pattern: ` "^[a-z_][a-z0-9_]{0,63}$" `

      - ` uses `: type ` unknown ` — action only: the pack trigger to subscribe to.

        - ` allOf constraint 1 `: type ` string ` — A pack trigger: <namespace>/<pack>/<trigger>@<semver>, or @<major>, which is resolved to the newest matching release when the version is published and pinned in it (and in the trigger subscription).

          - maxLength: ` 256 `

          - pattern: ` "^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+/[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?/[a-z][a-z0-9]*(-[a-z0-9]+)*@((0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)?|[1-9][0-9]*)$" `

      - ` with `: type ` unknown ` — cron, interval, webhook: the run input built from the event (context: trigger, vars), validated against inputs; absent: {} plus input defaults. action: the pack trigger's configuration, validated against its configSchema and evaluated once when the version is published (context: vars only); action runs start with {} plus input defaults and read the event from trigger.data.

        - ` allOf constraint 1 `: type ` object ` — An object of template values.

      - ` connections `: type ` object ` — action only: binds the pack trigger's connection keys (its requires.connections) to vault connections. A key left out is resolved when the version is published (the most recently created connection for that pack the publishing key may use) and pinned in the subscription; when there is none the publish fails with routine_invalid.

      - ` if `: type ` unknown ` — Start a run only when true (context: trigger, vars). A webhook delivery that does not match is acknowledged with 202 and starts nothing; an action event that does not match is dropped. Status functions are not available here.

        - ` allOf constraint 1 `: type ` string ` — A CEL bool expression, bare or wrapped in one ${{ }} (both forms are accepted, as in GitHub's if). Without a call to success(), failure() or always(), the condition is implicitly success() && (<expression>).

          - minLength: ` 1 `

          - maxLength: ` 4096 `

      - ` cron `: type ` string ` — Six fields with seconds: second minute hour day-of-month month day-of-week, single spaces. Numbers, names (JAN, MON), *, a-b, a,b, */n and ? are accepted; L, W and # in the day fields.

        - maxLength: ` 128 `

        - pattern: ` "^[0-9A-Za-z*/,?#-]+( [0-9A-Za-z*/,?#-]+){5}$" `

      - ` tz `: type ` string ` — IANA zone for cron, default UTC. A local time skipped by a DST change fires at the first instant after the gap; a local time that occurs twice fires once, at its first occurrence.

        - maxLength: ` 64 `

        - pattern: ` "^(UTC|[A-Za-z]+(/[A-Za-z0-9_+-]+){1,2})$" `

      - ` every_ms `: type ` integer ` — Interval period. Plans may raise the floor (GET /v1/limits).

        - minimum: ` 1000 `

        - maximum: ` 2592000000 `

      - ` anchor `: type ` unknown ` — interval: epoch fires at Unix-epoch multiples of every_ms (clock aligned); publish fires every_ms after the version became active, then every every_ms.

        - enum: ` ["epoch","publish"] `

        - default: ` "epoch" `

      - ` catch_up `: type ` unknown ` — Occurrences missed while the node was down or the routine was paused: skip runs none, latest runs only the newest one, all runs each one in order. Only occurrences inside catch_up_window_s count. Required on every schedule (ARCHITECTURE 8).

        - enum: ` ["skip","latest","all"] `

      - ` catch_up_window_s `: type ` integer `

        - default: ` 86400 `

        - minimum: ` 1 `

        - maximum: ` 2592000 `

      - ` overlap `: type ` unknown ` — When an occurrence fires while a run started by this trigger is still active: skip drops it, buffer_one keeps at most one pending occurrence (a newer one replaces it), allow starts it anyway, cancel_running cancels the active run first. defaults.concurrency still applies on top.

        - enum: ` ["skip","buffer_one","allow","cancel_running"] `

        - default: ` "skip" `

      - ` verify `: type ` object ` — Mandatory verification for POST /v1/routines/hooks/{hk_id} (ARCHITECTURE 4.3); the hk_ id is created when the version is published. b3os: B3OS-Signature 't=<unix>,v1=<hex HMAC-SHA256(secret, "<t>.<body>")>' with a per-hook secret B3OS generates (shown once by the API). github, stripe, slack, standard_webhooks: the provider's scheme as in b3pack verify. hmac_sha256: generic, and always timestamped: the signature covers '<value of timestamp_header>.<body>', and a delivery whose timestamp header is missing or not a Unix time is refused. Timestamped schemes reject deliveries more than tolerance_s (at most 300 s) from the node clock. Secrets never appear in the URL.

        - Required fields: ` scheme `

        - ` scheme `: type ` unknown `

          - enum: ` ["b3os","github","stripe","slack","standard_webhooks","hmac_sha256"] `

        - ` secret `: type ` string ` — An api_key vault connection whose api_key field holds the provider's signing secret (vault.yaml field names). Store it as a verification-only connection, without pack and without hosts: B3OS reads it to check signatures, and a connection with no hosts is never injected into any request. Not used with b3os.

          - pattern: ` "^conn_[0-9a-hjkmnp-tv-z]{26}$" `

        - ` header `: type ` string `

          - pattern: `` "^[A-Za-z0-9!#$%&'*+.^_`|~-]{1,64}$" ``

        - ` encoding `: type ` unknown `

          - enum: ` ["hex","base64"] `

        - ` prefix `: type ` string `

          - maxLength: ` 32 `

          - pattern: ` "^[ -~]*$" `

        - ` timestamp_header `: type ` string ` — hmac_sha256 only, and required there: header carrying the delivery's Unix timestamp.

          - pattern: `` "^[A-Za-z0-9!#$%&'*+.^_`|~-]{1,64}$" ``

        - ` tolerance_s `: type ` integer `

          - default: ` 300 `

          - minimum: ` 30 `

          - maximum: ` 300 `

      - ` dedupe `: type ` unknown ` — webhook: string key; a second delivery with the same key within 24 h returns the first delivery's response and starts nothing. Default per scheme: github X-GitHub-Delivery, stripe body.id, slack body.event_id, standard_webhooks webhook-id, b3os the Idempotency-Key header or else the v1 signature, hmac_sha256 none (then dedupe is required).

        - ` allOf constraint 1 `: type ` string ` — Exactly one CEL expression wrapped as the whole string: ${{ <cel> }}. Keeps its CEL type.

          - minLength: ` 6 `

          - maxLength: ` 4096 `

          - pattern: ` "^\\$\\{\\{[\\s\\S]*\\}\\}$" `

  - ` defaults `: type ` object `

    - ` durability `: type ` unknown ` — ephemeral: memory only, nothing journaled (the agent tool-call fast path; a crash loses the run). standard: group-commit journal, effects durable before dependents run. strict: intent and result commits for every effect step (ARCHITECTURE 4.9, 5.4). A run may request a different tier at start.

      - enum: ` ["ephemeral","standard","strict"] `

      - default: ` "standard" `

    - ` timeout_ms `: type ` integer ` — Default timeout for uses and map steps that do not set timeout_ms. Wait and sleep steps are not affected.

      - default: ` 30000 `

      - minimum: ` 1 `

      - maximum: ` 86400000 `

    - ` retry `: type ` unknown ` — Default retry policy, applied only to steps whose effect class is pure or read. A write step retries only when it sets retry itself; an irreversible step never retries.

      - ` allOf constraint 1 `: type ` object ` — Attempt n (n >= 2) starts after min(max_ms, initial_ms * factor^(n-2)) ms; with jitter the delay is drawn uniformly from [delay/2, delay] using a PRNG seeded by (run id, step id, item index, attempt), so replay is deterministic. Only failures the callee marks retryable (and timeouts of pure or read steps) are retried. Every attempt of a step carries the same idempotency key.

        - Required fields: ` max_attempts `

        - ` max_attempts `: type ` integer ` — Total attempts including the first.

          - minimum: ` 1 `

          - maximum: ` 20 `

        - ` initial_ms `: type ` integer `

          - default: ` 200 `

          - minimum: ` 1 `

          - maximum: ` 3600000 `

        - ` factor `: type ` number `

          - default: ` 2 `

          - minimum: ` 1 `

          - maximum: ` 10 `

        - ` max_ms `: type ` integer `

          - default: ` 30000 `

          - minimum: ` 1 `

          - maximum: ` 86400000 `

        - ` jitter `: type ` boolean `

          - default: ` true `

    - ` concurrency `: type ` object ` — Admission for runs of this routine, shaped like GitHub's concurrency. At most limit runs are active per group; on_limit decides what a new run does when the group is full.

      - Required fields: ` limit `

      - ` limit `: type ` integer `

        - minimum: ` 1 `

        - maximum: ` 1000 `

      - ` group `: type ` unknown ` — Group key evaluated at run start (context: inputs, vars, trigger). Default: one group for the routine.

        - maxLength: ` 512 `

        - ` allOf constraint 1 `: type ` string ` — A string that may contain ${{ <cel> }} expressions (README, 'Templates').

          - maxLength: ` 65536 `

      - ` on_limit `: type ` unknown ` — queue: wait (FIFO) for a free slot. skip: the new run ends canceled with reason concurrency_skipped. cancel_running: cancel the oldest active run in the group, then start.

        - enum: ` ["queue","skip","cancel_running"] `

        - default: ` "queue" `

  - ` steps `: type ` array `

    - ` array item `: type ` object ` — Exactly one of uses (call), map (fan-out), wait (signal or time) or sleep_ms (delay). Without needs a step depends on the step before it in the same list (the first step has no dependency); needs: [] makes it a root that starts immediately. Ids are unique across the whole routine, including map bodies.

      - Required fields: ` id `

      - ` id `: type ` string ` — A CEL identifier (so steps.<id>.outputs parses): lowercase letters, digits and _, not a CEL reserved word.

        - minLength: ` 1 `

        - maxLength: ` 64 `

        - pattern: ` "^[a-z_][a-z0-9_]{0,63}$" `

      - ` name `: type ` string ` — Display label.

        - maxLength: ` 100 `

      - ` uses `: type ` string ` — <ref>@<ver>. First-party: vision/<name>@<major>, render/<name>@<major>, sandboxes/<name>@<major>, infer/<name>@<major>, core/http@1, core/transform@1, core/log@1 (core/map, core/wait and core/sleep are written as the map, wait and sleep_ms step fields). Sub-routine: routine/<rtn_id>@<ver_id>. Pack action: <namespace>/<pack>/<action>@<semver>, or @<major> which is resolved to the newest matching release when the version is published and pinned in it. The compiler checks that the callable exists and validates with against its input schema.

        - maxLength: ` 256 `

        - ` anyOf alternative 1 `: type ` unknown `

          - pattern: ` "^(vision|render|sandboxes|infer)/[a-z][a-z0-9_-]{0,63}@[1-9][0-9]{0,3}$" `

        - ` anyOf alternative 2 `: type ` unknown `

          - pattern: ` "^core/[a-z][a-z0-9_-]{0,63}@[1-9][0-9]{0,3}$" `

        - ` anyOf alternative 3 `: type ` unknown `

          - pattern: ` "^routine/rtn_[0-9a-hjkmnp-tv-z]{26}@ver_[0-9a-hjkmnp-tv-z]{26}$" `

        - ` anyOf alternative 4 `: type ` unknown `

          - pattern: ` "^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+/[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?/[a-z][a-z0-9]*(-[a-z0-9]+)*@((0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)?|[1-9][0-9]*)$" `

      - ` with `: type ` unknown ` — Input for the callable. Context: inputs, vars, env, trigger, run, steps (ancestors only), item and index inside a map body.

        - ` allOf constraint 1 `: type ` object ` — An object of template values.

      - ` connections `: type ` object ` — Pack action steps only: binds the pack's connection keys to vault connections. A key left out is resolved when the step runs with SecretStore::find_connection (the most recently created connection for that pack the run's principal may use); when there is none the step fails with connection_required. Bind explicitly when a project holds several.

      - ` map `: type ` object ` — Runs steps once per element of over. Inside the body, item is the element and index its position; steps.<id> of a body step refers to the current item. outputs of the map step: {items: [the last body step's outputs per element, null for failed or skipped items], outcomes: [outcome per element]}, both in input order. An item fails when a body step fails without continue_on_error; the first failed item cancels in-flight items and fails the map.

        - Required fields: ` over `, ` steps `

        - ` over `: type ` unknown ` — Must evaluate to a list (at most 10,000 elements).

          - ` allOf constraint 1 `: type ` string ` — Exactly one CEL expression wrapped as the whole string: ${{ <cel> }}. Keeps its CEL type.

            - minLength: ` 6 `

            - maxLength: ` 4096 `

            - pattern: ` "^\\$\\{\\{[\\s\\S]*\\}\\}$" `

        - ` concurrency `: type ` integer `

          - default: ` 8 `

          - minimum: ` 1 `

          - maximum: ` 256 `

        - ` steps `: type ` array `

          - ` array item `: type ` object `; reference ` #/components/schemas/RoutineDefinitionStep ` — (circular reference)

      - ` wait `: type ` object ` — Blocks the step without holding resources. {signal}: wait for POST /v1/routines/runs/{run_id}/signal with that name (signals sent before the step starts are buffered, at most 16 per signal name per run, and consumed in order). {signal, ms} or {signal, until}: give up at the deadline. {until}: wait for a time. outputs for signal waits: {signal, payload, received_at, timed_out}; a deadline is not a failure: timed_out is true and signal, payload and received_at are null. outputs for {until}: {fired_at}.

        - ` signal `: type ` string `

          - pattern: ` "^[a-z][a-z0-9_.-]{0,63}$" `

        - ` until `: type ` unknown ` — RFC 3339 timestamp, literal or produced by an expression.

          - maxLength: ` 4096 `

          - ` allOf constraint 1 `: type ` string ` — A string that may contain ${{ <cel> }} expressions (README, 'Templates').

            - maxLength: ` 65536 `

        - ` ms `: type ` unknown ` — Signal waits only: maximum wait.

          - ` allOf constraint 1 `: type ` unknown ` — Milliseconds: a literal integer or one CEL expression that evaluates to an int.

            - ` oneOf alternative 1 `: type ` integer `

              - minimum: ` 1 `

              - maximum: ` 31536000000 `

            - ` oneOf alternative 2 `: type ` string ` — Exactly one CEL expression wrapped as the whole string: ${{ <cel> }}. Keeps its CEL type.

              - minLength: ` 6 `

              - maxLength: ` 4096 `

              - pattern: ` "^\\$\\{\\{[\\s\\S]*\\}\\}$" `

        - ` anyOf alternative 1 `: type ` unknown `

          - Required fields: ` signal `

        - ` anyOf alternative 2 `: type ` unknown `

          - Required fields: ` until `

      - ` sleep_ms `: type ` unknown ` — Durable delay; outputs {}.

        - ` allOf constraint 1 `: type ` unknown ` — Milliseconds: a literal integer or one CEL expression that evaluates to an int.

          - ` oneOf alternative 1 `: type ` integer `

            - minimum: ` 1 `

            - maximum: ` 31536000000 `

          - ` oneOf alternative 2 `: type ` string ` — Exactly one CEL expression wrapped as the whole string: ${{ <cel> }}. Keeps its CEL type.

            - minLength: ` 6 `

            - maxLength: ` 4096 `

            - pattern: ` "^\\$\\{\\{[\\s\\S]*\\}\\}$" `

      - ` needs `: type ` array ` — Ids of earlier steps in the same list that must finish first.

        - ` array item `: type ` string ` — A CEL identifier (so steps.<id>.outputs parses): lowercase letters, digits and _, not a CEL reserved word.

          - minLength: ` 1 `

          - maxLength: ` 64 `

          - pattern: ` "^[a-z_][a-z0-9_]{0,63}$" `

      - ` if `: type ` string ` — A CEL bool expression, bare or wrapped in one ${{ }} (both forms are accepted, as in GitHub's if). Without a call to success(), failure() or always(), the condition is implicitly success() && (<expression>).

        - minLength: ` 1 `

        - maxLength: ` 4096 `

      - ` retry `: type ` object ` — Attempt n (n >= 2) starts after min(max_ms, initial_ms * factor^(n-2)) ms; with jitter the delay is drawn uniformly from [delay/2, delay] using a PRNG seeded by (run id, step id, item index, attempt), so replay is deterministic. Only failures the callee marks retryable (and timeouts of pure or read steps) are retried. Every attempt of a step carries the same idempotency key.

        - Required fields: ` max_attempts `

        - ` max_attempts `: type ` integer ` — Total attempts including the first.

          - minimum: ` 1 `

          - maximum: ` 20 `

        - ` initial_ms `: type ` integer `

          - default: ` 200 `

          - minimum: ` 1 `

          - maximum: ` 3600000 `

        - ` factor `: type ` number `

          - default: ` 2 `

          - minimum: ` 1 `

          - maximum: ` 10 `

        - ` max_ms `: type ` integer `

          - default: ` 30000 `

          - minimum: ` 1 `

          - maximum: ` 86400000 `

        - ` jitter `: type ` boolean `

          - default: ` true `

      - ` timeout_ms `: type ` integer ` — uses steps: per attempt. map steps: the whole map.

        - minimum: ` 1 `

        - maximum: ` 86400000 `

      - ` continue_on_error `: type ` boolean ` — When the step fails, its outcome is failure but its conclusion is success, so dependents run and the run can still succeed.

        - default: ` false `

      - ` oneOf alternative 1 `: type ` unknown `

        - Required fields: ` uses `

      - ` oneOf alternative 2 `: type ` unknown `

        - Required fields: ` map `

      - ` oneOf alternative 3 `: type ` unknown `

        - Required fields: ` wait `

      - ` oneOf alternative 4 `: type ` unknown `

        - Required fields: ` sleep_ms `

Example:

```json
{
  "name": "nightly-digest",
  "description": "Scrape the news page and post a summary",
  "triggers": [
    {
      "type": "cron",
      "id": "nightly",
      "cron": "0 0 6 * * *",
      "tz": "America/Los_Angeles",
      "catch_up": "latest"
    },
    {
      "type": "webhook",
      "id": "inbound",
      "verify": {
        "scheme": "b3os"
      }
    }
  ],
  "steps": [
    {
      "id": "page",
      "uses": "vision/scrape@1",
      "with": {
        "url": "https://news.example.com"
      }
    }
  ]
}
```

## Responses

### ` 200 ` — A new version of an existing routine.

Content type: ` application/json `

- ` value `: type ` object `

  - Required fields: ` id `, ` object `, ` name `, ` description `, ` status `, ` paused_reason `, ` version `, ` triggers `, ` run_as `, ` created_at `, ` updated_at `, ` metadata `

  - ` id `: type ` string `

    - pattern: ` "^rtn_[0-9a-hjkmnp-tv-z]{26}$" `

  - ` object `: type ` string `

    - enum: ` ["routine"] `

  - ` name `: type ` string `

  - ` description `: type ` string `; nullable

  - ` status `: type ` string ` — `paused` when triggers are stopped; API runs still work unless `paused_reason` is `run_as_revoked`.

    - enum: ` ["active","paused"] `

  - ` paused_reason `: type ` string `; nullable

    - enum: ` ["manual","run_as_revoked",null] `

  - ` version `: type ` object `

    - Required fields: ` id `, ` number `, ` created_at `

    - ` id `: type ` string `

      - pattern: ` "^ver_[0-9a-hjkmnp-tv-z]{26}$" `

    - ` number `: type ` integer `

      - minimum: ` 1 `

    - ` created_at `: type ` string `; format ` date-time `

  - ` triggers `: type ` array `

    - ` array item `: type ` object `

      - Required fields: ` id `, ` type `, ` status `, ` next_fire_at `, ` hook `, ` subscription_id `

      - ` id `: type ` string ` — The trigger's `id` in the definition.

      - ` type `: type ` string `

        - enum: ` ["cron","interval","webhook","action"] `

      - ` status `: type ` string `

        - enum: ` ["active","paused"] `

      - ` next_fire_at `: type ` string `; nullable; format ` date-time ` — Next scheduled fire (cron and interval), or `null`.

      - ` hook `: type ` object `; nullable — The inbound URL of a webhook trigger, or `null`.

        - Required fields: ` id `, ` url `, ` verify `

        - ` id `: type ` string `

          - pattern: ` "^hk_[0-9a-hjkmnp-tv-z]{26}$" `

        - ` url `: type ` string `; format ` uri ` — Give this URL to the provider.

        - ` verify `: type ` string `

          - enum: ` ["b3os","github","stripe","slack","standard_webhooks","hmac_sha256"] `

        - ` secret `: type ` string ` — The `b3os` scheme's signing secret. Present only in the response of the publish that created the hook (and of a rotation).

          - pattern: ` "^whsec_[a-z2-7]{32}$" `

      - ` subscription_id `: type ` string `; nullable — The trigger subscription of an action trigger (`GET /v1/actions/subscriptions/{subscription_id}`), or `null`.

        - pattern: ` "^tsub_[0-9a-hjkmnp-tv-z]{26}$" `

  - ` run_as `: type ` object ` — The identity runs execute as (the key that published the active version, with its scopes at publish time).

    - Required fields: ` key `, ` scopes `

    - ` key `: type ` string `; nullable — The API key ID; `null` when a dashboard user published.

    - ` scopes `: type ` array `

      - ` array item `: type ` string `

  - ` created_at `: type ` string `; format ` date-time `

  - ` updated_at `: type ` string `; format ` date-time `

  - ` metadata `: type ` object ` — Up to 16 string key-value pairs you attach to an object for your own use. Keys are 1 to 40 characters of `A-Z a-z 0-9 _ . : -`; values are at most 500 characters. B3OS never interprets them. 

    - example: ` {"customer":"acme","ticket":"ENG-1423"} `

Response header ` x-request-id ` — The request ID (`req_…`). It is the `instance` of any problem and the key for `GET /v1/requests/{request_id}`.

- ` value `: type ` string `

  - pattern: ` "^req_[0-9a-hjkmnp-tv-z]{26}$" `

Response header ` Server-Timing ` — Phase durations in milliseconds (W3C Server Timing); the handler's phases break the request down. For a
response whose body size is known, `total` runs from reading the request head to writing the response head.
Streaming responses (event streams, WebSockets, chunked bodies) carry no `total` here: they report their
final phases in the terminal event (`job.succeeded`, `job.failed`, `job.canceled`) or the final job. The
time from the first request byte to the last response byte is recorded in the node's metrics and request
log (`GET /v1/requests/{request_id}`). The edge prepends `edge_auth` and `edge_upstream`.


- ` value `: type ` string `

Response header ` Idempotent-Replayed ` — Present with the value `true` when the response is a stored replay of an earlier request with the same `Idempotency-Key`.

- ` value `: type ` string `

  - enum: ` ["true"] `

Example:

```json
{
  "id": "rtn_01m5t6vhkw4a8fa4tb5w4ffz37",
  "object": "routine",
  "name": "nightly-digest",
  "description": "Scrape the news page and post a summary",
  "status": "active",
  "paused_reason": null,
  "version": {
    "id": "ver_01m5t6ncrsssrysy1p2fbn2pgf",
    "number": 2,
    "created_at": "2026-10-25T19:20:00.000Z"
  },
  "triggers": [
    {
      "id": "nightly",
      "type": "cron",
      "status": "active",
      "next_fire_at": "2026-10-26T13:00:00.000Z",
      "hook": null,
      "subscription_id": null
    },
    {
      "id": "inbound",
      "type": "webhook",
      "status": "active",
      "next_fire_at": null,
      "hook": {
        "id": "hk_01m5t3r2qwz23751t0x74bhahz",
        "url": "https://beta-api.b3os.org/v1/routines/hooks/hk_01m5t3r2qwz23751t0x74bhahz",
        "verify": "b3os",
        "secret": "whsec_ex4mple7ex4mple7ex4mple7ex4mple7"
      },
      "subscription_id": null
    }
  ],
  "run_as": {
    "key": "key_01m5t5gb92a5zncjx654gevx4p",
    "scopes": [
      "vision:run",
      "infer:run"
    ]
  },
  "created_at": "2026-10-25T19:10:00.000Z",
  "updated_at": "2026-10-25T19:20:00.000Z",
  "metadata": {}
}
```

### ` 201 ` — The routine did not exist and was created.

Content type: ` application/json `

- ` value `: type ` object `

  - Required fields: ` id `, ` object `, ` name `, ` description `, ` status `, ` paused_reason `, ` version `, ` triggers `, ` run_as `, ` created_at `, ` updated_at `, ` metadata `

  - ` id `: type ` string `

    - pattern: ` "^rtn_[0-9a-hjkmnp-tv-z]{26}$" `

  - ` object `: type ` string `

    - enum: ` ["routine"] `

  - ` name `: type ` string `

  - ` description `: type ` string `; nullable

  - ` status `: type ` string ` — `paused` when triggers are stopped; API runs still work unless `paused_reason` is `run_as_revoked`.

    - enum: ` ["active","paused"] `

  - ` paused_reason `: type ` string `; nullable

    - enum: ` ["manual","run_as_revoked",null] `

  - ` version `: type ` object `

    - Required fields: ` id `, ` number `, ` created_at `

    - ` id `: type ` string `

      - pattern: ` "^ver_[0-9a-hjkmnp-tv-z]{26}$" `

    - ` number `: type ` integer `

      - minimum: ` 1 `

    - ` created_at `: type ` string `; format ` date-time `

  - ` triggers `: type ` array `

    - ` array item `: type ` object `

      - Required fields: ` id `, ` type `, ` status `, ` next_fire_at `, ` hook `, ` subscription_id `

      - ` id `: type ` string ` — The trigger's `id` in the definition.

      - ` type `: type ` string `

        - enum: ` ["cron","interval","webhook","action"] `

      - ` status `: type ` string `

        - enum: ` ["active","paused"] `

      - ` next_fire_at `: type ` string `; nullable; format ` date-time ` — Next scheduled fire (cron and interval), or `null`.

      - ` hook `: type ` object `; nullable — The inbound URL of a webhook trigger, or `null`.

        - Required fields: ` id `, ` url `, ` verify `

        - ` id `: type ` string `

          - pattern: ` "^hk_[0-9a-hjkmnp-tv-z]{26}$" `

        - ` url `: type ` string `; format ` uri ` — Give this URL to the provider.

        - ` verify `: type ` string `

          - enum: ` ["b3os","github","stripe","slack","standard_webhooks","hmac_sha256"] `

        - ` secret `: type ` string ` — The `b3os` scheme's signing secret. Present only in the response of the publish that created the hook (and of a rotation).

          - pattern: ` "^whsec_[a-z2-7]{32}$" `

      - ` subscription_id `: type ` string `; nullable — The trigger subscription of an action trigger (`GET /v1/actions/subscriptions/{subscription_id}`), or `null`.

        - pattern: ` "^tsub_[0-9a-hjkmnp-tv-z]{26}$" `

  - ` run_as `: type ` object ` — The identity runs execute as (the key that published the active version, with its scopes at publish time).

    - Required fields: ` key `, ` scopes `

    - ` key `: type ` string `; nullable — The API key ID; `null` when a dashboard user published.

    - ` scopes `: type ` array `

      - ` array item `: type ` string `

  - ` created_at `: type ` string `; format ` date-time `

  - ` updated_at `: type ` string `; format ` date-time `

  - ` metadata `: type ` object ` — Up to 16 string key-value pairs you attach to an object for your own use. Keys are 1 to 40 characters of `A-Z a-z 0-9 _ . : -`; values are at most 500 characters. B3OS never interprets them. 

    - example: ` {"customer":"acme","ticket":"ENG-1423"} `

Response header ` x-request-id ` — The request ID (`req_…`). It is the `instance` of any problem and the key for `GET /v1/requests/{request_id}`.

- ` value `: type ` string `

  - pattern: ` "^req_[0-9a-hjkmnp-tv-z]{26}$" `

Response header ` Server-Timing ` — Phase durations in milliseconds (W3C Server Timing); the handler's phases break the request down. For a
response whose body size is known, `total` runs from reading the request head to writing the response head.
Streaming responses (event streams, WebSockets, chunked bodies) carry no `total` here: they report their
final phases in the terminal event (`job.succeeded`, `job.failed`, `job.canceled`) or the final job. The
time from the first request byte to the last response byte is recorded in the node's metrics and request
log (`GET /v1/requests/{request_id}`). The edge prepends `edge_auth` and `edge_upstream`.


- ` value `: type ` string `

Response header ` Idempotent-Replayed ` — Present with the value `true` when the response is a stored replay of an earlier request with the same `Idempotency-Key`.

- ` value `: type ` string `

  - enum: ` ["true"] `

Example:

```json
{
  "id": "rtn_01m5t47h7eka45aymrsf1rbgs9",
  "object": "routine",
  "name": "nightly-digest",
  "description": null,
  "status": "active",
  "paused_reason": null,
  "version": {
    "id": "ver_01m5t5z4xae12hr9s9r52tcf9b",
    "number": 1,
    "created_at": "2026-10-25T19:20:00.000Z"
  },
  "triggers": [],
  "run_as": {
    "key": "key_01m5t5gb92a5zncjx654gevx4p",
    "scopes": [
      "vision:run"
    ]
  },
  "created_at": "2026-10-25T19:20:00.000Z",
  "updated_at": "2026-10-25T19:20:00.000Z",
  "metadata": {}
}
```

### ` 400 ` — The request failed validation (`invalid_request`, `invalid_url`).

Content type: ` application/problem+json `

- ` value `: type ` object ` — An RFC 9457 problem. `code` is stable and listed in the errors catalog; `type` links to its entry. `detail` is human-readable and may change. Extension members appear only for the codes that document them; ignore members you do not recognize. 

  - Required fields: ` type `, ` title `, ` status `, ` code `, ` detail `

  - ` type `: type ` string `; format ` uri ` — `https://docs.beta.b3os.org/errors#<code>`.

    - example: ` "https://docs.beta.b3os.org/errors#invalid_request" `

  - ` title `: type ` string ` — Short summary of the problem type.

  - ` status `: type ` integer ` — The HTTP status code, repeated for convenience.

    - minimum: ` 400 `

    - maximum: ` 599 `

  - ` code `: type ` string ` — Stable machine-readable error code from the errors catalog.

    - pattern: ` "^[a-z][a-z0-9_]*$" `

  - ` detail `: type ` string ` — Explanation of this occurrence. Never contains secrets.

  - ` instance `: type ` string ` — The request ID (`req_…`) of the request that failed. Include it when you contact support.

    - example: ` "req_01m5t4yg394wegr3j4y01qb363" `

  - ` errors `: type ` array ` — Validation problems, one per field (`invalid_request` and the other codes that list `errors`).

    - ` array item `: type ` object ` — One validation problem.

      - Required fields: ` pointer `, ` detail `

      - ` pointer `: type ` string ` — Where the problem is: a JSON pointer into the request body (`/url`, `/steps/0/uses`), or `#query/<name>`, `#path/<name>` or `#header/<name>` for parameters. 

        - example: ` "/url" `

      - ` detail `: type ` string ` — What is wrong there.

        - example: ` "must be an absolute http or https URL" `

  - ` reason `: type ` string ` — Finer-grained cause for codes that document one (`unauthorized`, `forbidden`, `egress_denied`, `upstream_unreachable`, …).

  - ` required_scope `: type ` string ` — The scope that was missing (`forbidden` with `reason: scope`).

  - ` retry_after_s `: type ` integer ` — Same value as the `Retry-After` header, for clients that only read the body.

    - minimum: ` 0 `

  - ` limit `: type ` object ` — The limit an error refers to. IDs and current values are listed by `GET /v1/limits`.

    - Required fields: ` id `, ` limit `, ` unit `

    - ` id `: type ` string ` — Limit ID, such as `vision.scrape.rpm` or `sandboxes.concurrent`.

      - example: ` "vision.scrape.rpm" `

    - ` limit `: type ` number ` — The limit's value.

    - ` unit `: type ` string ` — What the limit counts, such as `requests`, `sandboxes`, `vcpu` or `MiB`.

    - ` window_s `: type ` integer ` — Window length in seconds for rate limits and quotas.

    - ` current `: type ` number ` — Current usage against the limit, when known.

  - ` current_status `: type ` string ` — The object's current status, for state conflicts such as `sandbox_not_running`.

  - ` upstream_status `: type ` integer ` — The HTTP status the upstream returned (`upstream_error`, `action_output_invalid`).

  - ` step `: type ` string ` — The failed step, as a step ID (routines), an index (`act`) or a path such as `beats/2/do/0` (render).

  - ` pack_error `: type ` object ` — The error a pack's error mapping chose (`upstream_error` from pack actions).

    - Required fields: ` code `, ` message `, ` retryable `

    - ` code `: type ` string `

    - ` message `: type ` string `

    - ` retryable `: type ` boolean `

Response header ` x-request-id ` — The request ID (`req_…`). It is the `instance` of any problem and the key for `GET /v1/requests/{request_id}`.

- ` value `: type ` string `

  - pattern: ` "^req_[0-9a-hjkmnp-tv-z]{26}$" `

Response header ` Server-Timing ` — Phase durations in milliseconds (W3C Server Timing); the handler's phases break the request down. For a
response whose body size is known, `total` runs from reading the request head to writing the response head.
Streaming responses (event streams, WebSockets, chunked bodies) carry no `total` here: they report their
final phases in the terminal event (`job.succeeded`, `job.failed`, `job.canceled`) or the final job. The
time from the first request byte to the last response byte is recorded in the node's metrics and request
log (`GET /v1/requests/{request_id}`). The edge prepends `edge_auth` and `edge_upstream`.


- ` value `: type ` string `

Example:

```json
{
  "type": "https://docs.beta.b3os.org/errors#invalid_request",
  "title": "Invalid request",
  "status": 400,
  "code": "invalid_request",
  "detail": "1 field failed validation",
  "instance": "req_01m5t4yg394wegr3j4y01qb363",
  "errors": [
    {
      "pointer": "/url",
      "detail": "must be an absolute http or https URL"
    }
  ]
}
```

### ` 401 ` — Missing, malformed, expired or revoked credentials (`unauthorized`).

Content type: ` application/problem+json `

- ` value `: type ` object ` — An RFC 9457 problem. `code` is stable and listed in the errors catalog; `type` links to its entry. `detail` is human-readable and may change. Extension members appear only for the codes that document them; ignore members you do not recognize. 

  - Required fields: ` type `, ` title `, ` status `, ` code `, ` detail `

  - ` type `: type ` string `; format ` uri ` — `https://docs.beta.b3os.org/errors#<code>`.

    - example: ` "https://docs.beta.b3os.org/errors#invalid_request" `

  - ` title `: type ` string ` — Short summary of the problem type.

  - ` status `: type ` integer ` — The HTTP status code, repeated for convenience.

    - minimum: ` 400 `

    - maximum: ` 599 `

  - ` code `: type ` string ` — Stable machine-readable error code from the errors catalog.

    - pattern: ` "^[a-z][a-z0-9_]*$" `

  - ` detail `: type ` string ` — Explanation of this occurrence. Never contains secrets.

  - ` instance `: type ` string ` — The request ID (`req_…`) of the request that failed. Include it when you contact support.

    - example: ` "req_01m5t4yg394wegr3j4y01qb363" `

  - ` errors `: type ` array ` — Validation problems, one per field (`invalid_request` and the other codes that list `errors`).

    - ` array item `: type ` object ` — One validation problem.

      - Required fields: ` pointer `, ` detail `

      - ` pointer `: type ` string ` — Where the problem is: a JSON pointer into the request body (`/url`, `/steps/0/uses`), or `#query/<name>`, `#path/<name>` or `#header/<name>` for parameters. 

        - example: ` "/url" `

      - ` detail `: type ` string ` — What is wrong there.

        - example: ` "must be an absolute http or https URL" `

  - ` reason `: type ` string ` — Finer-grained cause for codes that document one (`unauthorized`, `forbidden`, `egress_denied`, `upstream_unreachable`, …).

  - ` required_scope `: type ` string ` — The scope that was missing (`forbidden` with `reason: scope`).

  - ` retry_after_s `: type ` integer ` — Same value as the `Retry-After` header, for clients that only read the body.

    - minimum: ` 0 `

  - ` limit `: type ` object ` — The limit an error refers to. IDs and current values are listed by `GET /v1/limits`.

    - Required fields: ` id `, ` limit `, ` unit `

    - ` id `: type ` string ` — Limit ID, such as `vision.scrape.rpm` or `sandboxes.concurrent`.

      - example: ` "vision.scrape.rpm" `

    - ` limit `: type ` number ` — The limit's value.

    - ` unit `: type ` string ` — What the limit counts, such as `requests`, `sandboxes`, `vcpu` or `MiB`.

    - ` window_s `: type ` integer ` — Window length in seconds for rate limits and quotas.

    - ` current `: type ` number ` — Current usage against the limit, when known.

  - ` current_status `: type ` string ` — The object's current status, for state conflicts such as `sandbox_not_running`.

  - ` upstream_status `: type ` integer ` — The HTTP status the upstream returned (`upstream_error`, `action_output_invalid`).

  - ` step `: type ` string ` — The failed step, as a step ID (routines), an index (`act`) or a path such as `beats/2/do/0` (render).

  - ` pack_error `: type ` object ` — The error a pack's error mapping chose (`upstream_error` from pack actions).

    - Required fields: ` code `, ` message `, ` retryable `

    - ` code `: type ` string `

    - ` message `: type ` string `

    - ` retryable `: type ` boolean `

Response header ` x-request-id ` — The request ID (`req_…`). It is the `instance` of any problem and the key for `GET /v1/requests/{request_id}`.

- ` value `: type ` string `

  - pattern: ` "^req_[0-9a-hjkmnp-tv-z]{26}$" `

Response header ` Server-Timing ` — Phase durations in milliseconds (W3C Server Timing); the handler's phases break the request down. For a
response whose body size is known, `total` runs from reading the request head to writing the response head.
Streaming responses (event streams, WebSockets, chunked bodies) carry no `total` here: they report their
final phases in the terminal event (`job.succeeded`, `job.failed`, `job.canceled`) or the final job. The
time from the first request byte to the last response byte is recorded in the node's metrics and request
log (`GET /v1/requests/{request_id}`). The edge prepends `edge_auth` and `edge_upstream`.


- ` value `: type ` string `

Example:

```json
{
  "type": "https://docs.beta.b3os.org/errors#unauthorized",
  "title": "Unauthorized",
  "status": 401,
  "code": "unauthorized",
  "detail": "API key checksum does not match; check that the whole key was copied",
  "instance": "req_01m5t4yg394wegr3j4y01qb363",
  "reason": "malformed"
}
```

### ` 403 ` — Authenticated but not allowed (`forbidden`, `egress_denied`).

Content type: ` application/problem+json `

- ` value `: type ` object ` — An RFC 9457 problem. `code` is stable and listed in the errors catalog; `type` links to its entry. `detail` is human-readable and may change. Extension members appear only for the codes that document them; ignore members you do not recognize. 

  - Required fields: ` type `, ` title `, ` status `, ` code `, ` detail `

  - ` type `: type ` string `; format ` uri ` — `https://docs.beta.b3os.org/errors#<code>`.

    - example: ` "https://docs.beta.b3os.org/errors#invalid_request" `

  - ` title `: type ` string ` — Short summary of the problem type.

  - ` status `: type ` integer ` — The HTTP status code, repeated for convenience.

    - minimum: ` 400 `

    - maximum: ` 599 `

  - ` code `: type ` string ` — Stable machine-readable error code from the errors catalog.

    - pattern: ` "^[a-z][a-z0-9_]*$" `

  - ` detail `: type ` string ` — Explanation of this occurrence. Never contains secrets.

  - ` instance `: type ` string ` — The request ID (`req_…`) of the request that failed. Include it when you contact support.

    - example: ` "req_01m5t4yg394wegr3j4y01qb363" `

  - ` errors `: type ` array ` — Validation problems, one per field (`invalid_request` and the other codes that list `errors`).

    - ` array item `: type ` object ` — One validation problem.

      - Required fields: ` pointer `, ` detail `

      - ` pointer `: type ` string ` — Where the problem is: a JSON pointer into the request body (`/url`, `/steps/0/uses`), or `#query/<name>`, `#path/<name>` or `#header/<name>` for parameters. 

        - example: ` "/url" `

      - ` detail `: type ` string ` — What is wrong there.

        - example: ` "must be an absolute http or https URL" `

  - ` reason `: type ` string ` — Finer-grained cause for codes that document one (`unauthorized`, `forbidden`, `egress_denied`, `upstream_unreachable`, …).

  - ` required_scope `: type ` string ` — The scope that was missing (`forbidden` with `reason: scope`).

  - ` retry_after_s `: type ` integer ` — Same value as the `Retry-After` header, for clients that only read the body.

    - minimum: ` 0 `

  - ` limit `: type ` object ` — The limit an error refers to. IDs and current values are listed by `GET /v1/limits`.

    - Required fields: ` id `, ` limit `, ` unit `

    - ` id `: type ` string ` — Limit ID, such as `vision.scrape.rpm` or `sandboxes.concurrent`.

      - example: ` "vision.scrape.rpm" `

    - ` limit `: type ` number ` — The limit's value.

    - ` unit `: type ` string ` — What the limit counts, such as `requests`, `sandboxes`, `vcpu` or `MiB`.

    - ` window_s `: type ` integer ` — Window length in seconds for rate limits and quotas.

    - ` current `: type ` number ` — Current usage against the limit, when known.

  - ` current_status `: type ` string ` — The object's current status, for state conflicts such as `sandbox_not_running`.

  - ` upstream_status `: type ` integer ` — The HTTP status the upstream returned (`upstream_error`, `action_output_invalid`).

  - ` step `: type ` string ` — The failed step, as a step ID (routines), an index (`act`) or a path such as `beats/2/do/0` (render).

  - ` pack_error `: type ` object ` — The error a pack's error mapping chose (`upstream_error` from pack actions).

    - Required fields: ` code `, ` message `, ` retryable `

    - ` code `: type ` string `

    - ` message `: type ` string `

    - ` retryable `: type ` boolean `

Response header ` x-request-id ` — The request ID (`req_…`). It is the `instance` of any problem and the key for `GET /v1/requests/{request_id}`.

- ` value `: type ` string `

  - pattern: ` "^req_[0-9a-hjkmnp-tv-z]{26}$" `

Response header ` Server-Timing ` — Phase durations in milliseconds (W3C Server Timing); the handler's phases break the request down. For a
response whose body size is known, `total` runs from reading the request head to writing the response head.
Streaming responses (event streams, WebSockets, chunked bodies) carry no `total` here: they report their
final phases in the terminal event (`job.succeeded`, `job.failed`, `job.canceled`) or the final job. The
time from the first request byte to the last response byte is recorded in the node's metrics and request
log (`GET /v1/requests/{request_id}`). The edge prepends `edge_auth` and `edge_upstream`.


- ` value `: type ` string `

Example:

```json
{
  "type": "https://docs.beta.b3os.org/errors#forbidden",
  "title": "Forbidden",
  "status": 403,
  "code": "forbidden",
  "detail": "This key lacks the scope sandboxes:write",
  "instance": "req_01m5t4yg394wegr3j4y01qb363",
  "reason": "scope",
  "required_scope": "sandboxes:write"
}
```

### ` 404 ` — No such routine in this project (`routine_not_found`).

Content type: ` application/problem+json `

- ` value `: type ` object ` — An RFC 9457 problem. `code` is stable and listed in the errors catalog; `type` links to its entry. `detail` is human-readable and may change. Extension members appear only for the codes that document them; ignore members you do not recognize. 

  - Required fields: ` type `, ` title `, ` status `, ` code `, ` detail `

  - ` type `: type ` string `; format ` uri ` — `https://docs.beta.b3os.org/errors#<code>`.

    - example: ` "https://docs.beta.b3os.org/errors#invalid_request" `

  - ` title `: type ` string ` — Short summary of the problem type.

  - ` status `: type ` integer ` — The HTTP status code, repeated for convenience.

    - minimum: ` 400 `

    - maximum: ` 599 `

  - ` code `: type ` string ` — Stable machine-readable error code from the errors catalog.

    - pattern: ` "^[a-z][a-z0-9_]*$" `

  - ` detail `: type ` string ` — Explanation of this occurrence. Never contains secrets.

  - ` instance `: type ` string ` — The request ID (`req_…`) of the request that failed. Include it when you contact support.

    - example: ` "req_01m5t4yg394wegr3j4y01qb363" `

  - ` errors `: type ` array ` — Validation problems, one per field (`invalid_request` and the other codes that list `errors`).

    - ` array item `: type ` object ` — One validation problem.

      - Required fields: ` pointer `, ` detail `

      - ` pointer `: type ` string ` — Where the problem is: a JSON pointer into the request body (`/url`, `/steps/0/uses`), or `#query/<name>`, `#path/<name>` or `#header/<name>` for parameters. 

        - example: ` "/url" `

      - ` detail `: type ` string ` — What is wrong there.

        - example: ` "must be an absolute http or https URL" `

  - ` reason `: type ` string ` — Finer-grained cause for codes that document one (`unauthorized`, `forbidden`, `egress_denied`, `upstream_unreachable`, …).

  - ` required_scope `: type ` string ` — The scope that was missing (`forbidden` with `reason: scope`).

  - ` retry_after_s `: type ` integer ` — Same value as the `Retry-After` header, for clients that only read the body.

    - minimum: ` 0 `

  - ` limit `: type ` object ` — The limit an error refers to. IDs and current values are listed by `GET /v1/limits`.

    - Required fields: ` id `, ` limit `, ` unit `

    - ` id `: type ` string ` — Limit ID, such as `vision.scrape.rpm` or `sandboxes.concurrent`.

      - example: ` "vision.scrape.rpm" `

    - ` limit `: type ` number ` — The limit's value.

    - ` unit `: type ` string ` — What the limit counts, such as `requests`, `sandboxes`, `vcpu` or `MiB`.

    - ` window_s `: type ` integer ` — Window length in seconds for rate limits and quotas.

    - ` current `: type ` number ` — Current usage against the limit, when known.

  - ` current_status `: type ` string ` — The object's current status, for state conflicts such as `sandbox_not_running`.

  - ` upstream_status `: type ` integer ` — The HTTP status the upstream returned (`upstream_error`, `action_output_invalid`).

  - ` step `: type ` string ` — The failed step, as a step ID (routines), an index (`act`) or a path such as `beats/2/do/0` (render).

  - ` pack_error `: type ` object ` — The error a pack's error mapping chose (`upstream_error` from pack actions).

    - Required fields: ` code `, ` message `, ` retryable `

    - ` code `: type ` string `

    - ` message `: type ` string `

    - ` retryable `: type ` boolean `

Response header ` x-request-id ` — The request ID (`req_…`). It is the `instance` of any problem and the key for `GET /v1/requests/{request_id}`.

- ` value `: type ` string `

  - pattern: ` "^req_[0-9a-hjkmnp-tv-z]{26}$" `

Response header ` Server-Timing ` — Phase durations in milliseconds (W3C Server Timing); the handler's phases break the request down. For a
response whose body size is known, `total` runs from reading the request head to writing the response head.
Streaming responses (event streams, WebSockets, chunked bodies) carry no `total` here: they report their
final phases in the terminal event (`job.succeeded`, `job.failed`, `job.canceled`) or the final job. The
time from the first request byte to the last response byte is recorded in the node's metrics and request
log (`GET /v1/requests/{request_id}`). The edge prepends `edge_auth` and `edge_upstream`.


- ` value `: type ` string `

Example:

```json
{
  "type": "https://docs.beta.b3os.org/errors#routine_not_found",
  "title": "Routine not found",
  "status": 404,
  "code": "routine_not_found",
  "detail": "No routine named nightly-digest in this project",
  "instance": "req_01m5t4yg394wegr3j4y01qb363"
}
```

### ` 409 ` — The name is in use (`routine_name_taken`), or the `Idempotency-Key` was used for a different request (`idempotency_key_conflict`) or its first request is still being processed (`idempotency_request_in_progress`, with `Retry-After`).

Content type: ` application/problem+json `

- ` value `: type ` object ` — An RFC 9457 problem. `code` is stable and listed in the errors catalog; `type` links to its entry. `detail` is human-readable and may change. Extension members appear only for the codes that document them; ignore members you do not recognize. 

  - Required fields: ` type `, ` title `, ` status `, ` code `, ` detail `

  - ` type `: type ` string `; format ` uri ` — `https://docs.beta.b3os.org/errors#<code>`.

    - example: ` "https://docs.beta.b3os.org/errors#invalid_request" `

  - ` title `: type ` string ` — Short summary of the problem type.

  - ` status `: type ` integer ` — The HTTP status code, repeated for convenience.

    - minimum: ` 400 `

    - maximum: ` 599 `

  - ` code `: type ` string ` — Stable machine-readable error code from the errors catalog.

    - pattern: ` "^[a-z][a-z0-9_]*$" `

  - ` detail `: type ` string ` — Explanation of this occurrence. Never contains secrets.

  - ` instance `: type ` string ` — The request ID (`req_…`) of the request that failed. Include it when you contact support.

    - example: ` "req_01m5t4yg394wegr3j4y01qb363" `

  - ` errors `: type ` array ` — Validation problems, one per field (`invalid_request` and the other codes that list `errors`).

    - ` array item `: type ` object ` — One validation problem.

      - Required fields: ` pointer `, ` detail `

      - ` pointer `: type ` string ` — Where the problem is: a JSON pointer into the request body (`/url`, `/steps/0/uses`), or `#query/<name>`, `#path/<name>` or `#header/<name>` for parameters. 

        - example: ` "/url" `

      - ` detail `: type ` string ` — What is wrong there.

        - example: ` "must be an absolute http or https URL" `

  - ` reason `: type ` string ` — Finer-grained cause for codes that document one (`unauthorized`, `forbidden`, `egress_denied`, `upstream_unreachable`, …).

  - ` required_scope `: type ` string ` — The scope that was missing (`forbidden` with `reason: scope`).

  - ` retry_after_s `: type ` integer ` — Same value as the `Retry-After` header, for clients that only read the body.

    - minimum: ` 0 `

  - ` limit `: type ` object ` — The limit an error refers to. IDs and current values are listed by `GET /v1/limits`.

    - Required fields: ` id `, ` limit `, ` unit `

    - ` id `: type ` string ` — Limit ID, such as `vision.scrape.rpm` or `sandboxes.concurrent`.

      - example: ` "vision.scrape.rpm" `

    - ` limit `: type ` number ` — The limit's value.

    - ` unit `: type ` string ` — What the limit counts, such as `requests`, `sandboxes`, `vcpu` or `MiB`.

    - ` window_s `: type ` integer ` — Window length in seconds for rate limits and quotas.

    - ` current `: type ` number ` — Current usage against the limit, when known.

  - ` current_status `: type ` string ` — The object's current status, for state conflicts such as `sandbox_not_running`.

  - ` upstream_status `: type ` integer ` — The HTTP status the upstream returned (`upstream_error`, `action_output_invalid`).

  - ` step `: type ` string ` — The failed step, as a step ID (routines), an index (`act`) or a path such as `beats/2/do/0` (render).

  - ` pack_error `: type ` object ` — The error a pack's error mapping chose (`upstream_error` from pack actions).

    - Required fields: ` code `, ` message `, ` retryable `

    - ` code `: type ` string `

    - ` message `: type ` string `

    - ` retryable `: type ` boolean `

Response header ` x-request-id ` — The request ID (`req_…`). It is the `instance` of any problem and the key for `GET /v1/requests/{request_id}`.

- ` value `: type ` string `

  - pattern: ` "^req_[0-9a-hjkmnp-tv-z]{26}$" `

Response header ` Server-Timing ` — Phase durations in milliseconds (W3C Server Timing); the handler's phases break the request down. For a
response whose body size is known, `total` runs from reading the request head to writing the response head.
Streaming responses (event streams, WebSockets, chunked bodies) carry no `total` here: they report their
final phases in the terminal event (`job.succeeded`, `job.failed`, `job.canceled`) or the final job. The
time from the first request byte to the last response byte is recorded in the node's metrics and request
log (`GET /v1/requests/{request_id}`). The edge prepends `edge_auth` and `edge_upstream`.


- ` value `: type ` string `

Example:

```json
{
  "type": "https://docs.beta.b3os.org/errors#routine_name_taken",
  "title": "Routine name taken",
  "status": 409,
  "code": "routine_name_taken",
  "detail": "A routine named nightly-digest already exists; PUT /v1/routines/nightly-digest publishes a new version",
  "instance": "req_01m5t4yg394wegr3j4y01qb363"
}
```

### ` 413 ` — The request body exceeds this operation's limit.

Content type: ` application/problem+json `

- ` value `: type ` object ` — An RFC 9457 problem. `code` is stable and listed in the errors catalog; `type` links to its entry. `detail` is human-readable and may change. Extension members appear only for the codes that document them; ignore members you do not recognize. 

  - Required fields: ` type `, ` title `, ` status `, ` code `, ` detail `

  - ` type `: type ` string `; format ` uri ` — `https://docs.beta.b3os.org/errors#<code>`.

    - example: ` "https://docs.beta.b3os.org/errors#invalid_request" `

  - ` title `: type ` string ` — Short summary of the problem type.

  - ` status `: type ` integer ` — The HTTP status code, repeated for convenience.

    - minimum: ` 400 `

    - maximum: ` 599 `

  - ` code `: type ` string ` — Stable machine-readable error code from the errors catalog.

    - pattern: ` "^[a-z][a-z0-9_]*$" `

  - ` detail `: type ` string ` — Explanation of this occurrence. Never contains secrets.

  - ` instance `: type ` string ` — The request ID (`req_…`) of the request that failed. Include it when you contact support.

    - example: ` "req_01m5t4yg394wegr3j4y01qb363" `

  - ` errors `: type ` array ` — Validation problems, one per field (`invalid_request` and the other codes that list `errors`).

    - ` array item `: type ` object ` — One validation problem.

      - Required fields: ` pointer `, ` detail `

      - ` pointer `: type ` string ` — Where the problem is: a JSON pointer into the request body (`/url`, `/steps/0/uses`), or `#query/<name>`, `#path/<name>` or `#header/<name>` for parameters. 

        - example: ` "/url" `

      - ` detail `: type ` string ` — What is wrong there.

        - example: ` "must be an absolute http or https URL" `

  - ` reason `: type ` string ` — Finer-grained cause for codes that document one (`unauthorized`, `forbidden`, `egress_denied`, `upstream_unreachable`, …).

  - ` required_scope `: type ` string ` — The scope that was missing (`forbidden` with `reason: scope`).

  - ` retry_after_s `: type ` integer ` — Same value as the `Retry-After` header, for clients that only read the body.

    - minimum: ` 0 `

  - ` limit `: type ` object ` — The limit an error refers to. IDs and current values are listed by `GET /v1/limits`.

    - Required fields: ` id `, ` limit `, ` unit `

    - ` id `: type ` string ` — Limit ID, such as `vision.scrape.rpm` or `sandboxes.concurrent`.

      - example: ` "vision.scrape.rpm" `

    - ` limit `: type ` number ` — The limit's value.

    - ` unit `: type ` string ` — What the limit counts, such as `requests`, `sandboxes`, `vcpu` or `MiB`.

    - ` window_s `: type ` integer ` — Window length in seconds for rate limits and quotas.

    - ` current `: type ` number ` — Current usage against the limit, when known.

  - ` current_status `: type ` string ` — The object's current status, for state conflicts such as `sandbox_not_running`.

  - ` upstream_status `: type ` integer ` — The HTTP status the upstream returned (`upstream_error`, `action_output_invalid`).

  - ` step `: type ` string ` — The failed step, as a step ID (routines), an index (`act`) or a path such as `beats/2/do/0` (render).

  - ` pack_error `: type ` object ` — The error a pack's error mapping chose (`upstream_error` from pack actions).

    - Required fields: ` code `, ` message `, ` retryable `

    - ` code `: type ` string `

    - ` message `: type ` string `

    - ` retryable `: type ` boolean `

Response header ` x-request-id ` — The request ID (`req_…`). It is the `instance` of any problem and the key for `GET /v1/requests/{request_id}`.

- ` value `: type ` string `

  - pattern: ` "^req_[0-9a-hjkmnp-tv-z]{26}$" `

Response header ` Server-Timing ` — Phase durations in milliseconds (W3C Server Timing); the handler's phases break the request down. For a
response whose body size is known, `total` runs from reading the request head to writing the response head.
Streaming responses (event streams, WebSockets, chunked bodies) carry no `total` here: they report their
final phases in the terminal event (`job.succeeded`, `job.failed`, `job.canceled`) or the final job. The
time from the first request byte to the last response byte is recorded in the node's metrics and request
log (`GET /v1/requests/{request_id}`). The edge prepends `edge_auth` and `edge_upstream`.


- ` value `: type ` string `

Example:

```json
{
  "type": "https://docs.beta.b3os.org/errors#payload_too_large",
  "title": "Payload too large",
  "status": 413,
  "code": "payload_too_large",
  "detail": "Request body is 1572864 bytes; this operation accepts at most 1048576",
  "instance": "req_01m5t4yg394wegr3j4y01qb363"
}
```

### ` 415 ` — The Content-Type is not accepted by this operation.

Content type: ` application/problem+json `

- ` value `: type ` object ` — An RFC 9457 problem. `code` is stable and listed in the errors catalog; `type` links to its entry. `detail` is human-readable and may change. Extension members appear only for the codes that document them; ignore members you do not recognize. 

  - Required fields: ` type `, ` title `, ` status `, ` code `, ` detail `

  - ` type `: type ` string `; format ` uri ` — `https://docs.beta.b3os.org/errors#<code>`.

    - example: ` "https://docs.beta.b3os.org/errors#invalid_request" `

  - ` title `: type ` string ` — Short summary of the problem type.

  - ` status `: type ` integer ` — The HTTP status code, repeated for convenience.

    - minimum: ` 400 `

    - maximum: ` 599 `

  - ` code `: type ` string ` — Stable machine-readable error code from the errors catalog.

    - pattern: ` "^[a-z][a-z0-9_]*$" `

  - ` detail `: type ` string ` — Explanation of this occurrence. Never contains secrets.

  - ` instance `: type ` string ` — The request ID (`req_…`) of the request that failed. Include it when you contact support.

    - example: ` "req_01m5t4yg394wegr3j4y01qb363" `

  - ` errors `: type ` array ` — Validation problems, one per field (`invalid_request` and the other codes that list `errors`).

    - ` array item `: type ` object ` — One validation problem.

      - Required fields: ` pointer `, ` detail `

      - ` pointer `: type ` string ` — Where the problem is: a JSON pointer into the request body (`/url`, `/steps/0/uses`), or `#query/<name>`, `#path/<name>` or `#header/<name>` for parameters. 

        - example: ` "/url" `

      - ` detail `: type ` string ` — What is wrong there.

        - example: ` "must be an absolute http or https URL" `

  - ` reason `: type ` string ` — Finer-grained cause for codes that document one (`unauthorized`, `forbidden`, `egress_denied`, `upstream_unreachable`, …).

  - ` required_scope `: type ` string ` — The scope that was missing (`forbidden` with `reason: scope`).

  - ` retry_after_s `: type ` integer ` — Same value as the `Retry-After` header, for clients that only read the body.

    - minimum: ` 0 `

  - ` limit `: type ` object ` — The limit an error refers to. IDs and current values are listed by `GET /v1/limits`.

    - Required fields: ` id `, ` limit `, ` unit `

    - ` id `: type ` string ` — Limit ID, such as `vision.scrape.rpm` or `sandboxes.concurrent`.

      - example: ` "vision.scrape.rpm" `

    - ` limit `: type ` number ` — The limit's value.

    - ` unit `: type ` string ` — What the limit counts, such as `requests`, `sandboxes`, `vcpu` or `MiB`.

    - ` window_s `: type ` integer ` — Window length in seconds for rate limits and quotas.

    - ` current `: type ` number ` — Current usage against the limit, when known.

  - ` current_status `: type ` string ` — The object's current status, for state conflicts such as `sandbox_not_running`.

  - ` upstream_status `: type ` integer ` — The HTTP status the upstream returned (`upstream_error`, `action_output_invalid`).

  - ` step `: type ` string ` — The failed step, as a step ID (routines), an index (`act`) or a path such as `beats/2/do/0` (render).

  - ` pack_error `: type ` object ` — The error a pack's error mapping chose (`upstream_error` from pack actions).

    - Required fields: ` code `, ` message `, ` retryable `

    - ` code `: type ` string `

    - ` message `: type ` string `

    - ` retryable `: type ` boolean `

Response header ` x-request-id ` — The request ID (`req_…`). It is the `instance` of any problem and the key for `GET /v1/requests/{request_id}`.

- ` value `: type ` string `

  - pattern: ` "^req_[0-9a-hjkmnp-tv-z]{26}$" `

Response header ` Server-Timing ` — Phase durations in milliseconds (W3C Server Timing); the handler's phases break the request down. For a
response whose body size is known, `total` runs from reading the request head to writing the response head.
Streaming responses (event streams, WebSockets, chunked bodies) carry no `total` here: they report their
final phases in the terminal event (`job.succeeded`, `job.failed`, `job.canceled`) or the final job. The
time from the first request byte to the last response byte is recorded in the node's metrics and request
log (`GET /v1/requests/{request_id}`). The edge prepends `edge_auth` and `edge_upstream`.


- ` value `: type ` string `

Example:

```json
{
  "type": "https://docs.beta.b3os.org/errors#unsupported_media_type",
  "title": "Unsupported media type",
  "status": 415,
  "code": "unsupported_media_type",
  "detail": "Expected Content-Type application/json",
  "instance": "req_01m5t4yg394wegr3j4y01qb363"
}
```

### ` 422 ` — The definition does not compile (`routine_invalid`).

Content type: ` application/problem+json `

- ` value `: type ` object ` — An RFC 9457 problem. `code` is stable and listed in the errors catalog; `type` links to its entry. `detail` is human-readable and may change. Extension members appear only for the codes that document them; ignore members you do not recognize. 

  - Required fields: ` type `, ` title `, ` status `, ` code `, ` detail `

  - ` type `: type ` string `; format ` uri ` — `https://docs.beta.b3os.org/errors#<code>`.

    - example: ` "https://docs.beta.b3os.org/errors#invalid_request" `

  - ` title `: type ` string ` — Short summary of the problem type.

  - ` status `: type ` integer ` — The HTTP status code, repeated for convenience.

    - minimum: ` 400 `

    - maximum: ` 599 `

  - ` code `: type ` string ` — Stable machine-readable error code from the errors catalog.

    - pattern: ` "^[a-z][a-z0-9_]*$" `

  - ` detail `: type ` string ` — Explanation of this occurrence. Never contains secrets.

  - ` instance `: type ` string ` — The request ID (`req_…`) of the request that failed. Include it when you contact support.

    - example: ` "req_01m5t4yg394wegr3j4y01qb363" `

  - ` errors `: type ` array ` — Validation problems, one per field (`invalid_request` and the other codes that list `errors`).

    - ` array item `: type ` object ` — One validation problem.

      - Required fields: ` pointer `, ` detail `

      - ` pointer `: type ` string ` — Where the problem is: a JSON pointer into the request body (`/url`, `/steps/0/uses`), or `#query/<name>`, `#path/<name>` or `#header/<name>` for parameters. 

        - example: ` "/url" `

      - ` detail `: type ` string ` — What is wrong there.

        - example: ` "must be an absolute http or https URL" `

  - ` reason `: type ` string ` — Finer-grained cause for codes that document one (`unauthorized`, `forbidden`, `egress_denied`, `upstream_unreachable`, …).

  - ` required_scope `: type ` string ` — The scope that was missing (`forbidden` with `reason: scope`).

  - ` retry_after_s `: type ` integer ` — Same value as the `Retry-After` header, for clients that only read the body.

    - minimum: ` 0 `

  - ` limit `: type ` object ` — The limit an error refers to. IDs and current values are listed by `GET /v1/limits`.

    - Required fields: ` id `, ` limit `, ` unit `

    - ` id `: type ` string ` — Limit ID, such as `vision.scrape.rpm` or `sandboxes.concurrent`.

      - example: ` "vision.scrape.rpm" `

    - ` limit `: type ` number ` — The limit's value.

    - ` unit `: type ` string ` — What the limit counts, such as `requests`, `sandboxes`, `vcpu` or `MiB`.

    - ` window_s `: type ` integer ` — Window length in seconds for rate limits and quotas.

    - ` current `: type ` number ` — Current usage against the limit, when known.

  - ` current_status `: type ` string ` — The object's current status, for state conflicts such as `sandbox_not_running`.

  - ` upstream_status `: type ` integer ` — The HTTP status the upstream returned (`upstream_error`, `action_output_invalid`).

  - ` step `: type ` string ` — The failed step, as a step ID (routines), an index (`act`) or a path such as `beats/2/do/0` (render).

  - ` pack_error `: type ` object ` — The error a pack's error mapping chose (`upstream_error` from pack actions).

    - Required fields: ` code `, ` message `, ` retryable `

    - ` code `: type ` string `

    - ` message `: type ` string `

    - ` retryable `: type ` boolean `

Response header ` x-request-id ` — The request ID (`req_…`). It is the `instance` of any problem and the key for `GET /v1/requests/{request_id}`.

- ` value `: type ` string `

  - pattern: ` "^req_[0-9a-hjkmnp-tv-z]{26}$" `

Response header ` Server-Timing ` — Phase durations in milliseconds (W3C Server Timing); the handler's phases break the request down. For a
response whose body size is known, `total` runs from reading the request head to writing the response head.
Streaming responses (event streams, WebSockets, chunked bodies) carry no `total` here: they report their
final phases in the terminal event (`job.succeeded`, `job.failed`, `job.canceled`) or the final job. The
time from the first request byte to the last response byte is recorded in the node's metrics and request
log (`GET /v1/requests/{request_id}`). The edge prepends `edge_auth` and `edge_upstream`.


- ` value `: type ` string `

Example:

```json
{
  "type": "https://docs.beta.b3os.org/errors#routine_invalid",
  "title": "Invalid routine",
  "status": 422,
  "code": "routine_invalid",
  "detail": "2 problems in the definition",
  "instance": "req_01m5t4yg394wegr3j4y01qb363",
  "errors": [
    {
      "pointer": "/steps/1/uses",
      "detail": "io.b3os/slack/post-message@2 does not exist; newest is 1.4.0"
    },
    {
      "pointer": "/steps/1/needs/0",
      "detail": "no step with id pages"
    }
  ]
}
```

### ` 429 ` — A rate, concurrency or quota limit applies (`rate_limited`, `concurrency_limited`, `quota_exceeded`).

Content type: ` application/problem+json `

- ` value `: type ` object ` — An RFC 9457 problem. `code` is stable and listed in the errors catalog; `type` links to its entry. `detail` is human-readable and may change. Extension members appear only for the codes that document them; ignore members you do not recognize. 

  - Required fields: ` type `, ` title `, ` status `, ` code `, ` detail `

  - ` type `: type ` string `; format ` uri ` — `https://docs.beta.b3os.org/errors#<code>`.

    - example: ` "https://docs.beta.b3os.org/errors#invalid_request" `

  - ` title `: type ` string ` — Short summary of the problem type.

  - ` status `: type ` integer ` — The HTTP status code, repeated for convenience.

    - minimum: ` 400 `

    - maximum: ` 599 `

  - ` code `: type ` string ` — Stable machine-readable error code from the errors catalog.

    - pattern: ` "^[a-z][a-z0-9_]*$" `

  - ` detail `: type ` string ` — Explanation of this occurrence. Never contains secrets.

  - ` instance `: type ` string ` — The request ID (`req_…`) of the request that failed. Include it when you contact support.

    - example: ` "req_01m5t4yg394wegr3j4y01qb363" `

  - ` errors `: type ` array ` — Validation problems, one per field (`invalid_request` and the other codes that list `errors`).

    - ` array item `: type ` object ` — One validation problem.

      - Required fields: ` pointer `, ` detail `

      - ` pointer `: type ` string ` — Where the problem is: a JSON pointer into the request body (`/url`, `/steps/0/uses`), or `#query/<name>`, `#path/<name>` or `#header/<name>` for parameters. 

        - example: ` "/url" `

      - ` detail `: type ` string ` — What is wrong there.

        - example: ` "must be an absolute http or https URL" `

  - ` reason `: type ` string ` — Finer-grained cause for codes that document one (`unauthorized`, `forbidden`, `egress_denied`, `upstream_unreachable`, …).

  - ` required_scope `: type ` string ` — The scope that was missing (`forbidden` with `reason: scope`).

  - ` retry_after_s `: type ` integer ` — Same value as the `Retry-After` header, for clients that only read the body.

    - minimum: ` 0 `

  - ` limit `: type ` object ` — The limit an error refers to. IDs and current values are listed by `GET /v1/limits`.

    - Required fields: ` id `, ` limit `, ` unit `

    - ` id `: type ` string ` — Limit ID, such as `vision.scrape.rpm` or `sandboxes.concurrent`.

      - example: ` "vision.scrape.rpm" `

    - ` limit `: type ` number ` — The limit's value.

    - ` unit `: type ` string ` — What the limit counts, such as `requests`, `sandboxes`, `vcpu` or `MiB`.

    - ` window_s `: type ` integer ` — Window length in seconds for rate limits and quotas.

    - ` current `: type ` number ` — Current usage against the limit, when known.

  - ` current_status `: type ` string ` — The object's current status, for state conflicts such as `sandbox_not_running`.

  - ` upstream_status `: type ` integer ` — The HTTP status the upstream returned (`upstream_error`, `action_output_invalid`).

  - ` step `: type ` string ` — The failed step, as a step ID (routines), an index (`act`) or a path such as `beats/2/do/0` (render).

  - ` pack_error `: type ` object ` — The error a pack's error mapping chose (`upstream_error` from pack actions).

    - Required fields: ` code `, ` message `, ` retryable `

    - ` code `: type ` string `

    - ` message `: type ` string `

    - ` retryable `: type ` boolean `

Response header ` x-request-id ` — The request ID (`req_…`). It is the `instance` of any problem and the key for `GET /v1/requests/{request_id}`.

- ` value `: type ` string `

  - pattern: ` "^req_[0-9a-hjkmnp-tv-z]{26}$" `

Response header ` Server-Timing ` — Phase durations in milliseconds (W3C Server Timing); the handler's phases break the request down. For a
response whose body size is known, `total` runs from reading the request head to writing the response head.
Streaming responses (event streams, WebSockets, chunked bodies) carry no `total` here: they report their
final phases in the terminal event (`job.succeeded`, `job.failed`, `job.canceled`) or the final job. The
time from the first request byte to the last response byte is recorded in the node's metrics and request
log (`GET /v1/requests/{request_id}`). The edge prepends `edge_auth` and `edge_upstream`.


- ` value `: type ` string `

Response header ` Retry-After ` — Seconds to wait before retrying or polling again.

- ` value `: type ` integer `

  - minimum: ` 0 `

Response header ` RateLimit-Limit ` — Requests allowed in the current window for the limit that applies to this operation.

- ` value `: type ` integer `

Response header ` RateLimit-Remaining ` — Requests left in the current window.

- ` value `: type ` integer `

Response header ` RateLimit-Reset ` — Seconds until the current window resets.

- ` value `: type ` integer `

Response header ` RateLimit-Policy ` — The applied policy as `<limit>;w=<window seconds>;name="<limit id>"` (limit IDs are listed by `GET /v1/limits`).

- ` value `: type ` string `

Example:

```json
{
  "type": "https://docs.beta.b3os.org/errors#rate_limited",
  "title": "Rate limited",
  "status": 429,
  "code": "rate_limited",
  "detail": "60 scrapes per minute allowed; retry in 12 seconds",
  "instance": "req_01m5t4yg394wegr3j4y01qb363",
  "retry_after_s": 12,
  "limit": {
    "id": "vision.scrape.rpm",
    "limit": 60,
    "unit": "requests",
    "window_s": 60,
    "current": 60
  }
}
```

### ` 500 ` — Something failed inside B3OS (`internal`). Retry with backoff.

Content type: ` application/problem+json `

- ` value `: type ` object ` — An RFC 9457 problem. `code` is stable and listed in the errors catalog; `type` links to its entry. `detail` is human-readable and may change. Extension members appear only for the codes that document them; ignore members you do not recognize. 

  - Required fields: ` type `, ` title `, ` status `, ` code `, ` detail `

  - ` type `: type ` string `; format ` uri ` — `https://docs.beta.b3os.org/errors#<code>`.

    - example: ` "https://docs.beta.b3os.org/errors#invalid_request" `

  - ` title `: type ` string ` — Short summary of the problem type.

  - ` status `: type ` integer ` — The HTTP status code, repeated for convenience.

    - minimum: ` 400 `

    - maximum: ` 599 `

  - ` code `: type ` string ` — Stable machine-readable error code from the errors catalog.

    - pattern: ` "^[a-z][a-z0-9_]*$" `

  - ` detail `: type ` string ` — Explanation of this occurrence. Never contains secrets.

  - ` instance `: type ` string ` — The request ID (`req_…`) of the request that failed. Include it when you contact support.

    - example: ` "req_01m5t4yg394wegr3j4y01qb363" `

  - ` errors `: type ` array ` — Validation problems, one per field (`invalid_request` and the other codes that list `errors`).

    - ` array item `: type ` object ` — One validation problem.

      - Required fields: ` pointer `, ` detail `

      - ` pointer `: type ` string ` — Where the problem is: a JSON pointer into the request body (`/url`, `/steps/0/uses`), or `#query/<name>`, `#path/<name>` or `#header/<name>` for parameters. 

        - example: ` "/url" `

      - ` detail `: type ` string ` — What is wrong there.

        - example: ` "must be an absolute http or https URL" `

  - ` reason `: type ` string ` — Finer-grained cause for codes that document one (`unauthorized`, `forbidden`, `egress_denied`, `upstream_unreachable`, …).

  - ` required_scope `: type ` string ` — The scope that was missing (`forbidden` with `reason: scope`).

  - ` retry_after_s `: type ` integer ` — Same value as the `Retry-After` header, for clients that only read the body.

    - minimum: ` 0 `

  - ` limit `: type ` object ` — The limit an error refers to. IDs and current values are listed by `GET /v1/limits`.

    - Required fields: ` id `, ` limit `, ` unit `

    - ` id `: type ` string ` — Limit ID, such as `vision.scrape.rpm` or `sandboxes.concurrent`.

      - example: ` "vision.scrape.rpm" `

    - ` limit `: type ` number ` — The limit's value.

    - ` unit `: type ` string ` — What the limit counts, such as `requests`, `sandboxes`, `vcpu` or `MiB`.

    - ` window_s `: type ` integer ` — Window length in seconds for rate limits and quotas.

    - ` current `: type ` number ` — Current usage against the limit, when known.

  - ` current_status `: type ` string ` — The object's current status, for state conflicts such as `sandbox_not_running`.

  - ` upstream_status `: type ` integer ` — The HTTP status the upstream returned (`upstream_error`, `action_output_invalid`).

  - ` step `: type ` string ` — The failed step, as a step ID (routines), an index (`act`) or a path such as `beats/2/do/0` (render).

  - ` pack_error `: type ` object ` — The error a pack's error mapping chose (`upstream_error` from pack actions).

    - Required fields: ` code `, ` message `, ` retryable `

    - ` code `: type ` string `

    - ` message `: type ` string `

    - ` retryable `: type ` boolean `

Response header ` x-request-id ` — The request ID (`req_…`). It is the `instance` of any problem and the key for `GET /v1/requests/{request_id}`.

- ` value `: type ` string `

  - pattern: ` "^req_[0-9a-hjkmnp-tv-z]{26}$" `

Response header ` Server-Timing ` — Phase durations in milliseconds (W3C Server Timing); the handler's phases break the request down. For a
response whose body size is known, `total` runs from reading the request head to writing the response head.
Streaming responses (event streams, WebSockets, chunked bodies) carry no `total` here: they report their
final phases in the terminal event (`job.succeeded`, `job.failed`, `job.canceled`) or the final job. The
time from the first request byte to the last response byte is recorded in the node's metrics and request
log (`GET /v1/requests/{request_id}`). The edge prepends `edge_auth` and `edge_upstream`.


- ` value `: type ` string `

Example:

```json
{
  "type": "https://docs.beta.b3os.org/errors#internal",
  "title": "Internal error",
  "status": 500,
  "code": "internal",
  "detail": "internal error",
  "instance": "req_01m5t4yg394wegr3j4y01qb363"
}
```

### ` 503 ` — The node or the product is not available right now (`node_unavailable`, `product_unavailable`, `capacity_unavailable`).

Content type: ` application/problem+json `

- ` value `: type ` object ` — An RFC 9457 problem. `code` is stable and listed in the errors catalog; `type` links to its entry. `detail` is human-readable and may change. Extension members appear only for the codes that document them; ignore members you do not recognize. 

  - Required fields: ` type `, ` title `, ` status `, ` code `, ` detail `

  - ` type `: type ` string `; format ` uri ` — `https://docs.beta.b3os.org/errors#<code>`.

    - example: ` "https://docs.beta.b3os.org/errors#invalid_request" `

  - ` title `: type ` string ` — Short summary of the problem type.

  - ` status `: type ` integer ` — The HTTP status code, repeated for convenience.

    - minimum: ` 400 `

    - maximum: ` 599 `

  - ` code `: type ` string ` — Stable machine-readable error code from the errors catalog.

    - pattern: ` "^[a-z][a-z0-9_]*$" `

  - ` detail `: type ` string ` — Explanation of this occurrence. Never contains secrets.

  - ` instance `: type ` string ` — The request ID (`req_…`) of the request that failed. Include it when you contact support.

    - example: ` "req_01m5t4yg394wegr3j4y01qb363" `

  - ` errors `: type ` array ` — Validation problems, one per field (`invalid_request` and the other codes that list `errors`).

    - ` array item `: type ` object ` — One validation problem.

      - Required fields: ` pointer `, ` detail `

      - ` pointer `: type ` string ` — Where the problem is: a JSON pointer into the request body (`/url`, `/steps/0/uses`), or `#query/<name>`, `#path/<name>` or `#header/<name>` for parameters. 

        - example: ` "/url" `

      - ` detail `: type ` string ` — What is wrong there.

        - example: ` "must be an absolute http or https URL" `

  - ` reason `: type ` string ` — Finer-grained cause for codes that document one (`unauthorized`, `forbidden`, `egress_denied`, `upstream_unreachable`, …).

  - ` required_scope `: type ` string ` — The scope that was missing (`forbidden` with `reason: scope`).

  - ` retry_after_s `: type ` integer ` — Same value as the `Retry-After` header, for clients that only read the body.

    - minimum: ` 0 `

  - ` limit `: type ` object ` — The limit an error refers to. IDs and current values are listed by `GET /v1/limits`.

    - Required fields: ` id `, ` limit `, ` unit `

    - ` id `: type ` string ` — Limit ID, such as `vision.scrape.rpm` or `sandboxes.concurrent`.

      - example: ` "vision.scrape.rpm" `

    - ` limit `: type ` number ` — The limit's value.

    - ` unit `: type ` string ` — What the limit counts, such as `requests`, `sandboxes`, `vcpu` or `MiB`.

    - ` window_s `: type ` integer ` — Window length in seconds for rate limits and quotas.

    - ` current `: type ` number ` — Current usage against the limit, when known.

  - ` current_status `: type ` string ` — The object's current status, for state conflicts such as `sandbox_not_running`.

  - ` upstream_status `: type ` integer ` — The HTTP status the upstream returned (`upstream_error`, `action_output_invalid`).

  - ` step `: type ` string ` — The failed step, as a step ID (routines), an index (`act`) or a path such as `beats/2/do/0` (render).

  - ` pack_error `: type ` object ` — The error a pack's error mapping chose (`upstream_error` from pack actions).

    - Required fields: ` code `, ` message `, ` retryable `

    - ` code `: type ` string `

    - ` message `: type ` string `

    - ` retryable `: type ` boolean `

Response header ` x-request-id ` — The request ID (`req_…`). It is the `instance` of any problem and the key for `GET /v1/requests/{request_id}`.

- ` value `: type ` string `

  - pattern: ` "^req_[0-9a-hjkmnp-tv-z]{26}$" `

Response header ` Server-Timing ` — Phase durations in milliseconds (W3C Server Timing); the handler's phases break the request down. For a
response whose body size is known, `total` runs from reading the request head to writing the response head.
Streaming responses (event streams, WebSockets, chunked bodies) carry no `total` here: they report their
final phases in the terminal event (`job.succeeded`, `job.failed`, `job.canceled`) or the final job. The
time from the first request byte to the last response byte is recorded in the node's metrics and request
log (`GET /v1/requests/{request_id}`). The edge prepends `edge_auth` and `edge_upstream`.


- ` value `: type ` string `

Response header ` Retry-After ` — Seconds to wait before retrying or polling again.

- ` value `: type ` integer `

  - minimum: ` 0 `

Example:

```json
{
  "type": "https://docs.beta.b3os.org/errors#node_unavailable",
  "title": "Node unavailable",
  "status": 503,
  "code": "node_unavailable",
  "detail": "Node n1 is not reachable; retry in 5 seconds",
  "instance": "req_01m5t4yg394wegr3j4y01qb363",
  "retry_after_s": 5
}
```

## Request examples

### cURL

```curl
curl -X PUT 'https://beta-api.b3os.org/v1/routines/nightly-digest' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "nightly-digest",
  "description": "Scrape the news page and post a summary",
  "triggers": [
    {
      "type": "cron",
      "id": "nightly",
      "cron": "0 0 6 * * *",
      "tz": "America/Los_Angeles",
      "catch_up": "latest"
    },
    {
      "type": "webhook",
      "id": "inbound",
      "verify": {
        "scheme": "b3os"
      }
    }
  ],
  "steps": [
    {
      "id": "page",
      "uses": "vision/scrape@1",
      "with": {
        "url": "https://news.example.com"
      }
    }
  ]
}'
```

### JavaScript

```javascript
const response = await fetch('https://beta-api.b3os.org/v1/routines/nightly-digest', {
  method: 'PUT',
  headers: {
      "Authorization": "Bearer YOUR_API_TOKEN",
      "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "name": "nightly-digest",
    "description": "Scrape the news page and post a summary",
    "triggers": [
      {
        "type": "cron",
        "id": "nightly",
        "cron": "0 0 6 * * *",
        "tz": "America/Los_Angeles",
        "catch_up": "latest"
      },
      {
        "type": "webhook",
        "id": "inbound",
        "verify": {
          "scheme": "b3os"
        }
      }
    ],
    "steps": [
      {
        "id": "page",
        "uses": "vision/scrape@1",
        "with": {
          "url": "https://news.example.com"
        }
      }
    ]
  })
});

const data = await response.json();
console.log(data);
```

### Python

```python
import requests

headers = {
    'Authorization': 'Bearer YOUR_API_TOKEN'
}

response = requests.put('https://beta-api.b3os.org/v1/routines/nightly-digest', headers=headers, json={
  "name": "nightly-digest",
  "description": "Scrape the news page and post a summary",
  "triggers": [
    {
      "type": "cron",
      "id": "nightly",
      "cron": "0 0 6 * * *",
      "tz": "America/Los_Angeles",
      "catch_up": "latest"
    },
    {
      "type": "webhook",
      "id": "inbound",
      "verify": {
        "scheme": "b3os"
      }
    }
  ],
  "steps": [
    {
      "id": "page",
      "uses": "vision/scrape@1",
      "with": {
        "url": "https://news.example.com"
      }
    }
  ]
})
print(response.json())
```