Jobs API
Jobs are the core resource: a schedule, a sink, and policies for retries, misfires, overlap and auto-disable. All endpoints are workspace-scoped through your API key.
The job object
{
"id": "job_01J9ZK3Q…",
"name": "nightly-report",
"description": "Builds the daily revenue report",
"tags": ["reports"],
"schedule": {
"expr": "every day at 9am",
"kind": "natural",
"canonical": "FREQ=DAILY;BYHOUR=9;BYMINUTE=0;BYSECOND=0",
"description": "At 09:00 every day",
"timezone": "Asia/Kolkata",
"starts_at": null,
"ends_at": null,
"count": null
},
"sink": {"kind": "webhook", "config": {"url": "https://api.example.com/reports", "method": "POST"}},
"retry": {"max_attempts": 3, "backoff": "exponential", "initial_interval": "10s", "max_interval": "10m", "multiplier": 2, "jitter": 0.2},
"timeout": "30s",
"sla_threshold": null,
"misfire_policy": "fire_once",
"overlap_policy": "allow",
"auto_disable_threshold": null,
"state": "active",
"disabled_reason": null,
"consecutive_failures": 0,
"next_run_at": "2026-11-11T03:30:00Z",
"next_runs": ["2026-11-11T03:30:00Z", "2026-11-12T03:30:00Z", "2026-11-13T03:30:00Z"],
"last_run_at": "2026-11-10T03:30:00Z",
"last_run_status": "succeeded",
"version": 3,
"created_at": "2026-10-16T08:00:00Z",
"updated_at": "2026-10-20T11:12:00Z"
}
null for sla_threshold and auto_disable_threshold means “use the server default”. next_runs is included by GET /jobs/{id}.
Create a job
POST /api/v1/jobs
{
"name": "nightly-report",
"schedule": {"expr": "every day at 9am", "timezone": "Asia/Kolkata"},
"sink": {"kind": "webhook", "config": {"url": "https://api.example.com/reports"}},
"retry": {"max_attempts": 5},
"timeout": "30s",
"tags": ["reports"]
}
Only name, schedule.expr and sink are required. Omitted fields take server defaults. Partial retry objects are merged with the defaults.
Response 201 is the job object plus, once only, "signing_secret": "whsec_…" for webhook jobs.
Send an Idempotency-Key header to replay the same create response for 24 hours within a workspace. Reusing the key with a different payload returns 409 idempotency_conflict.
Errors: 400 validation_failed (bad schedule, unknown secret, URL not allowed, sink disabled on this server), 403 limit_exceeded (job count or minimum interval).
List jobs
GET /api/v1/jobs?state=active&tag=reports&q=report&limit=50
| Query | Description |
|---|---|
state | active, paused, disabled, completed |
tag | Jobs with this tag |
q | Case-insensitive match on name and description |
Get a job
GET /api/v1/jobs/{id}, including next_runs and 24-hour stats_24h (runs, succeeded, failed, sla_breached, p95_start_lag_ms).
Update a job
PATCH /api/v1/jobs/{id} with any subset of the create fields. The job’s version increases, queued occurrences of the old version are dropped, and the next fire time is recalculated from now.
Delete a job
DELETE /api/v1/jobs/{id} → 204. The job stops immediately; its run history remains until it ages out.
Change state
| Endpoint | From | To |
|---|---|---|
POST /api/v1/jobs/{id}/pause | active | paused |
POST /api/v1/jobs/{id}/resume | paused | active (next future occurrence) |
POST /api/v1/jobs/{id}/enable | disabled | active, failure counter reset |
Calling from any other state returns 409 conflict.
Run now
POST /api/v1/jobs/{id}/trigger → 202 with {"run_id": "run_…"}. Creates a run with trigger = manual that starts immediately. The schedule is unaffected. Works on paused jobs too.
Test the sink
POST /api/v1/jobs/{id}/test → 200
{"outcome": "success", "status_code": 200, "duration_ms": 87, "response_body": "{\"ok\":true}", "request_summary": "POST https://api.example.com/reports (231 bytes)"}
Executes the sink once, synchronously, without recording a run or affecting counters. You can also test an unsaved config with POST /api/v1/jobs/test and a full job body.
Rotate the signing secret
POST /api/v1/jobs/{id}/rotate-signing-secret → 200 with the new signing_secret. Both secrets sign requests for 24 hours.