Skip to main content
Pipelines are persisted, versioned workflow definitions. All routes below use the /api/v1 prefix. Workspace routes require a session/JWT and a workspace the caller belongs to; mutation role checks are listed below. Errors use the API’s Problem Details shape (error is the human-readable message).

Route inventory

create, manage, and delete are the server’s role-action gates (normally MANAGER+, OWNER/ADMIN, and OWNER/ADMIN respectively). A missing pipeline is 404; malformed JSON or invalid input is 400; insufficient role is 403; an unavailable runner/store is 503.

Core endpoint contract

All paths in this page require the /api/v1 prefix. member means an authenticated session or CLI token with membership in {workspaceId}; create, manage, and delete are the role/capability gates described above. Requests are JSON unless the operation is a GET or DELETE with no body. Successful responses are JSON and use the status shown by the operation; an empty successful delete uses 204 No Content. Invalid JSON, missing required path/body values, or invalid query values return 400; missing or soft-deleted resources return 404; authorization failures return 401 or 403; unavailable runner/storage dependencies return 503; unexpected handler/database failures return 500. The route-specific request and response contracts are: The public POST /api/v1/webhooks/{token} route is the exception to session authentication: the high-entropy token authenticates dispatch, and an configured signing secret adds HMAC verification. Accepted dispatches return 202; invalid signatures return 401, rate limits return 429, and an inactive target returns 409.

Run payload

run and dry_run accept:
tier_override is trivial, fast, moderate, or smart; an unknown value is treated as no override. triggered_via accepts manual, schedule, webhook, call_pipeline, or issue; other values fall back to manual. Omit it for the default manual. run returns the run result (including run_id, status, output, step_outputs, duration_ms, and cost_usd). An Idempotency-Key header deduplicates runs. run_batch takes { "items": [{ "inputs": {}, "tags": [], "metadata": {} }], "tags": [], "tier_override": "fast" } and returns per-item index, run_id, status, or error. inputs, tags, and metadata are optional. delay_seconds, ttl_seconds, debounce_window_seconds, and debounce_max_seconds must be between 0 and 2,592,000 seconds when deferred dispatch is used; invalid values return 400. priority is carried to the pending queue. The Idempotency-Key header deduplicates a run (the optional idempotency_key_ttl_seconds body field sets the deduplication window; 0 uses the server default). run_batch requires a non-empty items array and accepts at most 100 items; violations return 400.

Run statuses and responses

POST .../pipelines/{slug}/run requires member plus create (normally MANAGER+), and returns 200 OK with a run result for an immediate execution. The immediate result includes run_id, pipeline_id, pipeline_slug, status, output, step_outputs, duration_ms, and cost_usd (plus failure/wait fields when applicable). A deferred request (delay_seconds > 0 or a non-empty debounce_key) returns 202 Accepted with { "status": "SCHEDULED", "pending_id", "fire_at", "coalesced", "priority" } and does not yet have a run ID. The run status values are: SCHEDULED responses use 202 Accepted and contain pending_id, RFC3339Nano fire_at, priority, and coalesced. WAITING responses also contain the current step and waitpoint token. DEDUPED may still refer to an in-flight original. --wait/polling follows WAITING and DEDUPED until the persisted run reaches a terminal status. Persisted run records additionally use queued, running, completed, failed, cancelled, interrupted, dry_run, and waiting (lowercase); interrupted marks boot recovery.

Save and validation

slug and definition are required. Call test_run first with { "definition": {...}, "author_crew_id": "crew_123", "sample_inputs": {} }; the successful response contains an HMAC-bound save_token. The legacy last_test_run_at/last_test_run_passed fields are not proof by themselves. skip_test_gate and skip_governance_gate are OWNER/ADMIN-only. Risky saves may have status proposed until approved; only active pipelines run.

Replay, failures, budgets, and state

Budget and state writes require manage; state keys are limited to 128 characters. The budget response reports month, spent_usd, has_budget, pct_used, and over_budget where applicable.

Audited operation reference

The following details are the wire contract for the remaining pipeline operations. Every endpoint is prefixed with /api/v1. member means an authenticated session with membership in {workspaceId}. A request from an unauthenticated caller returns 401; a caller without workspace membership returns 403. Problem responses use the API error shape with an error message. Unless an endpoint says otherwise, an unexpected database/store failure is 500.

