Skip to main content

Jobs

A job is the unit of scheduling: when to run (a cron spec, or a single startDate for one-time jobs), what to pass (data), and how to run it (executorId). Jobs live inside a project.

Job object​

{
"id": 1,
"accountId": 123,
"projectId": 456,
"executorId": 789,
"spec": "0 0 2 * * *",
"data": "{\"action\": \"cleanup\", \"target\": \"logs\"}",
"startDate": "2026-02-01T00:00:00Z",
"endDate": "2026-12-31T23:59:59Z",
"lastExecutionDate": "2026-02-01T00:00:00Z",
"retryMax": 3,
"timezone": "UTC",
"timezoneOffset": 0,
"status": "active",
"executionId": "a1b2c3...",
"dateCreated": "2026-01-15T10:30:00Z",
"dateModified": null,
"createdBy": "user123",
"modifiedBy": null,
"deletedBy": null
}
FieldDescription
projectIdRequired. Must belong to your account.
executorIdRequired. Must belong to your account.
timezoneRequired. An IANA zone name such as UTC or America/New_York (Local is rejected). Scheduling is computed in this zone.
specCron expression (see below). Leave empty for a one-time job.
startDateRFC3339. Required when spec is empty; otherwise the earliest time the job may fire. Must not be in the past.
endDateRFC3339, optional. The job stops firing after this time. Must not be in the past or before startDate.
dataA JSON-encoded string passed to the executor. Up to 3 KB by default; 1 MB with the increased_job_payload_size_to_1mb account feature.
retryMaxRetries after a failed execution. Up to 3 by default; up to 15 with the increased_retry_max_by_five feature.
statusactive (default) or inactive (paused).
timezoneOffsetOptional integer stored with the job. Scheduling uses timezone; this field is informational.
createdBy / modifiedBy / deletedByActor identifiers; required on create / update / delete respectively.
lastExecutionDate, executionIdMaintained by the scheduler.

Fields with zero values are omitted from responses.

Cron format​

Specs are parsed by robfig/cron v1, which uses six fields with a leading seconds field:

┌───────────── second (0 - 59)
│ ┌───────────── minute (0 - 59)
│ │ ┌───────────── hour (0 - 23)
│ │ │ ┌───────────── day of month (1 - 31)
│ │ │ │ ┌───────────── month (1 - 12)
│ │ │ │ │ ┌───────────── day of week (0 - 6, Sunday = 0)
* * * * * *
ExpressionMeaning
0 0 2 * * *Every day at 02:00
0 */15 * * * *Every 15 minutes
0 0 9 * * 1-509:00 Monday to Friday
0 0 0 1 * *Midnight on the first of each month

Descriptors are also accepted: @yearly/@annually, @monthly, @weekly, @daily/@midnight, @hourly, and intervals such as @every 5m or @every 1h30m. A spec that does not parse returns 400 job spec is not valid.

A standard five-field cron expression is interpreted with the first field as seconds, so 0 2 * * * means "second 0 of every minute of 02:00", not "02:00 daily". Always write six fields.

Endpoints​

MethodPathScopeNotes
GET/jobsreadPaginated; optional projectId filter
POST/jobswriteBody is a JSON array of 1–100 jobs → 202 + request ID
GET/jobs/{id}read
PUT/jobs/{id}writeFull replace; modifiedBy required
DELETE/jobs/{id}writeBody {deletedBy} → 204
GET/async-tasks/{id}readResult of a POST /jobs request

Create jobs​

POST /jobs takes an array, even for a single job (a non-array body returns 422). Every job needs projectId, executorId, timezone, createdBy, and either spec or startDate.

curl -X POST "https://api.scheduler0.com/api/v1/jobs" \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-Secret-Key: YOUR_SECRET_KEY" \
-H "X-Account-ID: YOUR_ACCOUNT_ID" \
-d '[
{
"projectId": 456,
"executorId": 789,
"spec": "0 0 2 * * *",
"data": "{\"action\": \"backup\", \"database\": \"production\"}",
"retryMax": 3,
"timezone": "UTC",
"createdBy": "user123"
}
]'

The batch is validated synchronously — an invalid spec, unknown project or executor, a startDate in the past, an oversized data payload or a retryMax above your limit returns 400 with the reason. Once validation passes the insert happens asynchronously: the response is 202 Accepted with the request ID in data and a Location header pointing at the async task:

