Skip to main content

Scheduling Routines

A routine is a named, persistent schedule that fires an existing Mise task on a recurring cadence. Routines build on the same FlowWorkflow.run pipeline as your one-shot HTTP runs, so anything you can launch with POST /run/{collection}/{task} you can put on a schedule — with the option to override init_params per routine.

If you have not run a workflow on Jetty yet, start with the Agentic Workflows Quickstart. Routines assume an existing task you can already trigger manually.

When You Need This

A bare task is fine when you launch it by hand or from your own glue code. Reach for a routine when:

  • The task should fire on a recurring cadence without any external scheduler
  • A team relies on fresh outputs every morning / hour / weekday without checking the UI
  • You want to save a named invocation preset with init_params_overrides baked in (cadence manual)
  • You need to pause / resume scheduled execution without deleting and re-creating
  • You want trajectories to be queryable by routine — every fire tags triggered_by_routine_id

Routines are not a creation surface for tasks. They reference a task that already exists and only override its init_params. File uploads still go through POST /api/v1/files/upload (or the multipart form on the run endpoints) — a routine references the resulting paths, it cannot upload on your behalf.

Cadences at a Glance

CadenceFiresRequired paramsUse it for
manualNever automatically — only via run-nownoneSaving a named invocation preset with overrides
hourlyEvery hour at minute_utcminute_utc (defaults to 0)High-frequency polling, monitoring jobs
dailyEvery day at hour_utc:minute_utc UTChour_utcMorning reports, nightly batch work
weekdaysMon–Fri at hour_utc:minute_utc UTChour_utcBusiness-hours digests, weekday-only ETL
weeklyOne day per week at hour_utc:minute_utc UTChour_utc, day_of_weekWeekly rollups, sprint reports

All times are UTC. The Spot UI translates to your browser timezone for display only.

Default overlap policy is skip: if a previous run is still executing when the next fire is due, the next fire is dropped. This avoids stampedes on long-running tasks.

How a Routine Fires

The routine record holds the cadence, overrides, and metadata. For non-manual cadences, a scheduled trigger is registered behind the scenes. When it fires, it starts the same FlowWorkflow.run your HTTP run endpoint does — the only difference is that the resulting trajectory is tagged with triggered_by_routine_id so you can filter for it later.

For more on how the run pipeline works under the hood, see Agentic Workflows.

Step-by-Step Walkthrough

The flow below assumes you already have a task you can trigger with POST /api/v1/run/{collection_name}/{task_name}.

1. Inspect the task's init_params schema

Before creating a routine, look up which keys you can override. The helper endpoint returns one entry per init_param defined on the task workflow:

curl -s "https://flows-api.jetty.io/api/v1/tasks/$COLLECTION/$TASK/init-params-schema" \
-H "Authorization: Bearer $JETTY_API_TOKEN"
{
"keys": [
{"name": "model", "default": "gpt-4o", "type_hint": "string"},
{"name": "tenant_filter", "default": null, "type_hint": "string"},
{"name": "sample_size", "default": 10, "type_hint": "integer"}
]
}

Any key you put in init_params_overrides must appear in this list. Unknown keys are rejected with a 400 and the offending keys echoed back.

2. Create the routine

POST /api/v1/routines/{collection_name}/{task_name} with a RoutineCreate body. The example below schedules nl-to-sql-regression to run every weekday at 09:00 UTC, overriding sample_size:

curl -X POST "https://flows-api.jetty.io/api/v1/routines/$COLLECTION/$TASK" \
-H "Authorization: Bearer $JETTY_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "morning-regression",
"cadence": {
"type": "weekdays",
"hour_utc": 9,
"minute_utc": 0
},
"init_params_overrides": {
"sample_size": 50
},
"secret_params": {},
"paused": false
}'

The response is a RoutineRead — the same shape as the create body plus id, resolved next_run_at, and the overlap_policy.

3. Run it once on demand

While you are dialing in overrides, fire the routine immediately without waiting for the next cadence tick:

curl -X POST "https://flows-api.jetty.io/api/v1/routines/$COLLECTION/$TASK/morning-regression/run-now" \
-H "Authorization: Bearer $JETTY_API_TOKEN"

