Documentation menu

Machine instructions

The machine-level counterpart to workflows & steps: the endpoints, payloads, and config schemas an agent or programmatic caller needs to create tasks, run runbooks, and read the results. The API reference covers the chat-completions endpoint in depth.

Base URL https://flows-api.jetty.io, header Authorization: Bearer $JETTY_API_TOKEN. Tokens resolve everywhere in the same order: explicit value → JETTY_API_TOKEN env → ~/.config/jetty/token. The full OpenAPI spec is unauthenticated at flows-api.jetty.io/openapi.json.

Creating tasks

A task is a deployed runbook: named, addressable as {collection}/{task}, runnable.

POST /api/v1/tasks/{collection}          # 201; body: {name, description, workflow, is_private, has_file_uploads}
PUT  /api/v1/tasks/{collection}/{task}   # partial update; bumps revision
GET  /api/v1/tasks/{collection}/{task}
DELETE /api/v1/tasks/{collection}/{task}

GET works on any public task, and the instruction inside the returned workflow is the runbook markdown — every public runbook is also a template.

The workflow for a runbook task is one step running the runbook activity, with every execution field indirected through init_params:

{
  "init_params": {
    "agent": "claude-code", "model": "anthropic/claude-sonnet-5",
    "model_provider": "openrouter", "snapshot": "python312-uv",
    "instruction": "<the RUNBOOK.md content>", "vars": {}, "file_paths": []
  },
  "step_configs": {
    "run": {
      "activity": "runbook",
      "agent_path": "init_params.agent", "model_path": "init_params.model",
      "snapshot_path": "init_params.snapshot", "instruction_path": "init_params.instruction",
      "template_variables_path": "init_params.vars", "files_path": "init_params.file_paths",
      "cpus": 4, "memory": "8G", "timeout_sec": 7200
    }
  },
  "steps": ["run"]
}

The *_path indirection is the point: because the step reads its config through paths into init_params, any of these can be overridden per run without redeploying. SDK: createTask(collection, name, workflow, description?) / updateTask.

Running runbooks

POST /api/v1/run/{collection}/{task}        # async; 200 {workflow_id, metadata}
POST /api/v1/run-sync/{collection}/{task}   # blocks; returns the trajectory
POST /v1/chat/completions                   # OpenAI-compatible; `jetty` block selects runbook mode

Run endpoints accept JSON or multipart. JSON body fields: init_params, secret_params, webhook_url, webhook_secret, subscription_credential; multipart adds repeatable files. Two of these — secret_params and subscription_credential — are parsed from the raw body and don't appear in the published OpenAPI schema, so don't trust openapi.json as exhaustive here. (Trial keys are applied automatically when the collection is eligible — there is no flag.) The chat-completions form flips into runbook mode with jetty.runbook: true, and the runbook markdown travels inline as the system message — there is no URL-reference field; to run a runbook from a URL, fetch it and send it inline. The full jetty block: runbook, collection, task, agent, model_provider, snapshot, timeout_sec, cpus, memory, template_variables, agent_env, mcp_servers, file_paths, files, webhook_url, webhook_secret, timeout_hint. When a run outlasts the sync window (default 1200 s, tunable via jetty.timeout_hint) it returns 202 with jetty_metadata.poll_url and logs_url.

An async run responds immediately with workflow_id: "{collection}-{task}--{trajectory_id}". The trajectory id is the short hex suffix after the last -- — poll GET /api/v1/trajectory/{collection}/{task}/{trajectory_id} or stream live logs from GET /api/v1/workflows-logs-stream/{workflow_id} (SSE). A synchronous run that exceeds ~100 s returns a Cloudflare 524 while the run continues server-side: treat it as “still working”, poll the trajectory, never retry the POST — a retry starts a second run.

Chat-completions without a jetty block is passthrough mode: a plain LLM proxy routed across 100+ providers, every call recorded as a trajectory. OpenRouter routing is tunable per collection via the OPENROUTER_PROVIDER_ORDER and OPENROUTER_ALLOW_FALLBACKS environment variables.

SDK equivalents: runWorkflow, runWorkflowSync, runWithFiles, runAndWait(collection, task, initParams, {pollMs: 2000, timeoutMs: 1_800_000}).

