Skip to main content
Routines are the product name for saved pipelines. The API paths intentionally use pipelines; this page documents routine-specific controls. All workspace routes require an authenticated session or CLI token and membership in {workspaceId}. Error responses use the API Problem Details shape with an error message. An unauthenticated request is 401; a caller outside the workspace is 403.

Governance

Each governance operation is a POST with an empty request body. The response is the updated routine object (200 OK). A missing or soft-deleted routine is 404; a routine in the wrong lifecycle state is 409; malformed JSON is 400; the role gate is 403. Approval/rejection are valid only for proposed routines. Disable cancels in-flight runs; only active routines can run.

Standing approval grants

A wait:approval gate blocks every run until a human decides. A standing grant lets a gate the operator keeps waving through stop asking. A grant is scoped to one gate of one routine and pinned to the routine’s definition_hash. Editing the routine moves that hash, so no grant matches and the gate asks again — trust never carries onto a body nobody reviewed. A grant is never an invisible path: when it fires, an ordinary approved waitpoint is still written, attributed to the granting operator, with the grant id in its decision_payload. Request notes for POST:
  • step_id is required. A grant with no step would be trust in the routine as a whole, which this endpoint deliberately cannot express — 400.
  • definition_hash is the anti-TOCTOU token. The inbox card that offered the grant carries the hash it was rendered for. If the routine changed in between, the supplied hash no longer matches and the request is 409 rather than being silently retargeted at the new body. Omit it to accept whatever is saved now.
  • A second grant for the same gate and definition is 409, not a silent reset of the existing grant’s use counter.
  • max_uses and expires_at are both optional; absent means unbounded. An expires_at already in the past is 400: such a grant could never fire, and a row that reads as trust granted while changing nothing is worse than a refusal.
The response to GET reports definition_hash at the top level — the value a client must echo back to grant new trust — alongside each grant’s own pinned hash. A grant whose hash differs from the current one is already inert. DELETE is scoped to the routine in the path: grant ids are workspace-unique, so revoking through an unrelated routine’s URL is 404 rather than a silent cross-routine write. Both granting and revoking require an authenticated user — the audit exists to answer who trusted this gate and who took it back, and neither half may be NULL. Grants are ignored entirely when the governing crew is at autonomy_level: strict. That is the routine’s author crew — a routine executes in its author’s context, reusing that crew’s persona and credentials — or the invoking crew when one invokes across crews.

Schedules

See the dedicated Schedules API reference for the complete request fields, response fields, merge semantics, and status branches. The create request accepts target_pipeline_slug or target_pipeline_id and requires cron_expr:
catchup_policy is once, all, or skip. A wake pipeline must be a different routine whose DSL declares agentless: true. Invalid cron, timezone, target, wake references, or JSON is 400; so is a preset the target recipe rejects, or a pin to a version the routine never archived, on create, on a PATCH that changes inputs, target, pin or enables the plan, and on activate (see Schedules). The schedule representation includes next_run_at, last_run_at, last_status, last_run_id, failure breaker counters, wake telemetry, and the version a fire would use (effective_version, null when the target is gone) with version_pinned saying whether it comes from target_pipeline_version. A circuit-breaker disable sets disabled_reason to circuit_breaker.

Run detail

The routines workspace reads a run’s step executions and its artifacts from two paginated endpoints, and the schedule board from a calendar endpoint. Executions and artifacts each answer in two shapes on the same path. Without a selector they return a page of rows — at most 100 executions, at most 50 artifacts — with next_cursor set when more remain, which is passed back as ?after=. The two caps differ, so a client that treats a short page as the last one has to know which endpoint it is reading. Name one row with ?execution_id= or ?artifact_id= and the response is that row’s payload instead. ?download= on artifacts is the exception: it streams the stored bytes with the artifact’s own content type, nosniff, an attachment disposition and Cache-Control: private, no-store, so it is not a JSON body at all. The calendar merges three kinds of event into one list: planned occurrences computed from schedules, pending runs already queued, and run rows that have executed. Both planned and pending events carry a read-only inputs preview ({} when empty); historical run events omit it. Planned presets are not run results or a guarantee of success. Preview maps retain safe primitives; credential, file and recognized secret values become {type: "credential"}, {type: "file"} or {type: "redacted"}. Other objects/arrays become empty containers, preserving field counts without exposing contents. Never replay a preview as inputs. truncated is true when the window held more than the endpoint will return, in which case narrow from/to rather than paging. A run outside {workspaceId} is 404, as is a named execution or artifact that does not belong to the run. A malformed cursor, a missing or unparseable from/to, and a calendar window outside zero to 32 days are all 400.

Webhooks

The public dispatch token is the authentication mechanism. When a signing secret is configured, the request must also carry a valid HMAC signature. Dispatch is asynchronous and returns 202; an inactive routine returns 409.

Editing a webhook in place (F21)

Before PATCH existed, changing anything about a webhook meant delete + recreate — which mints a new token and therefore a new public URL, breaking every sender already configured against the old one. PATCH edits in place instead; every field is optional and an absent field keeps its existing value, the same convention PATCH .../pipeline-schedules/{scheduleId} uses:
The token — and therefore the public URL — never changes through this endpoint, under any field combination, rotate_secret included. Rotating the URL still requires delete + recreate; that is the one thing this endpoint deliberately does not do. signing_secret is present in the response only when rotate_secret: true was sent; an ordinary edit returns signing_secret_set: true with no secret value, matching the list/get show-once contract. CLI: crewship routine webhooks update <webhook_id> [--name ...] [--rate-limit N] [--inputs-template '{"...":"..."}'] [--enabled[=false]] [--rotate-secret].