Run changes

GET /api/v1/workspaces/{workspaceId}/pipeline-runs/{runId}/changes

Auth: member plus the read role capability (normally any workspace member). The run must belong to the workspace; a missing or foreign run is 404 (Run not found). The handler resolves the run’s invoking_crew_id, falling back to the pipeline’s author_crew_id, and re-checks that crew’s workspace before contacting the crew container. Response: 200 OK. If no crew/repository can be resolved, the response is { "is_repo": false }. Otherwise the response is the decoded crew git-diff object (the proxy forwards that object without reshaping it). Failure to reach the crew proxy is 502; an internal run/crew lookup failure is 500. A malformed proxy response degrades to { "is_repo": false } with 200.

Run logs

GET /api/v1/workspaces/{workspaceId}/pipeline-runs/{runId}/logs?limit=N

Auth: member plus read. runId is required (400). The run is workspace-scoped and a missing/foreign run returns 404. limit defaults to 200; a positive value from 1 through 1000 is honored, while an absent, non-numeric, zero, negative, or larger value leaves the default in place. Response: 200 OK with an oldest-first JSON array. Each entry is { "ts": "RFC3339", "level": "info", "message": "...", "type": "..." }; type is omitted when empty and an empty severity is returned as info. Journal lookup, row scan, or iteration failures return 500.

Run metadata

PATCH /api/v1/workspaces/{workspaceId}/pipeline-runs/{runId}/metadata

Auth: member plus the update capability (MANAGER+). runId is required (400). The JSON body must contain at least one non-empty operation map:
set replaces a key; increment adds numerically (missing keys start at zero); append pushes onto an array (missing arrays start empty). Invalid JSON or no operation maps is 400. A missing/foreign run is 404; an unavailable run store is 503; an update failure is 500. Response: 200 OK{ "metadata": { ...merged metadata... } }.

POST /api/v1/workspaces/{workspaceId}/pipeline-runs/{runId}/signal

Auth: member plus update (MANAGER+). The body is { "event_type": "approval", "payload": "..." }; event_type is required. Invalid JSON, a missing event_type, or a missing runId is 400. A missing run store or signal registry is 503; a missing/foreign run is 404. If the run is not waiting for that event, the response is 404. Durable delivery records the signal before best-effort in-memory delivery and resumes a parked run. Response: 200 OK{ "ok": true, "delivered": true }. A durable delivery write failure is 500.

GET /api/v1/workspaces/{workspaceId}/pipeline-runs/{runId}/tree

Auth: member. runId is required (400); a missing/foreign run is 404; an unavailable run store is 503; tree loading failure is 500. Response: 200 OK{ "nodes": [...] }, where each node contains id, optional parent_id, pipeline_slug, status, triggered_via, and cost_usd. Nodes are a flat parent-linked run plus descendant list, including call_pipeline, deferred, and replay descendants.

Pipeline budgets

GET /api/v1/workspaces/{workspaceId}/pipelines/{slug}/budget

Auth: member. A missing/soft-deleted pipeline is 404; an unavailable run-history store is 503; pipeline lookup or monthly-spend calculation failure is 500. Response: 200 OK{ "slug", "has_budget", "monthly_budget_usd", "month": "YYYY-MM", "spent_usd", "pct_used"?, "over_budget"? }. pct_used and over_budget are omitted when no positive budget is set. Spend is for the current calendar month.

PATCH /api/v1/workspaces/{workspaceId}/pipelines/{slug}/budget

Auth: member plus manage (OWNER/ADMIN). Body: { "monthly_budget_usd": 25 }. 0 clears the cap; negative values are 400. Invalid JSON/body read failures are 400. A missing pipeline is 404, an unavailable run-history store is 503, and persistence or spend calculation failures are 500. Response: 200 OK with the same budget object as GET.

GET /api/v1/workspaces/{workspaceId}/pipelines/budget-summary

Auth: member. The response is 200 OK with { "month": "YYYY-MM", "routines": [...], "total_budget_usd", "total_spent_usd" }. Each routine row has slug, monthly_budget_usd, spent_usd, and optional pct_used/over_budget. Routines with neither a positive budget nor current month spend are omitted. An unavailable run-history store or list/spend calculation failure is 503/500, respectively.

Appearance