Reading trajectories

GET  /api/v1/db/trajectory/{c}/{t}/{id}        # one trajectory
GET  /api/v1/db/trajectories/{c}/{t}           # list (page, limit, status, include_archived)
POST /api/v1/db/trajectories/{c}/{t}/query     # filtered list: label_key/label_value, param_key/param_value, status, time range
GET  /api/v1/db/stats/{collection}/{task}      # aggregate numbers
GET  /api/v1/file/{storage_path}               # one artifact (paths come from step outputs)
GET  /api/v1/trajectory/{c}/{t}/{id}/download  # everything as a zip

The list response is wrapped as {trajectories, total, page, limit, has_more}, and a trajectory's steps are an object keyed by step name, not an array. Label filtering goes through the query endpoint, not the plain list. Labels are key/value/author triples attached per trajectory — the raw material for cohorts, sweeps, and eval results (graded: pass, config: warm, cohort: 2026-08).

Defining inputs and initial parameters

init_params merge shallowly, caller over task definition: deploy-time values are defaults, run-time values override. Inside a step, each parameter resolves in priority order: literal in step config → *_path in step config → literal in init_params*_path in init_params → declared default.

GET /api/v1/tasks/{c}/{t}/init-params-schema returns the declared keys with defaults and type hints — use it to build forms or validate calls. Template variables ride in init_params.vars; attached files always land in init_params.file_paths (the list exists on every trajectory, empty or not).

Files in and out

Attach files as multipart files on the run endpoints (runWithFiles in the SDK), or pre-stage with POST /api/v1/sandbox/upload (up to 10 files, 50 MB each) and pass the returned storage paths. Uploads land at init_params.file_paths[] — a list, not named keys — and are mounted at /app/assets/ in the sandbox; zips are auto-extracted.

Everything the agent writes to /app/results/ is persisted to collection storage as results_files, with the frontmatter's primary_outputs surfaced first. Fetch via GET /api/v1/file/{storage_path} or the trajectory zip download above.

Environment variables and secrets

Collection-level env vars are set via Settings or PATCH /api/v1/collections/{c}/environment and injected into every run's sandbox. Per-run secret_params merge over the collection's env at launch and are never persisted to the trajectory. Runbooks declare required env vars in a secrets: frontmatter block, which powers the plugin's check-secrets preflight ({configured, missing, ready}).

Attaching MCP servers

Runbook tasks declare MCP servers in the step config, as a local command or a remote HTTP endpoint:

"mcp_servers": {
  "playwright": { "command": "npx", "args": ["@playwright/mcp@latest"] },
  "figma": { "url": "https://mcp.figma.com/mcp", "type": "http" }
}

Bearer tokens for HTTP servers come from mcp_auth_tokens (per-server) or mcp_auth_token (all servers), both of which accept path expressions so a token can flow in from init_params or an earlier step. MCP support varies by runtime: claude-code, gemini-cli, and opencode wire the config in; codex currently ignores it.

Sweeping agents and models

There is no first-class sweep primitive — but the *_path indirection makes one unnecessary for small matrices: the same task runs under any {agent, model, model_provider} combination passed through init_params.

for (const cfg of [
  { agent: "claude-code", model: "anthropic/claude-sonnet-5", model_provider: "openrouter" },
  { agent: "codex",       model: "gpt-5.5",                   model_provider: "openai" },
]) {
  const run = await jetty.runAndWait("acme", "triage", { ...cfg, vars: { ticket } });
  await jetty.addLabel("acme", "triage", run.trajectory_id, "sweep.agent", cfg.agent, "sweep");
}

Label each run with its configuration, then compare via the labeled trajectory list or get-stats. For server-side fan-out, the list_emit_await activity maps a list into parallel child runs (max_concurrency, continue_on_error) and extract_from_trajectories aggregates the results. Keep the grader fixed while sweeping — same evaluation task across every combination, or the numbers don't compare.

Defining runbooks

Frontmatter is the machine-readable contract:

