REST API
Complete reference for the Chronos developer-facing API.
Conventions
Section titled “Conventions”| Convention | Value |
|---|---|
| Base URL | https://api.chronos.sh |
| Authentication | Authorization: Bearer chrns_your_api_key |
| Content type | application/json |
| Field casing | snake_case |
| IDs | UUID v4 |
| Timestamps | ISO 8601 |
| Pagination | page + size query params (defaults: page 1, size 20, max size 100) |
Response envelope
Section titled “Response envelope”All responses use a consistent envelope:
{ "success": true, "message": "Human-readable message", "data": { ... }}Paginated responses add:
{ "meta": { "paging": { "page": 1, "pages": 5, "size": 20, "total": 100 } }}Error responses:
{ "success": false, "message": "What went wrong", "code": "error_code", "errors": [{ "field": "name", "message": "is required" }]}Rate limiting
Section titled “Rate limiting”Approximately 100 requests per 10 seconds per client IP. Every response includes rate-limit headers; treat them as the authoritative view of your budget:
| Header | Description |
|---|---|
X-RateLimit-Limit |
Maximum requests allowed per window. |
X-RateLimit-Remaining |
Requests remaining in the current window. |
X-RateLimit-Reset |
Seconds until the current window resets. |
When the limit is exceeded, the API returns 429 with code rate_limit_exceeded and a Retry-After header indicating how many seconds to wait before retrying.
Schedules
Section titled “Schedules”Create Schedule
Section titled “Create Schedule”POST /v1/schedulesCreates a recurring schedule. Exactly one of cron or interval is required.
Request body:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string | Yes | — | Display name. Max 255 chars. |
description |
string | No | null |
Max 1000 chars. |
handler |
string | Required for pull delivery | — | Routing key. Max 255 chars. |
cron |
string | XOR with interval |
— | Cron expression. |
interval |
object | XOR with cron |
— | { value: int, unit: string } |
interval.value |
integer | Yes (if interval) | — | Positive integer. |
interval.unit |
string | Yes (if interval) | — | second, minute, hour, day, week |
timezone |
string | No | Account timezone | IANA timezone (e.g., America/New_York). |
runs |
integer | No | null (unlimited) |
Max jobs to create before completing. |
stop_at |
string | No | null |
ISO 8601 date. Must be in the future. |
delivery |
object | No | { type: "pull" } |
Delivery configuration. |
delivery.type |
string | Yes (if delivery) | — | pull or push |
delivery.http |
object | Required if push | — | HTTP delivery config. |
delivery.http.url |
string | Yes | — | HTTPS URL. No private/localhost IPs. |
delivery.http.method |
string | Yes | — | GET, POST, PUT, PATCH, DELETE |
delivery.http.headers |
object | No | — | Custom headers (Record<string, string>). |
payload |
object | No | null |
Job payload. Max 64KB serialized. Must be non-empty if provided. |
timeout |
integer | No | 30 |
Seconds. Positive integer, capped by your plan. |
max_retries |
integer | No | 3 |
Min 0. |
cron and interval must not resolve below your plan’s minimum interval. See Pricing & Limits.
Response: 201 Created
{ "success": true, "message": "Schedule created", "data": { "id": "uuid", "account_id": "uuid", "name": "string", "description": "string | null", "handler": "string | null", "cron": "string | null", "interval": "{ value, unit } | null", "timezone": "string", "runs": "number | null", "run_count": 0, "stop_at": "ISO 8601 | null", "delivery": { "type": "pull | push", "http": "{ url, method, headers? } | undefined" }, "payload": "object | null", "timeout": 30, "max_retries": 3, "status": "active", "next_run_at": "ISO 8601 | null", "last_run_at": "ISO 8601 | null", "created_at": "ISO 8601", "updated_at": "ISO 8601" }}Errors:
validation_error(422): invalid or missing fieldsplan_limit_exceeded(403): active-schedule limit reached,timeoutover your plan’s cap, or cadence below your plan’s minimum intervalrate_limit_exceeded(429)
List Schedules
Section titled “List Schedules”GET /v1/schedulesQuery params:
| Param | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 |
Page number. |
size |
integer | 20 |
Items per page. Max 100. |
status |
string | — | Filter: active, paused, completed, archived |
Returns all schedules including archived unless filtered by status.
Response: 200 OK. paginated array of schedule objects.
Get Schedule
Section titled “Get Schedule”GET /v1/schedules/:idResponse: 200 OK. single schedule object.
Errors:
schedule_not_found(404)
Update Schedule
Section titled “Update Schedule”PATCH /v1/schedules/:idAll fields optional. At least one field required.
| Field | Type | Description |
|---|---|---|
name |
string | Max 255 chars. |
description |
string | null | null clears. Max 1000 chars. |
handler |
string | Max 255 chars. |
cron |
string | Cannot combine with interval in same request. |
interval |
object | Cannot combine with cron in same request. |
timezone |
string | IANA timezone. |
runs |
integer | null | null removes run limit. |
stop_at |
string | null | null removes stop time. |
delivery |
object | Same shape as create. |
payload |
object | null | null clears. Max 64KB. |
timeout |
integer | Positive, capped by your plan. |
max_retries |
integer | Min 0. |
If cron or interval changes, next_run_at is recomputed. If timezone changes, next_run_at is recomputed for cron schedules.
Response: 200 OK. updated schedule object.
Errors:
schedule_not_found(404)schedule_archived(409): cannot update archived schedulesschedule_completed(409): cannot update completed scheduleshandler_required_for_pull(400): pull delivery requires a handlerschedule_status_changed(409): optimistic concurrency conflictplan_limit_exceeded(403):timeoutover your plan’s cap, or cadence below your plan’s minimum intervalvalidation_error(422)
Trigger Schedule
Section titled “Trigger Schedule”POST /v1/schedules/:id/triggerNo request body. Creates a job from the schedule immediately, bypassing its timing. Increments run_count: a manual trigger consumes a run, and completes the schedule if it reaches the runs limit.
Response: 201 Created. the created job object.
Errors:
schedule_not_found(404)schedule_archived(409): cannot trigger archived schedulesschedule_completed(409): cannot trigger completed schedules
Pause Schedule
Section titled “Pause Schedule”POST /v1/schedules/:id/pauseNo request body. No new jobs are spawned while paused. Jobs already spawned are unaffected.
Response: 200 OK. schedule object with status: "paused".
Errors:
schedule_not_found(404)schedule_already_paused(409)schedule_archived(409)schedule_completed(409)
Resume Schedule
Section titled “Resume Schedule”POST /v1/schedules/:id/resumeNo request body. Recomputes next_run_at from the current time; interval schedules re-anchor to the resume time, cron schedules keep their alignment. If the schedule’s runs limit is reached or stop_at falls before the next run, the request fails with schedule_completed.
Response: 200 OK. schedule object with status: "active".
Errors:
schedule_not_found(404)schedule_already_active(409)schedule_archived(409)schedule_completed(409)schedule_cadence_invalid(422): the stored cron, interval, or timezone can no longer be computed; update the schedule before resumingplan_limit_exceeded(403): resuming would exceed your plan’s active-schedule limit, or the storedtimeoutor cadence no longer fits your plan (after a downgrade)
Archive Schedule
Section titled “Archive Schedule”POST /v1/schedules/:id/archiveNo request body. Cancels all pending/queued/ready/retrying jobs for this schedule.
Response: 200 OK. schedule object with status: "archived".
Errors:
schedule_not_found(404)schedule_archived(409): already archived
Delete Schedule
Section titled “Delete Schedule”DELETE /v1/schedules/:idPermanently deletes the schedule. Associated jobs have their schedule_id set to null.
Response: 204 No Content
Errors:
schedule_not_found(404)
Create Job
Section titled “Create Job”POST /v1/jobsCreates a one-off job. Runs immediately unless delay or scheduled_for is set.
Request body:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string | Yes | — | Display name. Max 255 chars. |
handler |
string | Required for pull | — | Routing key. Max 255 chars. |
delay |
object | No | — | Relative timing. XOR with scheduled_for. |
delay.value |
integer | Yes (if delay) | — | Positive integer. |
delay.unit |
string | Yes (if delay) | — | second, minute, hour, day, week |
scheduled_for |
string | No | — | Absolute ISO 8601 time. Must be in the future. XOR with delay. |
delivery |
object | No | { type: "pull" } |
Same shape as schedules. |
payload |
object | No | null |
Max 64KB. Must be non-empty if provided. |
timeout |
integer | No | 30 |
Seconds. Positive integer, capped by your plan. |
max_retries |
integer | No | 3 |
Min 0. |
idempotency_key |
string | No | null |
Dedup key. Max 255 chars. Scoped to your account. |
Timing options:
- Neither
delaynorscheduled_for→ runs immediately delay→ runs after the specified durationscheduled_for→ runs at the specified time
Response: 201 Created
{ "success": true, "message": "Job created", "data": { "id": "uuid", "account_id": "uuid", "schedule_id": null, "name": "string", "handler": "string | null", "idempotency_key": "string | null", "scheduled_for": "ISO 8601", "run_at": "ISO 8601", "delivery": { "type": "pull | push", "http": "..." }, "payload": "object | null", "timeout": 30, "max_retries": 3, "attempt_count": 0, "status": "pending", "last_error": "string | null", "started_at": "ISO 8601 | null", "completed_at": "ISO 8601 | null", "created_at": "ISO 8601", "updated_at": "ISO 8601" }}Errors:
idempotency_key_conflict(409): duplicate key on same accountplan_limit_exceeded(403):timeoutover your plan’s capvalidation_error(422)
List Jobs
Section titled “List Jobs”GET /v1/jobsQuery params:
| Param | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 |
Page number. |
size |
integer | 20 |
Max 100. |
schedule_id |
string | — | UUID, or literal "null" for one-off jobs. |
handler |
string | — | Filter by handler name. |
status |
string | — | pending, admitting, queued, ready, running, retrying, completed, failed, cancelled, skipped |
scheduled_before |
string | — | ISO 8601 upper bound. |
scheduled_after |
string | — | ISO 8601 lower bound. Must be ≤ scheduled_before. |
Response: 200 OK. paginated array of job objects.
Get Job
Section titled “Get Job”GET /v1/jobs/:idResponse: 200 OK. single job object.
Errors:
job_not_found(404)
Cancel Job
Section titled “Cancel Job”POST /v1/jobs/:id/cancelNo request body. The job record is preserved for audit.
Response: 200 OK. job object with status: "cancelled".
Errors:
job_not_found(404)job_not_cancellable(409): job must bepending,admitting,queued,ready, orretryingjob_status_changed(409): concurrent update detected; retry the operation
Skip Job
Section titled “Skip Job”POST /v1/jobs/:id/skipNo request body. Only schedule-spawned jobs can be skipped; cancel one-off jobs instead.
Response: 200 OK. job object with status: "skipped".
Errors:
job_not_found(404)job_not_scheduled(409): job has noschedule_idjob_not_skippable(409): job must bependingorqueuedjob_status_changed(409): concurrent update detected; retry the operation
Retry Job
Section titled “Retry Job”POST /v1/jobs/:id/retryNo request body. Re-queues a failed job to run immediately; a new execution is created when it runs. The retry budget is not reset: attempt_count carries over, so a job that exhausted max_retries gets one more attempt, not a fresh set.
Response: 200 OK. job object with status: "pending".
Errors:
job_not_found(404)job_not_failed(409): only failed jobs can be retriedjob_status_changed(409): concurrent update detected; retry the operation
Executions
Section titled “Executions”List Executions
Section titled “List Executions”GET /v1/executionsQuery params:
| Param | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 |
Page number. |
size |
integer | 20 |
Max 100. |
job_id |
string | — | UUID. |
schedule_id |
string | — | UUID, or "null" for one-off job executions. |
handler |
string | — | Filter by handler name. |
status |
string | — | pending, running, completed, failed, timeout |
created_before |
string | — | ISO 8601 upper bound. |
created_after |
string | — | ISO 8601 lower bound. Must be ≤ created_before. |
Response: 200 OK
{ "success": true, "message": "Executions fetched", "data": [{ "id": "uuid", "account_id": "uuid", "job_id": "uuid", "status": "pending | running | completed | failed | timeout", "trigger": "system | system_retry | manual", "started_at": "ISO 8601 | null", "completed_at": "ISO 8601 | null", "duration_ms": "number | null", "result": "object | null", "error": "string | null", "response_code": "number | null", "created_at": "ISO 8601" }], "meta": { "paging": { ... } }}Get Execution
Section titled “Get Execution”GET /v1/executions/:idResponse: 200 OK. single execution object.
Errors:
execution_not_found(404)
Workers
Section titled “Workers”List Workers
Section titled “List Workers”GET /v1/workersReturns registered workers for your account with derived online/offline status.
Query params:
| Param | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 |
Page number. |
size |
integer | 20 |
Items per page. Max 100. |
status |
string | — | Filter: online, offline |
handler |
string | — | Filter by handler name. |
Response: 200 OK
{ "success": true, "message": "Workers fetched", "data": [ { "id": "uuid", "worker_id": "w_a1b2c3", "sdk_version": "0.0.2", "runtime": "node/v22.1.0", "handlers": ["echo", "slow-job"], "poll_wait_seconds": 20, "uptime_ms": 3600000, "status": "online", "last_seen_at": "2026-06-20T12:00:00.000Z", "created_at": "2026-06-20T11:00:00.000Z" } ], "meta": { "paging": { "page": 1, "pages": 1, "size": 20, "total": 1 } }}Get Worker
Section titled “Get Worker”GET /v1/workers/:idResponse: 200 OK. Single worker object with derived status.
Errors:
worker_not_found(404)
Claim Job
Section titled “Claim Job”These endpoints are used by the SDK internally. You can also call them directly if building a custom worker.
POST /v1/workers/jobs/claimClaims the next available job for your registered handlers.
Request body:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
wait_time_seconds |
integer | No | 0 |
Long-poll duration. 0–20. |
handlers |
string[] | No | — | Filter by handler names. Min 1 item if provided. |
When wait_time_seconds > 0, the request holds open until a job is available or the timeout expires. The SDK uses 20 seconds by default.
Response (job claimed): 200 OK
{ "success": true, "message": "Job claimed", "data": { "job_id": "uuid", "execution_id": "uuid", "handler": "string", "scheduled_for": "ISO 8601", "attempt": 1, "timeout": 30, "payload": "object | null", "schedule": "{ id: uuid, name: string } | null" }}Response (no job available): 200 OK
{ "success": true, "message": "No job available", "data": null}Report Result
Section titled “Report Result”POST /v1/workers/executions/:id/resultReports the outcome of a claimed job’s execution.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
status |
string | Yes | completed, failed, or timeout |
result |
object | No | Execution result data. Allowed only when completed. |
error |
string | Required when failed |
Error message. Max 4096 chars. Forbidden for completed and timeout. |
Business rules:
- Same-status re-report is idempotent (returns 200, no-op)
- On
failedwith retries remaining: job transitions toretrying, new execution scheduled with exponential backoff - On
failedwith retries exhausted: job transitions tofailed
Response: 200 OK
{ "success": true, "message": "Result recorded", "data": null}Errors:
execution_not_found(404)execution_not_running(409): execution is not inrunningstatus
Dashboard-Only Endpoints
Section titled “Dashboard-Only Endpoints”Some of the API surface is session-authenticated and reserved for the dashboard; API keys cannot call it. This covers account and team management, billing, alert settings, signing key and API key management, the activity feed, and webhook URL testing.
Manage these through the dashboard. In particular, API-key clients cannot read, create, rotate, or revoke keys. See the Signature Verification guide for using your signing secret.