The response shape mirrors the existing run endpoint (WorkflowResponse) — including workflow_id and the trajectory ID — so the same polling code works.

4. Pause, resume, update

Pausing keeps the row but stops the schedule from firing:

curl -X POST "https://flows-api.jetty.io/api/v1/routines/$COLLECTION/$TASK/morning-regression/pause" \
-H "Authorization: Bearer $JETTY_API_TOKEN"

Resume with the symmetric /resume endpoint. To change the cadence or the overrides, use PATCH:

curl -X PATCH "https://flows-api.jetty.io/api/v1/routines/$COLLECTION/$TASK/morning-regression" \
-H "Authorization: Bearer $JETTY_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"cadence": {"type": "daily", "hour_utc": 13, "minute_utc": 30}}'

A running execution is not interrupted. The new cadence applies to the next fire.

5. Inspect past runs

Routine-fired trajectories are tagged with triggered_by_routine_id. List them with:

curl -s "https://flows-api.jetty.io/api/v1/routines/$COLLECTION/$TASK/morning-regression/runs" \
-H "Authorization: Bearer $JETTY_API_TOKEN"

This is the same shape as any other trajectory list, filtered server-side.

Python Example

The Mise REST API is plain JSON — any HTTP client works. Here is a typed example using httpx:

import httpx

BASE = "https://flows-api.jetty.io"
TOKEN = "<your-jetty-api-token>"
COLLECTION = "my-org"
TASK = "nl-to-sql-regression"
ROUTINE = "morning-regression"

headers = {"Authorization": f"Bearer {TOKEN}"}

with httpx.Client(base_url=BASE, headers=headers, timeout=30.0) as http:
# Create
http.post(
f"/api/v1/routines/{COLLECTION}/{TASK}",
json={
"name": ROUTINE,
"cadence": {"type": "weekdays", "hour_utc": 9, "minute_utc": 0},
"init_params_overrides": {"sample_size": 50},
},
).raise_for_status()

# Fire once on demand
response = http.post(
f"/api/v1/routines/{COLLECTION}/{TASK}/{ROUTINE}/run-now"
)
response.raise_for_status()
workflow_id = response.json()["workflow_id"]
print("started workflow:", workflow_id)

# Inspect runs
runs = http.get(
f"/api/v1/routines/{COLLECTION}/{TASK}/{ROUTINE}/runs"
).json()
for r in runs:
print(r["trajectory_id"], r["status"], r["created_at"])

For the full request and response shapes — including every endpoint and the validation rules — see the Routines API Reference.

Troubleshooting

400 unknown init_params keys: [...]

The keys in init_params_overrides must already exist on the task's workflow.init_params. Hit GET /api/v1/tasks/{collection}/{task}/init-params-schema and reconcile against the keys list. If you really want to add a new param, edit the task workflow first; routines do not extend the surface area.

400 daily cadence requires hour_utc

daily, weekdays, and weekly all require hour_utc. weekly additionally requires day_of_week. manual rejects every cadence param except type.

404 on a routine you just created

Routines are scoped to (collection_id, task_id). An API key bound to a different collection cannot see them — you will get 404, not 403, by design. Confirm the Authorization header is for the right collection.

A scheduled fire skipped

The default overlap policy is skip: if the previous run is still executing when the next cadence tick lands, the new fire is dropped. Long-running runbooks plus a tight cadence are the usual cause. Either lengthen the cadence or speed up the task.

next_run_at is null

Manual routines never have a next_run_at — they are saved invocation presets and only fire on run-now. For non-manual routines, null means the scheduler has not yet computed the next fire (transient — re-fetch in a few seconds) or the routine is paused.

Schedule survives a routine that no longer makes sense

If the underlying task was edited and an override key disappeared, the routine becomes invalid. v1 surfaces this as a warning on the routine GET response. Either edit the routine to remove the stale override or delete it.

Secrets need rotating

secret_params is encrypted at rest and never logged, but rotation is currently a manual workflow: PATCH the routine with new secret_params, then revoke the old credential at the source. Treat routine secrets the same way you treat any long-lived credential.

See Also