FieldValuesNotes
versionsemver stringbumped by /optimize-runbook on applied edits
evaluationrubric | programmatichow the run self-checks
agentclaude-code (default), codex, gemini-cli, opencode, hermes, goose, pithe runtime
model, model_providerprovider-qualified slug + gatewaymust agree with each other
snapshote.g. python312-uv, prism-playwrightpre-built sandbox image
timeout_sec60–7200, default 7200agent wall-clock budget
primary_outputsfile list, relative to results dirsurfaced first in the trajectory
secrets{NAME: {env, description, required}}env vars the run expects

The body must contain the objective, the output manifest, a ## Parameters table for {{vars}}, numbered steps, an evaluation step, a bounded iteration step, and a final verification checklist — the structure /create-runbook scaffolds and validates. Deploying is createTask with the workflow shape above; the transformation is invertible, so a task's runbook can always be recovered from init_params.instruction.

Timeouts

  • timeout_sec (step config or frontmatter): the agent's execution budget. Default and max 7200 s (2 h). On expiry the agent process is killed and the step fails.
  • Sync HTTP calls: ~100 s at the edge before a 524. The run continues; poll, don't retry.
  • Chat-completions sync window: jetty.timeout_hint, default 1200 s, then 202 + poll URL.
  • Client-side: SDK runAndWait gives up polling after 30 min by default (timeoutMs) — raise it for long runbooks.

Long-running runbooks should write output incrementally so partial results survive a timeout.

Snapshots

A snapshot is a pre-built sandbox image. Cold-building an environment (base image + apt + pip + agent install) costs minutes per run; a snapshot cuts sandbox bring-up to seconds and pins the toolchain. Environment resolution order is snapshot → custom image → generated Dockerfile (base_image + apt_packages + pip_packages — these three apply only on the Dockerfile path; a snapshot already contains its packages).

Named snapshots: python312-uv (default — Python 3.12 + uv) and prism-playwright (browser automation). The snapshot rides through init_params, so a single task can be pointed at a different environment per run.

Webhooks

Pass webhook_url (and webhook_secret) on any run or routine to get a POST when the run completes — the alternative to polling, and how async runs hand control back to your system.

  • Payload: the full trajectory JSON.
  • Headers: X-Mise-Signature, X-Mise-Timestamp, X-Mise-Trajectory-Id.
  • Signature: HMAC-SHA256(secret, "{timestamp}.{body}"), hex-encoded. Verify before trusting.
  • Delivery: up to 3 attempts, exponential backoff, 30 s per attempt. Webhook failure never fails the run.

Distinct from the webhook step activity, which is an outbound HTTP call made mid-workflow.

Scheduling runs

A routine is a scheduled run of a task. Same workflow as a manual run — same steps, same config, same evals; there is no separate scheduled code path — each firing producing its own trajectory tagged with triggered_by_routine_id.

POST   /api/v1/routines/{collection}/{task}          # create
GET    /api/v1/routines/{collection}[/{task}]        # list (includes live next_run_at)
PATCH  /api/v1/routines/{c}/{t}/{name}               # update any subset
POST   /api/v1/routines/{c}/{t}/{name}/pause|resume|run-now
GET    /api/v1/routines/{c}/{t}/{name}/runs          # trajectories this routine fired

Create body:

{
  "name": "nightly-eval",
  "cadence": { "type": "daily", "hour_utc": 9, "minute_utc": 0 },
  "init_params_overrides": { "vars": { "mode": "full" } },
  "webhook_url": "https://ops.example.com/jetty",
  "overlap_policy": "skip"
}

Cadence is a structured enum — manual | hourly | daily | weekdays | weekly with hour_utc, minute_utc, and day_of_week — not raw cron. manual saves the configuration as a run-now preset with no schedule. init_params_overrides are validated against the task's declared init_params (unknown keys are a 400). overlap_policy defaults to skip: a firing is dropped rather than stacked if the previous run is still going. Pausing preserves the schedule; run-now bypasses it and returns a normal workflow_id.

The two standing use cases: recurring jobs (daily report, weekly digest, scheduled data pull) and regression detection — a runbook that passed at 84% last month can quietly regress under model drift, and a routine plus a webhook turns that into an alert instead of a discovery.


Next: the conceptual treatment in workflows & steps, the chat-completions endpoint in the API reference, or the activity catalog in the step library.