{ "success": true, "data": "5f3c9e2a-..." }
Location: /async-tasks/5f3c9e2a-...

One-time jobs​

Omit spec and set startDate. The job fires once at startDate (interpreted in timezone).

[
{
"projectId": 456,
"executorId": 789,
"startDate": "2026-03-01T08:00:00Z",
"timezone": "UTC",
"data": "{\"orderId\": 42}",
"createdBy": "user123"
}
]

Check the result​

GET /async-tasks/{id} returns the task for the request ID. If the batch is still being processed the call blocks until it finishes, so one call is normally enough.

curl "https://api.scheduler0.com/api/v1/async-tasks/5f3c9e2a-..." \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-Secret-Key: YOUR_SECRET_KEY" \
-H "X-Account-ID: YOUR_ACCOUNT_ID"
{
"success": true,
"data": {
"id": 12,
"requestId": "5f3c9e2a-...",
"input": "[...]",
"output": "[...]",
"service": "job-service",
"state": 2,
"accountId": 123,
"dateCreated": "2026-01-15T10:30:00Z",
"dateModified": "2026-01-15T10:30:01Z"
}
}
stateMeaning
0Not started
1In progress
2Success — output contains the created jobs
3Failed — output contains the error

List and get​

# All jobs in the account
curl "https://api.scheduler0.com/api/v1/jobs?limit=10&offset=0&orderBy=date_created&orderByDirection=desc" \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-Secret-Key: YOUR_SECRET_KEY" \
-H "X-Account-ID: YOUR_ACCOUNT_ID"

# Jobs in one project
curl "https://api.scheduler0.com/api/v1/jobs?projectId=456&limit=10&offset=0" \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-Secret-Key: YOUR_SECRET_KEY" \
-H "X-Account-ID: YOUR_ACCOUNT_ID"
ParameterDescription
projectIdOptional filter
limitDefault 10, maximum 100 (429 above that)
offsetDefault 0
orderByid, project_id, spec, date_created (default), date_modified, timezone, account_id, modified_by, deleted_by, executor_id, start_date, end_date, retry_max
orderByDirectionasc or desc (default)

The response is { "total", "offset", "limit", "jobs": [...] }. GET /jobs/{id} returns one job or 404.

Update a job​

PUT /jobs/{id} is a full replacement, not a patch. Send the complete job: projectId and spec (or a future startDate) are needed to pass validation, and any of data, retryMax, startDate, endDate, status you leave out is reset to empty. timezone, timezoneOffset and executorId fall back to their existing values when omitted. modifiedBy is required (400 otherwise).

The cron spec and projectId cannot be changed by an update — the values you send must match, and to reschedule a job you delete and recreate it.

curl -X PUT "https://api.scheduler0.com/api/v1/jobs/1" \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-Secret-Key: YOUR_SECRET_KEY" \
-H "X-Account-ID: YOUR_ACCOUNT_ID" \
-d '{
"projectId": 456,
"spec": "0 0 2 * * *",
"data": "{\"action\": \"backup\", \"database\": \"production\"}",
"retryMax": 5,
"status": "active",
"modifiedBy": "user123"
}'

Pause and resume​

Set status to inactive to stop a job from firing and back to active to resume it, sending the rest of the job as above.

curl -X PUT "https://api.scheduler0.com/api/v1/jobs/1" \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-Secret-Key: YOUR_SECRET_KEY" \
-H "X-Account-ID: YOUR_ACCOUNT_ID" \
-d '{
"projectId": 456,
"spec": "0 0 2 * * *",
"data": "{\"action\": \"backup\", \"database\": \"production\"}",
"retryMax": 3,
"status": "inactive",
"modifiedBy": "user123"
}'

Delete a job​

curl -X DELETE "https://api.scheduler0.com/api/v1/jobs/1" \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-Secret-Key: YOUR_SECRET_KEY" \
-H "X-Account-ID: YOUR_ACCOUNT_ID" \
-d '{"deletedBy": "user123"}'

Returns 204.

Execution and retries​

When a job fires, the scheduler records an execution in state scheduled, invokes the executor, and updates the execution to success or failed. A failed invocation is retried up to retryMax times; retryMax: 0 disables retries. Each execution has a uniqueId derived from the job, project, last execution date and next execution time, so a run is never processed twice across restarts or leader changes. See Executions.