PATCH /api/v1/workspaces/{workspaceId}/pipelines/{slug}/appearance

Auth: member plus create (OWNER/ADMIN/MEMBER). Picking an icon is a labelling act rather than an operational one, so it sits at the create tier rather than manage. This is deliberately not part of PUT .../pipelines/{slug}. Save rewrites definition_json, recomputes definition_hash, can mint a new version and re-runs the governance risk classifier — none of which should happen because somebody picked a different colour. This endpoint writes two columns. Request: JSON body { "icon": "receipt", "color": "amber" }. Both fields are optional and independent: Values are opaque to the server: icon is a crew-icon name and color a gradient-palette id, both owned by the web UI’s icon kit. The server validates length only — each is bounded at 64 characters — so the kit can gain an icon without the API rejecting it. An unknown value renders the fallback icon, which is what an unset value already does. Bodies are read up to 4 KiB. Response: 200 OK with the updated routine (the same object GET .../pipelines/{slug} returns, including icon and color). If the write lands but the follow-up read fails, the response is still 200 OK but carries only { "icon", "color" } — failing here would have the caller retry a change that already applied. That is why the response schema is a oneOf. Statuses: 400 for an unreadable body, invalid JSON, or a value over 64 characters; 401 unauthenticated; 403 below the create tier; 404 when the routine does not exist in the workspace; 500 on persistence failure.
See crewship routine appearance for the CLI equivalent.

Deferred and replay operations

GET /api/v1/workspaces/{workspaceId}/pipelines/pending

Auth: member. 200 OK returns an array (never null) of at most 100 pending deferred triggers. Each item contains id, pipeline_slug, optional debounce_key, priority, and RFC3339Nano fire_at. An unavailable database is 503; listing failure is 500.

POST /api/v1/workspaces/{workspaceId}/pipelines/pending/{pendingId}/cancel

Auth: member plus update (MANAGER+). pendingId is required (400). An unavailable database is 503; a missing, already-fired, expired, or already-cancelled item is 404; cancellation failure is 500. Response: 200 OK{ "ok": true, "cancelled": "<pendingId>" }.

POST /api/v1/workspaces/{workspaceId}/pipelines/runs/{runId}/replay

Auth: member plus create (MANAGER+). Body is optional; when present it is { "pinned_version": 3 }. The replay uses the source run’s original inputs, metadata, and tags, marks the new run as a replay, and uses HEAD unless pinned_version is supplied. Invalid JSON or a missing runId is 400. Response: 200 OK with the newly-created run result. A missing/foreign source run is 404; a deleted source pipeline is 404; an unavailable run store is 503; an execution concurrency limit is 429; a pinned version that does not exist is 409; other lookup/execution failures are 500. The response is the ordinary RunResult object: run_id, pipeline_id, pipeline_slug, status, output, step_outputs, duration_ms, and cost_usd, with failed_at_step/error_message for failures and waitpoint_token/current_step when the replay parks at a waitpoint. The new run is marked as a replay of runId; its inputs, metadata, and tags come from the source run. An omitted pinned_version uses the current head version. Statuses: 200 OK; 400 for invalid input; 401 without authentication; 403 without membership/create permission; 404 for a missing source; 409 for an invalid pinned version; 429 at the concurrency limit; 503 for an unavailable run store; 500 for other failures.

GET /api/v1/workspaces/{workspaceId}/pipelines/runs/errors?limit=N

Auth: member. limit is passed to the failure-group query; the default is 50, and the store normalizes non-positive or greater-than-200 values to 50. The response excludes runs whose pipeline was soft-deleted and groups failed runs by error_fingerprint, newest group first. Response: 200 OK{ "groups": [...] }. Each group contains fingerprint, count, pipeline_slug, failed_at_step, sample_error, and run_ids. run_ids is a newest-first sample capped at 100 per group; it is the input for bulk_replay, not necessarily the complete group. An unavailable run store is 503; failure-group lookup is 500.

POST /api/v1/workspaces/{workspaceId}/pipelines/runs/bulk_replay

