POST /v1/routines/validate
Compiles a definition without publishing it and returns every diagnostic with a JSON pointer: schema errors, unknown callables and step references, dependency cycles, CEL type errors and input mismatches against the callables' schemas. Always answers 200; check `valid`. Use it while an agent writes a routine.

Authentication

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

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

200 The validation result.
application/json
object string REQUIRED
Enum: routine_validation
valid boolean REQUIRED
diagnostics object[] REQUIRED
Array of:
severity string REQUIRED
Enum: error, warning
code string REQUIRED
An error code from the catalog, such as `routine_invalid` or `connection_required`.
pointer string REQUIRED
JSON pointer into the definition.
detail string REQUIRED
steps integer
Number of steps in the compiled plan, map bodies included.
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`).
413 The request body exceeds this operation's limit.
415 The Content-Type is not accepted by this operation.
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/validate' \  -H 'Authorization: Bearer YOUR_API_TOKEN' \  -H 'Content-Type: application/json' \  -d '{  "name": "nightly-digest",  "steps": [    {      "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/validate', {  method: 'POST',  headers: {      "Authorization": "Bearer YOUR_API_TOKEN",      "Content-Type": "application/json"  },  body: JSON.stringify({    "name": "nightly-digest",    "steps": [      {        "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/validate', headers=headers, json={  "name": "nightly-digest",  "steps": [    {      "id": "summary",      "uses": "infer/chat@1",      "with": {        "messages": [          {            "role": "user",            "content": "Summarize: ${{ steps.page.outputs.markdown }}"          }        ]      }    }  ]})print(response.json())
200 Response
{  "object": "routine_validation",  "valid": false,  "diagnostics": [    {      "severity": "error",      "code": "routine_invalid",      "pointer": "/steps/0/with/messages/0/content",      "detail": "steps.page is not a step of this routine"    }  ],  "steps": 1}
Ask a question... ⌘I