POST /v1/routines
Compiles and publishes a new routine as version 1. The body is the routine definition, as JSON or as YAML (`Content-Type: application/yaml`). Fails with `routine_invalid` (pointers into the definition) when it does not compile, and with `routine_name_taken` when the name exists; `PUT /v1/routines/{routine}` creates or updates by name instead. Webhook triggers verified with the `b3os` scheme get a signing secret that is returned in this response only.

Authentication

Bearer Token (b3os_sk_ API key or b3os_pat_ token) API Key (cookie: __Host-b3os_session)

Header Parameters

Idempotency-Key string optional header
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`.

Request Body required

application/json
$schema string (uri)
Optional pointer to this schema for editors.
name string REQUIRED
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.
description string
What the routine does; an MCP server that lists routines as tools uses it as the tool description.
inputs object | boolean & object
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 ({}).
output 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).
env 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 object
Read-only JSON constants exposed as vars.<name>. Values are literals; strings in them are not evaluated as templates.
triggers object[]
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 of:
type unknown REQUIRED
Enum: cron, interval, webhook, action
id string REQUIRED
A CEL identifier (so steps.<id>.outputs parses): lowercase letters, digits and _, not a CEL reserved word.
uses string
action only: the pack trigger to subscribe to.
with object
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.
connections 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 string
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.
cron 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.
tz 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.
every_ms integer
Interval period. Plans may raise the floor (GET /v1/limits).
anchor 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
catch_up 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 integer
overlap 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
verify 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.
scheme unknown REQUIRED
Enum: b3os, github, stripe, slack, standard_webhooks, hmac_sha256
secret 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.
header string
encoding unknown
Enum: hex, base64
prefix string
timestamp_header string
hmac_sha256 only, and required there: header carrying the delivery's Unix timestamp.
tolerance_s integer
dedupe string
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).
defaults object
durability 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
timeout_ms integer
Default timeout for uses and map steps that do not set timeout_ms. Wait and sleep steps are not affected.
retry object
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.
max_attempts integer REQUIRED
Total attempts including the first.
initial_ms integer
factor number
max_ms integer
jitter boolean
concurrency 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.
limit integer REQUIRED
group string
Group key evaluated at run start (context: inputs, vars, trigger). Default: one group for the routine.
on_limit 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
steps (unknown | unknown | unknown | unknown)[] REQUIRED
Array of:
One of:
Option 1unknown
Option 2unknown
Option 3unknown
Option 4unknown

Responses

201 The routine and its first version.
application/json
id string REQUIRED
object string REQUIRED
Enum: routine
name string REQUIRED
description string | null REQUIRED
status string REQUIRED
`paused` when triggers are stopped; API runs still work unless `paused_reason` is `run_as_revoked`.
Enum: active, paused
paused_reason string | null REQUIRED
Enum: manual, run_as_revoked, null
version object REQUIRED
id string REQUIRED
number integer REQUIRED
created_at string (date-time) REQUIRED
triggers object[] REQUIRED
Array of:
id string REQUIRED
The trigger's `id` in the definition.
type string REQUIRED
Enum: cron, interval, webhook, action
status string REQUIRED
Enum: active, paused
next_fire_at string (date-time) | null REQUIRED
Next scheduled fire (cron and interval), or `null`.
hook object | null REQUIRED
The inbound URL of a webhook trigger, or `null`.
id string REQUIRED
url string (uri) REQUIRED
Give this URL to the provider.
verify string REQUIRED
Enum: b3os, github, stripe, slack, standard_webhooks, hmac_sha256
secret string
The `b3os` scheme's signing secret. Present only in the response of the publish that created the hook (and of a rotation).
subscription_id string | null REQUIRED
The trigger subscription of an action trigger (`GET /v1/actions/subscriptions/{subscription_id}`), or `null`.
run_as object REQUIRED
The identity runs execute as (the key that published the active version, with its scopes at publish time).
key string | null REQUIRED
The API key ID; `null` when a dashboard user published.
scopes string[] REQUIRED
Array of: string
created_at string (date-time) REQUIRED
updated_at string (date-time) REQUIRED
metadata object REQUIRED
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.
400 The request failed validation (`invalid_request`, `invalid_url`).
401 Missing, malformed, expired or revoked credentials (`unauthorized`).
403 Authenticated but not allowed (`forbidden`, `egress_denied`).
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`).
413 The request body exceeds this operation's limit.
415 The Content-Type is not accepted by this operation.
422 The definition does not compile (`routine_invalid`).
429 A rate, concurrency or quota limit applies (`rate_limited`, `concurrency_limited`, `quota_exceeded`).
500 Something failed inside B3OS (`internal`). Retry with backoff.
503 The node or the product is not available right now (`node_unavailable`, `product_unavailable`, `capacity_unavailable`).
curl -X POST 'https://beta-api.b3os.org/v1/routines' \  -H 'Authorization: Bearer YOUR_API_TOKEN' \  -H 'Content-Type: application/json' \  -d '{  "name": "nightly-digest",  "triggers": [    {      "type": "cron",      "id": "nightly",      "cron": "0 0 6 * * *",      "tz": "America/Los_Angeles",      "catch_up": "latest"    }  ],  "steps": [    {      "id": "page",      "uses": "vision/scrape@1",      "with": {        "url": "https://news.example.com",        "formats": [          "markdown"        ]      }    },    {      "id": "summary",      "uses": "infer/chat@1",      "with": {        "messages": [          {            "role": "user",            "content": "Summarize: ${{ steps.page.outputs.markdown }}"          }        ]      }    }  ]}'
const response = await fetch('https://beta-api.b3os.org/v1/routines', {  method: 'POST',  headers: {      "Authorization": "Bearer YOUR_API_TOKEN",      "Content-Type": "application/json"  },  body: JSON.stringify({    "name": "nightly-digest",    "triggers": [      {        "type": "cron",        "id": "nightly",        "cron": "0 0 6 * * *",        "tz": "America/Los_Angeles",        "catch_up": "latest"      }    ],    "steps": [      {        "id": "page",        "uses": "vision/scrape@1",        "with": {          "url": "https://news.example.com",          "formats": [            "markdown"          ]        }      },      {        "id": "summary",        "uses": "infer/chat@1",        "with": {          "messages": [            {              "role": "user",              "content": "Summarize: ${{ steps.page.outputs.markdown }}"            }          ]        }      }    ]  })});const data = await response.json();console.log(data);
import requestsheaders = {    'Authorization': 'Bearer YOUR_API_TOKEN'}response = requests.post('https://beta-api.b3os.org/v1/routines', headers=headers, json={  "name": "nightly-digest",  "triggers": [    {      "type": "cron",      "id": "nightly",      "cron": "0 0 6 * * *",      "tz": "America/Los_Angeles",      "catch_up": "latest"    }  ],  "steps": [    {      "id": "page",      "uses": "vision/scrape@1",      "with": {        "url": "https://news.example.com",        "formats": [          "markdown"        ]      }    },    {      "id": "summary",      "uses": "infer/chat@1",      "with": {        "messages": [          {            "role": "user",            "content": "Summarize: ${{ steps.page.outputs.markdown }}"          }        ]      }    }  ]})print(response.json())
201 Response
{  "id": "rtn_01m5t6vhkw4a8fa4tb5w4ffz37",  "object": "routine",  "name": "nightly-digest",  "description": null,  "status": "active",  "paused_reason": null,  "version": {    "id": "ver_01m5t3vcc6bxj0v3x705zmg4cd",    "number": 1,    "created_at": "2026-10-25T19:10:00.000Z"  },  "triggers": [    {      "id": "nightly",      "type": "cron",      "status": "active",      "next_fire_at": "2026-10-26T13:00:00.000Z",      "hook": null,      "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:10:00.000Z",  "metadata": {}}
Ask a question... ⌘I