Documentation menu

Jetty REST API Reference

The Jetty REST API is one HTTP surface. The primary way in is the tasks API: deploy a runbook as a task, start runs of it, and poll the results. An OpenAI-compatible chat-completions endpoint is also provided for compatibility, so OpenAI-shaped SDKs and tools can point at Jetty without changes. A handful of smaller APIs cover scheduling, async callbacks, and source control. Everything here authenticates the same way and records every call as a run (the API calls them trajectories).

Base URL & auth

The base URL is https://flows-api.jetty.io. Authenticate every request with a bearer token:

Authorization: Bearer $JETTY_API_TOKEN

Grab a token from Settings → API keys, or run the jetty-setup skill from an agent with the Jetty plugin to write it to ~/.config/jetty/token. Prefer not to hand-roll requests? The typed SDK and the MCP tool layer wrap everything below.

EndpointMethodWhat it does
/api/v1/run/{collection}/{task}POSTThe primary endpoint. Start a run of a deployed task; async — returns an id you poll.
/api/v1/tasks/{collection}/{task}GET / PUTRead and update a deployed task's configuration.
/v1/chat/completionsPOSTOpenAI-compatible compatibility layer: passthrough LLM proxy or a runbook run, depending on the body.
/api/v1/routines/{collection}/{task}GET / POST / PATCH / DELETECRUD and lifecycle for scheduled runs.
/api/v1/trajectories/{collection}/{task}GETList and get runs to monitor progress.
webhook_url (run field)POST to youCompletion callback for async runs. There is no webhooks endpoint; you pass a URL when you start a run or create a schedule.
/v1/github/pull-requestsPOSTOpen a GitHub PR from a workflow.

Tasks API

The tasks API is the front door. Deploy a runbook as a task once (from the web app or your agent), and it becomes a stable, named thing you can run, schedule, and override per run — without resending the runbook each time:

curl -X POST https://flows-api.jetty.io/api/v1/run/my-collection/cbc-homepage-summary \
  -H "Authorization: Bearer $JETTY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "init_params": {
      "vars": { "url": "https://www.cbc.ca/" }
    }
  }'

The call is async by design: it returns an id, and you poll the run for progress and results. The init_params merge over the task's stored defaults (caller wins). The merge is shallow: each top-level key you send replaces the stored one whole. Sending vars replaces the task's entire vars object, so include every variable the runbook needs, not just the ones you're changing. The body also takes secret_params (injected as environment variables, never stored in the run) and webhook_url / webhook_secret (see Webhooks). For short jobs, /api/v1/run-sync/{collection}/{task} waits for the result instead. The full task surface is in the machine instructions.

Chat Completions API

The POST /v1/chat/completions endpoing enables the OpenAI API so you can point at Jetty without changes. It is secondary to the tasks API. There are two modes.

Passthrough mode (no jetty block)

Without a jetty block, the endpoint is a standard LLM proxy across 100+ providers. Send it like any OpenAI chat request, and every call is recorded as a run you can replay and grade later.

curl https://flows-api.jetty.io/v1/chat/completions \
  -H "Authorization: Bearer $JETTY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5.6-terra",
    "messages": [
      { "role": "user", "content": "Summarize the theory of relativity in 3 bullets" }
    ]
  }'

Runbook mode (add a jetty block)

Add a jetty block with runbook: true, a collection and a task name, and the same endpoint provisions a sandbox, runs an agent against your runbook, and returns structured results. The runbook markdown travels inline as the system message, and the user message is passed to the runbook as {{prompt}}. There is no URL field: to run a runbook from a URL, fetch it and send it inline. The task is created in the collection on the first call.

RUNBOOK=$(curl -s https://raw.githubusercontent.com/org/repo/main/RUNBOOK.md)

jq -n --arg runbook "$RUNBOOK" '{
  model: "anthropic/claude-sonnet-5",
  messages: [
    { role: "system", content: $runbook },
    { role: "user", content: "Research the EV charging market" }
  ],
  jetty: {
    runbook: true,
    collection: "my-collection",
    task: "ev-market-research",
    template_variables: { region: "north-america" }
  }
}' | curl https://flows-api.jetty.io/v1/chat/completions \
  -H "Authorization: Bearer $JETTY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d @-

template_variables fill the runbook's other {{vars}}. Runs that go past the sync window (1200 s by default, set with jetty.timeout_hint) return 202 with jetty_metadata.poll_url. Add jetty.webhook_url to get a 202 straight away and a webhook when the run finishes.

See the full walkthrough in the get-started guide.

Scheduling API

