Skip to content

Schedules API

All schedule endpoints are project-scoped. The actions are enable / disable / trigger, not pause / resume.

GET /api/v1/projects/{slug}/schedules

Role: viewer.

Query params: scheduler (e.g. celery-beat, apscheduler), enabled (bool), cursor, limit.

{
"items": [
{
"id": "...",
"project_id": "...",
"name": "email.daily-digest",
"scheduler": "celery-beat",
"trigger_type": "cron",
"trigger": {"minute": "0", "hour": "9", "day_of_week": "*"},
"task": "email.send_daily_digest",
"args": [],
"kwargs": {"locale": "en-US"},
"enabled": true,
"last_run_at": "...",
"next_run_at": "...",
"created_at": "...",
"updated_at": "..."
}
],
"next_cursor": "..."
}
GET /api/v1/projects/{slug}/schedules/{schedule_id}

Role: viewer.

POST /api/v1/projects/{slug}/schedules

Role: operator. CSRF-protected.

{
"name": "email.daily-digest",
"scheduler": "celery-beat",
"trigger_type": "cron",
"trigger": {"minute": "0", "hour": "9", "day_of_week": "*"},
"task": "email.send_daily_digest",
"args": [],
"kwargs": {"locale": "en-US"},
"enabled": true
}

args and kwargs JSON are each capped at 64 KiB.

PATCH /api/v1/projects/{slug}/schedules/{schedule_id}

Role: operator. CSRF-protected. Any subset of the create fields.

DELETE /api/v1/projects/{slug}/schedules/{schedule_id}

Role: admin. CSRF-protected.

POST /api/v1/projects/{slug}/schedules/{schedule_id}/enable
POST /api/v1/projects/{slug}/schedules/{schedule_id}/disable
POST /api/v1/projects/{slug}/schedules/{schedule_id}/trigger

Role: operator. CSRF-protected. trigger fires the underlying task once immediately (the schedule’s cadence is untouched) and returns the schedule. Manual triggers are attributed: the resulting fire-history row carries the triggering user’s id.

GET /api/v1/projects/{slug}/schedules/{schedule_id}/fires

Role: viewer. Returns the schedule’s recent fires, newest first. limit defaults to 100, capped at 1000. Recorded for schedules fired by z4j-scheduler.

[
{
"id": "...",
"fire_id": "...",
"schedule_id": "...",
"command_id": "...",
"status": "acked_success",
"scheduled_for": "...",
"fired_at": "...",
"acked_at": "...",
"latency_ms": 594,
"error_code": null,
"error_message": null,
"triggered_by_user_id": null
}
]

status is one of pending, delivered, buffered, acked_success, acked_failed, failed. triggered_by_user_id names the operator behind a manual “run now” fire; null means a scheduler-driven cadence fire. See Schedule fire history for lifecycle and retention.

GET /api/v1/projects/{slug}/schedules/{schedule_id}/misfires

Role: viewer. Returns the schedule’s detected misfires, newest first (limit defaults to 50, capped at 1000). A misfire is a system-detected “this enabled schedule missed its slot” event, written by the brain’s misfire detector; each row carries detected_at, expected_fire_at, lateness_seconds, and the grace in force at detection time. See misfire detection.

GET /api/v1/projects/{slug}/schedules/misfires

Role: viewer. Returns every detected misfire across ALL of the project’s schedules, newest first (limit defaults to 50, capped at 1000). Unlike the per-schedule view above, each row carries its own schedule_id so an operator can triage missed slots project-wide in one call. The z4j misfires --project <slug> CLI command is the shell-side twin of this endpoint.

When the scheduler is authoritative (Celery beat, APScheduler), three colon-suffixed endpoints drive the sync workflow:

POST /api/v1/projects/{slug}/schedules:import
POST /api/v1/projects/{slug}/schedules:diff
POST /api/v1/projects/{slug}/schedules:resync

:import materialises the scheduler’s current schedules into the brain. :diff reports differences without writing. :resync reconciles drift in one direction (brain to scheduler or the reverse depending on the body).