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: ..." }
StatusMeaning
400Validation failed (bad cron, unknown timezone, missing field, invalid URL, …)
401Missing or invalid API key
404Job (or route) not found
500Unexpected server error

The Job object

FieldTypeDescription
idstringUUID, assigned by the server.
namestringDisplay name.
descriptionstring?Optional free text.
scheduleScheduleWhen the job runs.
actionActionWhat the job does.
retriesintegerExtra attempts after a failure, 0–10. Retries happen immediately. Default 0.
allowOverlapbooleanIf false (default), a scheduled tick is skipped while the previous run is still in progress.
enabledbooleanfalse when paused. One-shot jobs become disabled after they fire.
nextRunAtstring | nullNext scheduled execution, or null if paused/finished. Read-only.
lastRunobject | null{ "at", "status" } of the most recent run. Read-only.
runCountintegerTotal executions (scheduled + manual). Read-only.
createdAt, updatedAtstringISO 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.

FieldTypeDescription
cronstringRecurring schedule in cron syntax (5 or 6 fields).
runAtstringOne-shot: ISO date/time in the future. The job runs once, then is disabled.
timezonestring?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

FieldTypeDescription
url requiredstringhttp:// or https:// URL.
method optionalstringDefault GET.
headers optionalobjectRequest headers.
body optionalanyStrings are sent as-is; anything else is JSON-encoded with Content-Type: application/json. Ignored for GET/HEAD.
timeoutMs optionalintegerDefault 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

FieldTypeDescription
message requiredstringPrinted to stdout; also stored as the run output. Handy for testing schedules.

shell — run a command on the host

Disabled by default. Only available when the server runs with ALLOW_SHELL=1; otherwise creating a shell job returns 400. Enable it only together with API_KEY — it gives callers arbitrary command execution.
FieldTypeDescription
command requiredstringRun via /bin/sh -c. Non-zero exit = failure.
timeoutMs optionalintegerDefault 60000.

The Run object

One execution of a job. The last 50 runs per job are kept.

FieldTypeDescription
idstringUUID.
triggerstringschedule or manual.
statusstringsuccess or failed (after all retries).
attemptsintegerHow many attempts were made.
outputstring?Action output (HTTP status + body, stdout/stderr, or log message), max 4 KB.
errorstring?Failure reason, if failed.
startedAt, finishedAtstringISO timestamps.
durationMsintegerTotal 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

GET/api/health

Liveness probe. Never requires authentication.

200 OK
{ "ok": true, "jobs": 3 }

List jobs

GET/api/jobs

Returns an array of all Job objects, oldest first.

curl /api/jobs -H 'X-API-Key: $KEY'

Create a job

POST/api/jobs
Body fieldTypeDescription
name requiredstring
schedule requiredSchedule
action requiredAction
description optionalstring
retries optionalinteger0–10, default 0.
allowOverlap optionalbooleanDefault false.
enabled optionalbooleanCreate 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

GET/api/jobs/{id}

Returns the Job, or 404.

Update a job

PATCH/api/jobs/{id}

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

DELETE/api/jobs/{id}

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

POST/api/jobs/{id}/pause

Stops scheduled executions (sets enabled: false). Manual runs still work. Returns the Job.

Resume a job

POST/api/jobs/{id}/resume

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

POST/api/jobs/{id}/run

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

GET/api/jobs/{id}/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)
│ │ │ │ │ │
* * * * * *
ExpressionMeaning
* * * * *Every minute
*/15 * * * *Every 15 minutes
0 * * * *Top of every hour
0 9 * * MON-FRI09:00 on weekdays
0 0 1 * *Midnight on the 1st of each month
*/30 * * * * *Every 30 seconds (6-field)
@daily, @hourly, @weeklyNicknames

Supports lists (1,15), ranges (1-5), steps (*/5), L (last day of month) and # (nth weekday, e.g. FRI#2).