Scheduled runs are configured under the routines endpoint (the API's name for a schedule). Create, list, update, and delete them, or trigger one immediately with run-now.

POST   /api/v1/routines/{collection}/{task}               # create
GET    /api/v1/routines/{collection}[/{task}]             # list
PATCH  /api/v1/routines/{collection}/{task}/{name}        # update
DELETE /api/v1/routines/{collection}/{task}/{name}        # delete
POST   /api/v1/routines/{collection}/{task}/{name}/run-now  # run now

A schedule stores init_params_overrides (same shallow merge as a run; keys must be declared in the task's init_params, or the call is a 400) and can carry its own webhook_url / webhook_secret, which apply to every run it fires.

Webhooks

Pass webhook_url when you start a run and Jetty POSTs to it once the run finishes, whether it completed or failed. It's the alternative to polling. There is no registration endpoint. The URL rides on the request:

  • POST /api/v1/run/{collection}/{task}: top-level webhook_url and webhook_secret (JSON or multipart fields).
  • POST /v1/chat/completions in runbook mode: jetty.webhook_url and jetty.webhook_secret. With a webhook set (and stream off) the call returns 202 straight away instead of waiting for the run.
  • Schedules: webhook_url and webhook_secret on the routine, applied to every run it fires.

/api/v1/run-sync doesn't send webhooks. It ignores both fields, because the response already carries the result.

curl -X POST https://flows-api.jetty.io/api/v1/run/my-collection/cbc-homepage-summary \
  -H "Authorization: Bearer $JETTY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "init_params": { "vars": { "url": "https://www.cbc.ca/" } },
    "webhook_url": "https://ops.example.com/jetty",
    "webhook_secret": "'"$WEBHOOK_SECRET"'"
  }'

Signed vs. unsigned

webhook_secret is optional. Setting a URL is enough to get a delivery. The secret only decides whether the delivery is signed:

HeaderWith a secretWithout a secret
Content-Type: application/jsonSentSent
X-Mise-TimestampSent (Unix seconds)Sent (Unix seconds)
X-Mise-Trajectory-IdSentSent
X-Mise-SignatureSent: hex HMAC-SHA256(secret, "{timestamp}.{body}")Absent

Unsigned deliveries are fine for a throwaway endpoint or a test. For anything that acts on the payload, set a secret and make your receiver fail closed: reject a request with no X-Mise-Signature instead of treating the header as optional. Otherwise anyone who learns the URL can forge a delivery. To verify, recompute the HMAC over the timestamp header, a literal ., and the raw request body (not re-serialized JSON), compare it in constant time, and reject stale timestamps to stop replays:

import hashlib, hmac, time

def verify(headers, raw_body: bytes, secret: str, max_age=300) -> bool:
    sig = headers.get("X-Mise-Signature")
    ts = headers.get("X-Mise-Timestamp")
    if not sig or not ts:
        return False  # fail closed on unsigned deliveries
    if abs(time.time() - int(ts)) > max_age:
        return False
    expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)

Payload & delivery

  • Body: the full run (trajectory) JSON, the same document GET /api/v1/trajectory/{collection}/{task}/{trajectory_id} returns. Check its status (completed or failed). Your webhook_url appears in it, but webhook_secret never does.
  • Retries: up to 3 attempts with exponential backoff (1 s, then 2 s) and a 30 s timeout each. Any response below 400 counts as delivered.
  • Best effort: a failed delivery is logged on the run but never fails the run. Poll the run if you need a guarantee.

This callback is separate from the webhook step activity, which is an outbound HTTP call a workflow makes mid-run.

GitHub PR API

Open pull requests programmatically from a workflow, so an agent that produces code or config changes can land them through review rather than commit directly.

Monitoring a run

List or get runs to track one from start to finish. Each run captures the step inputs and outputs, so you can watch progress, inspect results, and grade them.

curl https://flows-api.jetty.io/api/v1/trajectories/my-collection/cbc-homepage-summary \
  -H "Authorization: Bearer $JETTY_API_TOKEN"

Behind the endpoint

One API surface, five components:

  • Passthrough proxy: the OpenAI-compatible front door to 100+ providers.
  • Workflow engine: runs multi-step DAGs. See Running AI Workloads.
  • Runbook engine: spins up sandboxed agents to execute markdown runbooks.
  • Persistence: a relational database for metadata plus object storage for artifacts.
  • Tracing: every execution is recorded as a run — the full trace of what ran, in what order, with which inputs and outputs. That's what makes a run replayable and gradeable.

Durable execution

Workflows are backed by a durable execution engine, which gives you automatic retries and exactly-once semantics. Each runbook run gets its own isolated sandbox, so concurrent runs can't see or step on each other.