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_overridesbaked in (cadencemanual) - 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
| Cadence | Fires | Required params | Use it for |
|---|---|---|---|
manual | Never automatically — only via run-now | none | Saving a named invocation preset with overrides |
hourly | Every hour at minute_utc | minute_utc (defaults to 0) | High-frequency polling, monitoring jobs |
daily | Every day at hour_utc:minute_utc UTC | hour_utc | Morning reports, nightly batch work |
weekdays | Mon–Fri at hour_utc:minute_utc UTC | hour_utc | Business-hours digests, weekday-only ETL |
weekly | One day per week at hour_utc:minute_utc UTC | hour_utc, day_of_week | Weekly 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
- Routines API Reference — Full endpoint and Pydantic shape reference
- Agentic Workflows — How
FlowWorkflow.runworks - Writing Runbooks — Author the task body that a routine schedules
- CI Integration — Trigger runbooks from GitHub Actions instead of (or alongside) Mise routines