Auth: member plus create (MANAGER+). Body must select runs by run_ids or fingerprint; optional limit defaults to 50 and is clamped to 50 when absent, non-positive, or greater than 100:
When both selectors are supplied, IDs are retained and fingerprint IDs are appended, then the combined list is truncated to limit. The handler defaults limit to 50 when it is absent, non-positive, or greater than 100. No selected IDs is 400; invalid JSON is 400; an unavailable run store or failure loading fingerprint groups is 503/500. Individual replay failures are reported per item rather than failing the whole request. The fingerprint selector expands from the newest sampled IDs returned by the failure-group store, so a group larger than the sample cannot replay every historical run in one request. Statuses: 200 OK; 400 for invalid selectors or JSON; 401 without authentication; 403 without membership; 503 for an unavailable run store; 500 for failure-group lookup. Response: 200 OK{ "requested": N, "replayed": N, "results": [{ "source_run_id", "new_run_id"?, "status"?, "error"? }] }. Statuses: 200 OK; 400 for invalid selectors or JSON; 401 without authentication; 403 without membership/create permission; 503 for an unavailable run store; 500 for selector or replay failures.

Version diff

GET /api/v1/workspaces/{workspaceId}/pipelines/{slug}/diff?from=N&to=M

Auth: member. Both positive integer query parameters are required (400). A missing pipeline, from version, or to version is 404; pipeline/version lookup failures or diff generation failure are 500. Response: 200 OK{ "slug", "from_version", "to_version", "from_hash", "to_hash", "identical", "unified_diff" }. Definitions are pretty-printed before diffing. unified_diff is empty when the hashes are identical.

Governance

POST .../pipelines/{slug}/approve and POST .../pipelines/{slug}/reject take no request body and require create (MANAGER+). Approve changes a proposed pipeline to active and returns 200{ "slug", "status": "active" }. Reject soft-deletes the proposed pipeline and returns 200 with the resulting status. A pipeline that is not in the expected state returns 409; a missing pipeline is 404; other store failures are 500. POST .../pipelines/{slug}/disable and /enable take no request body and require manage (OWNER/ADMIN). Disable returns 200 with { "slug", "status": "disabled", "cancelled_runs": N }; enable returns 200 with { "slug", "status": "active" }. They return 404 for a missing pipeline and 500 for persistence failures. A disabled or proposed pipeline cannot be run; the run endpoint reports 409 with its current status.

Schedules and public waitpoint completion

Schedule administration is also available through the pipeline API. Create uses POST .../pipeline-schedules with a JSON body containing cron_expr and either target_pipeline_slug or target_pipeline_id; it returns 201 with the schedule object. PATCH .../pipeline-schedules/{scheduleId} merges the supplied fields into the existing schedule and returns 200 with the schedule object. Both return 400 for invalid JSON, missing targets, or invalid cron/timezone data; missing schedules or targets are 404; backend failures are 500 and an unwired schedule store is 503. Update requires manage; create requires create. See Schedules for the request fields and complete schedule representation. DELETE .../pipeline-schedules/{scheduleId} requires manage, returns 204 on success, and soft-deletes the schedule. POST .../pipeline-schedules/{scheduleId}/run requires create, takes no body, and force-fires the stored schedule inputs without changing its cron cadence. It returns the normal run result (200), or 404 for a missing/foreign schedule or target, 409 for a missing pinned version, 429 for the concurrency limit, 500 for execution/storage errors, and 503 when the schedule store or runner is unavailable. The public POST /api/v1/waitpoint-tokens/{token} callback has no workspace session requirement; the high-entropy token authenticates it. Its optional body is { "approved": true, "payload": <any JSON> }; approved defaults to true. It returns 200{ "ok": true, "approved": true|false }, 400 for an empty token or invalid JSON, 409 when the waitpoint was already decided or expired, 500 for completion failure, and 503 when the waitpoint backend is unavailable.

Step overrides

GET /api/v1/workspaces/{workspaceId}/pipelines/{slug}/overrides

Auth: member. A missing pipeline is 404; pipeline or override lookup failure is 500. Statuses: 200 OK; 401 without authentication; 403 without membership; 404 for a missing pipeline or key; 503 for an unavailable state store; 500 for lookup failure. Response: 200 OK{ "overrides": [...] }, with each row containing step_id and optional prompt and model_override.

PUT /api/v1/workspaces/{workspaceId}/pipelines/{slug}/steps/{stepId}/override

