Cron for developers

Drive your scheduled jobs from your own code: a REST API with scoped keys and per-key rate limits, the full execution history of every run, and signed webhooks when something happens.

API keys

Get a key and authenticate

The Cron REST API lets your own backend do everything the dashboard does with your scheduled jobs: create and edit them, run one immediately, and read what happened on every past run.

Open your project in the Cron dashboard and create an API key under API keys. The secret is shown once, when the key is created, and never again — store it somewhere safe. A key belongs to a single project, so the project is implied by the key and never has to be sent.

Authenticate every request with HTTP Basic auth carrying only the key secret, base64-encoded, in the Authorization header.

# The Authorization header is HTTP Basic auth carrying only the key secret,
# with no username and no colon.
Authorization: Basic $(printf %s YOUR_API_KEY_SECRET | base64)

Every endpoint lives under https://api.cron.cool. Requests made with a key are rate limited per key; going over the limit returns 429.

Quick start

Your first three calls

List the jobs of your project, schedule a new one, then read its execution history.

# List the cron jobs of the project the key belongs to
curl https://api.cron.cool/api/jobs \
  -H "Authorization: Basic $(printf %s YOUR_API_KEY_SECRET | base64)"

# Create a job that calls your endpoint every five minutes.
# expression is an EventBridge Scheduler expression — rate(5 minutes) or
# cron(0/5 * * * ? *), not a five-field unix crontab line.
curl -X POST https://api.cron.cool/api/jobs \
  -H "Authorization: Basic $(printf %s YOUR_API_KEY_SECRET | base64)" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "warm-cache",
    "type": "webhook",
    "expression": "rate(5 minutes)",
    "url": "https://example.com/warm-cache",
    "httpMethod": "POST",
    "contentType": "application/json",
    "input": { "reason": "scheduled warm-up" }
  }'

# Read the last executions of that job
curl https://api.cron.cool/api/jobs/JOB_ID/executions?limit=20 \
  -H "Authorization: Basic $(printf %s YOUR_API_KEY_SECRET | base64)"

Browse the full API reference — every endpoint with its parameters, request body, responses and required scope.

Scopes

Least privilege by default

Each key carries a list of scopes, so an integration that only needs to watch your jobs never gets the ability to change them. New keys start read-only; widen them explicitly in the dashboard. A request whose key is missing the scope an endpoint requires is refused with 403.

  • jobs:readList your cron jobs and read a single job.
  • jobs:writeCreate, update and delete jobs, and run one on demand.
  • executions:readRead a job's execution history.
  • workflows:readRead hosted workflow runs, steps, events and hooks.
  • workflows:writeCreate workflow events, enqueue and dispatch work, cancel and replay runs.

The workflows:read and workflows:write scopes gate the endpoints the hosted workflow runtime calls on your behalf. The reference at /api/ covers the job and webhook subscription endpoints you call yourself; the workflow endpoints are not part of it.

Webhooks

Signed webhooks

Add a webhook subscription to your project and Cron POSTs the events you picked to your server as they happen.

  • job.createdA job was created.
  • job.updatedA job was edited, paused or resumed.
  • job.deletedA job was deleted.
  • job.executedA job ran; the payload is the execution, with its status, http status, duration and truncated response body.
POST https://your-server.com/cron-webhook
X-Croncool-Event: job.executed
X-Croncool-Signature: t=1719000000,v1=<hmac-sha256 hex>
Content-Type: application/json

{
  "event": "job.executed",
  "timestamp": 1719000000,
  "data": { "...": "..." }
}

Verify the signature

Every delivery carries an X-Croncool-Signature header of the form t=timestamp,v1=signature, where the signature is an HMAC-SHA256 of timestamp.body keyed by the subscription secret shown to you once when the subscription was created. Recompute it over the raw body and compare before trusting the payload.

import crypto from 'node:crypto'

// body must be the RAW request body, byte for byte
function verify(header, body, secret) {
  const [t, v1] = (header || '').split(',').map(part => part.split('=')[1])
  if (!t || !v1) return false

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${t}.${body}`)
    .digest('hex')

  // timingSafeEqual throws on a length mismatch, so a malformed signature
  // has to be rejected before the comparison rather than by it.
  if (v1.length !== expected.length) return false

  return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
}

Delivery is one best-effort attempt with a five second timeout and no retries, so respond 2xx quickly and do the work asynchronously. An endpoint that fails twenty times in a row is disabled automatically and has to be re-enabled in the dashboard.

Start building