Cron API reference
Create jobs that run on a cron schedule (or once at a given time). Each job performs an action — call a webhook, log a message, or run a shell command — and every execution is recorded.
Base URL: /api. Requests and responses are JSON (Content-Type: application/json). Timestamps are ISO 8601 in UTC.
Quick start
curl -X POST /api/jobs \
-H 'Content-Type: application/json' \
-d '{
"name": "Ping homepage",
"schedule": { "cron": "*/5 * * * *" },
"action": { "type": "http", "url": "https://example.com" }
}'
The job now fires every 5 minutes. Check its executions with GET /api/jobs/{id}/runs.
Authentication
If the server is started with the API_KEY environment variable, every endpoint except /health requires that key, sent as either header:
X-API-Key: <key>
Authorization: Bearer <key>
Requests without a valid key get 401. If API_KEY is unset, the API is open.
Errors
Errors return a non-2xx status and a body with a human-readable message:
{ "error": "invalid cron expression: ..." }
| Status | Meaning |
|---|---|
400 | Validation failed (bad cron, unknown timezone, missing field, invalid URL, …) |
401 | Missing or invalid API key |
404 | Job (or route) not found |
500 | Unexpected server error |
The Job object
| Field | Type | Description |
|---|---|---|
id | string | UUID, assigned by the server. |
name | string | Display name. |
description | string? | Optional free text. |
schedule | Schedule | When the job runs. |
action | Action | What the job does. |
retries | integer | Extra attempts after a failure, 0–10. Retries happen immediately. Default 0. |
allowOverlap | boolean | If false (default), a scheduled tick is skipped while the previous run is still in progress. |
enabled | boolean | false when paused. One-shot jobs become disabled after they fire. |
nextRunAt | string | null | Next scheduled execution, or null if paused/finished. Read-only. |
lastRun | object | null | { "at", "status" } of the most recent run. Read-only. |
runCount | integer | Total executions (scheduled + manual). Read-only. |
createdAt, updatedAt | string | ISO timestamps. Read-only. |
{
"id": "4f1c9a8e-2b3d-4c5e-9f00-1a2b3c4d5e6f",
"name": "Nightly report",
"schedule": { "cron": "0 2 * * *", "timezone": "Europe/Berlin" },
"action": { "type": "http", "method": "POST", "url": "https://example.com/reports", "body": { "kind": "nightly" } },
"retries": 2,
"allowOverlap": false,
"enabled": true,
"runCount": 14,
"lastRun": { "at": "2026-10-04T00:00:00.012Z", "status": "success" },
"nextRunAt": "2026-10-05T00:00:00.000Z",
"createdAt": "2026-09-20T10:11:12.000Z",
"updatedAt": "2026-09-20T10:11:12.000Z"
}
Schedule
Provide exactly one of cron or runAt.
| Field | Type | Description |
|---|---|---|
cron | string | Recurring schedule in cron syntax (5 or 6 fields). |
runAt | string | One-shot: ISO date/time in the future. The job runs once, then is disabled. |
timezone | string? | IANA timezone (e.g. America/New_York) used to interpret cron. Defaults to the server's timezone. |
{ "cron": "30 9 * * MON-FRI", "timezone": "Asia/Kolkata" } // 09:30 IST on weekdays
{ "runAt": "2026-12-31T23:59:00Z" } // once
Actions
The action.type field selects one of:
http — call a URL
| Field | Type | Description |
|---|---|---|
url required | string | http:// or https:// URL. |
method optional | string | Default GET. |
headers optional | object | Request headers. |
body optional | any | Strings are sent as-is; anything else is JSON-encoded with Content-Type: application/json. Ignored for GET/HEAD. |
timeoutMs optional | integer | Default 30000. |
A run succeeds on a 2xx response and fails on any other status, network error or timeout. The run output contains the status line and response body (truncated to 4 KB).
{ "type": "http", "method": "POST", "url": "https://hooks.example.com/x",
"headers": { "Authorization": "Bearer abc" }, "body": { "event": "tick" }, "timeoutMs": 10000 }
log — write a message to the server log
| Field | Type | Description |
|---|---|---|
message required | string | Printed to stdout; also stored as the run output. Handy for testing schedules. |
shell — run a command on the host
ALLOW_SHELL=1; otherwise creating a shell job returns 400. Enable it only together with API_KEY — it gives callers arbitrary command execution.| Field | Type | Description |
|---|---|---|
command required | string | Run via /bin/sh -c. Non-zero exit = failure. |
timeoutMs optional | integer | Default 60000. |
The Run object
One execution of a job. The last 50 runs per job are kept.
| Field | Type | Description |
|---|---|---|
id | string | UUID. |
trigger | string | schedule or manual. |
status | string | success or failed (after all retries). |
attempts | integer | How many attempts were made. |
output | string? | Action output (HTTP status + body, stdout/stderr, or log message), max 4 KB. |
error | string? | Failure reason, if failed. |
startedAt, finishedAt | string | ISO timestamps. |
durationMs | integer | Total time including retries. |
{
"id": "b7e0…", "trigger": "schedule", "status": "failed", "attempts": 3,
"error": "HTTP 503", "output": "HTTP 503\nService Unavailable",
"startedAt": "2026-10-04T02:00:00.004Z", "finishedAt": "2026-10-04T02:00:01.220Z", "durationMs": 1216
}
Health check
Liveness probe. Never requires authentication.
200 OK
{ "ok": true, "jobs": 3 }
List jobs
Returns an array of all Job objects, oldest first.
curl /api/jobs -H 'X-API-Key: $KEY'
Create a job
| Body field | Type | Description |
|---|---|---|
name required | string | |
schedule required | Schedule | |
action required | Action | |
description optional | string | |
retries optional | integer | 0–10, default 0. |
allowOverlap optional | boolean | Default false. |
enabled optional | boolean | Create paused with false. Default true. |
curl -X POST /api/jobs \
-H 'Content-Type: application/json' -H 'X-API-Key: $KEY' \
-d '{
"name": "Nightly report",
"schedule": { "cron": "0 2 * * *", "timezone": "Europe/Berlin" },
"action": { "type": "http", "method": "POST", "url": "https://example.com/reports", "body": { "kind": "nightly" } },
"retries": 2
}'
Responds 201 Created with the Job, including nextRunAt.
Get a job
Returns the Job, or 404.
Update a job
Send any subset of name, description, schedule, action, retries, allowOverlap, enabled. Omitted fields are unchanged; schedule and action are replaced as a whole. The job is rescheduled immediately.
curl -X PATCH /api/jobs/$ID \
-H 'Content-Type: application/json' -H 'X-API-Key: $KEY' \
-d '{ "schedule": { "cron": "0 */6 * * *" } }'
Responds 200 with the updated Job.
Delete a job
Unschedules the job and deletes its run history. Responds 204 No Content. A run already in progress finishes but isn't recorded.
Pause a job
Stops scheduled executions (sets enabled: false). Manual runs still work. Returns the Job.
Resume a job
Re-enables scheduling. Returns the Job with a fresh nextRunAt. A one-shot job whose runAt has passed stays without a next run — give it a new schedule via PATCH.
Run a job now
Executes the action immediately (including retries), regardless of schedule or paused state, and waits for it to finish. Responds 200 with the Run. The response is 200 even if the run failed — check status.
curl -X POST /api/jobs/$ID/run -H 'X-API-Key: $KEY'
List runs
Returns up to the last 50 Runs, newest first.
Cron syntax
Standard 5-field cron, with an optional leading seconds field (6 fields).
┌──────────── second (0-59, optional)
│ ┌────────── minute (0-59)
│ │ ┌──────── hour (0-23)
│ │ │ ┌────── day of month (1-31)
│ │ │ │ ┌──── month (1-12 or JAN-DEC)
│ │ │ │ │ ┌── day of week (0-7 or SUN-SAT; 0 and 7 = Sunday)
│ │ │ │ │ │
* * * * * *
| Expression | Meaning |
|---|---|
* * * * * | Every minute |
*/15 * * * * | Every 15 minutes |
0 * * * * | Top of every hour |
0 9 * * MON-FRI | 09:00 on weekdays |
0 0 1 * * | Midnight on the 1st of each month |
*/30 * * * * * | Every 30 seconds (6-field) |
@daily, @hourly, @weekly | Nicknames |
Supports lists (1,15), ranges (1-5), steps (*/5), L (last day of month) and # (nth weekday, e.g. FRI#2).