Auth: member plus update (MANAGER+). stepId is required (400). Body is { "prompt": "...", "model_override": "..." }; at least one of the two non-empty fields is required (400). Invalid JSON is 400. A missing pipeline or step in the current definition is 404; an invalid stored definition is 500; save failure is 500. Response: 200 OK{ "ok": true, "step_id": "<stepId>" }.

DELETE /api/v1/workspaces/{workspaceId}/pipelines/{slug}/steps/{stepId}/override

Auth: member plus update (MANAGER+). The response is 200 OK with { "ok": true, "step_id": "<stepId>" }. A missing pipeline is 404; delete failure is 500. The endpoint is idempotent for an absent override.

Pipeline state

GET /api/v1/workspaces/{workspaceId}/pipelines/{slug}/state?schedule_id=S

Auth: member. Without schedule_id, all schedule buckets are returned; with it, only that bucket is returned. An empty value selects the shared manual/webhook bucket. 200 OK returns { "slug": "...", "buckets": [{ "schedule_id", "entries": [{ "key", "value", "updated_at" }] }] }. A missing pipeline is 404; an unavailable state store is 503; lookup failure is 500.

PUT /api/v1/workspaces/{workspaceId}/pipelines/{slug}/state/{key}

Auth: member plus manage (OWNER/ADMIN). Body is { "value": "...", "schedule_id": "..." }; omitted schedule_id means the manual/webhook bucket. Empty keys or keys longer than 128 characters are 400; invalid JSON is 400. A missing pipeline is 404; an unavailable state store is 503; write failure is 500. Response: 200 OK{ "slug", "schedule_id", "key", "value" }. Statuses: 200 OK; 400 for invalid JSON or key; 401 without authentication; 403 without manage permission; 404 for a missing pipeline; 503 for an unavailable state store; 500 for write failure.

DELETE /api/v1/workspaces/{workspaceId}/pipelines/{slug}/state/{key}?schedule_id=S

Auth: member plus manage (OWNER/ADMIN). Empty key is 400. The response is 200 OK{ "slug", "schedule_id", "key", "deleted": true }. An absent key in the selected bucket is 404; a missing pipeline is 404; an unavailable state store is 503; delete failure is 500. Statuses: 200 OK; 400 for an empty key; 401 without authentication; 403 without manage permission; 404 for a missing pipeline or key; 503 for an unavailable state store; 500 for delete failure.

DELETE /api/v1/workspaces/{workspaceId}/pipelines/{slug}/state?schedule_id=S

Auth: member plus manage (OWNER/ADMIN). The operation clears only the selected bucket (empty schedule_id selects manual/webhook state). 200 OK returns { "slug", "schedule_id", "removed": N }. A missing pipeline is 404; an unavailable state store is 503; clear failure is 500. Statuses: 200 OK; 401 without authentication; 403 without manage permission; 404 for a missing pipeline; 503 for an unavailable state store; 500 for clear failure.

Single-step execution

POST /api/v1/workspaces/{workspaceId}/pipelines/{slug}/step_run

Auth: member plus create (MANAGER+). This executes one agent_run, http, script, or transform step against a fixture; it does not traverse the DAG or create a pipeline run record. The JSON body is:
step_id is required (400); invalid JSON is 400; a missing pipeline or step is 404; a non-supported step type is 400; a non-active pipeline is 409; an unwired runner is 503; model resolution failure is 500; step execution failure is 502. Response: 200 OK with step_id, step_type, output, valid, optional validation_reason, cost_usd, tokens_in, tokens_out, duration_ms, simulated: true, and optional warnings. Agent steps additionally include adapter, model, and rendered_prompt. Unseeded upstream output references are reported in warnings.

Pipeline tags

PUT /api/v1/workspaces/{workspaceId}/pipelines/{slug}/tags

Auth: member plus create at the route and update in the handler (MANAGER+). Body is { "tags": ["tag-a", "tag-b"] }; an empty list or invalid JSON is 400. A missing pipeline is 404; exceeding the tag-store limit is 400; other lookup/store failures are 500. Response: 200 OK{ "tags": ["..."] } with the routine’s complete tag set after the add.

DELETE /api/v1/workspaces/{workspaceId}/pipelines/{slug}/tags/{tag}

Auth: member plus create at the route (MANAGER+). 200 OK returns { "ok": true }. A missing pipeline is 404; tag removal failure is 500. Removing an absent tag is otherwise successful.