Tasks, Handlers, and Schedules
A task is anything you want to happen on a schedule — an API call, a Slack message, an AI-generated report, a health check. Define what it does, when it runs, and how it handles failure. Cronlet takes care of the rest.
A task combines:
- human-readable metadata (
name,description) - a
handler - a
schedule - execution controls (
retryAttempts,retryBackoff,retryDelay,timeout) - lifecycle controls (
active,maxRuns,expiresAt) - optional
callbackUrl - optional
metadata
Task create shape
Cloud task creation accepts a source field at the API boundary. In normal product usage this is set for you:
- dashboard task creation writes
source: "dashboard" - SDK task creation writes
source: "sdk" - MCP/agent task creation writes
source: "mcp"
await cronlet.tasks.create({ name: "API health check", description: "Ping the production health endpoint", handler: { type: "webhook", url: "https://api.example.com/health", method: "GET", }, schedule: { type: "every", interval: "5m", }, timezone: "UTC", retryAttempts: 2, retryBackoff: "linear", retryDelay: "5s", timeout: "30s", active: true, callbackUrl: "https://app.example.com/api/cronlet/callback", metadata: { userId: "user_123", monitorId: "monitor_456", }, maxRuns: 100, expiresAt: "2026-12-31T23:59:59.000Z",});API request shape:
{ name: string; description?: string; handler: HandlerConfig; schedule: ScheduleConfig; timezone?: string; retryAttempts?: number; retryBackoff?: "linear" | "exponential"; retryDelay?: string; timeout?: string; active?: boolean; source?: "dashboard" | "mcp" | "sdk"; callbackUrl?: string; metadata?: Record<string, unknown>; maxRuns?: number; expiresAt?: string;}Handlers
webhook
Use this when Cronlet should call an HTTP endpoint directly.
handler: { type: "webhook", url: "https://api.example.com/webhook", method: "POST", headers: { "X-Source": "cronlet", }, body: { kind: "report", }, auth: { type: "bearer", secretName: "INTERNAL_API_TOKEN", },}Supported fields:
url: stringmethod?: "GET" | "POST"headers?: Record<string, string>body?: unknownauth?: { type: "bearer" | "basic" | "header"; secretName: string }
tools
Use this when Cronlet should execute a hosted multi-step workflow.
handler: { type: "tools", steps: [ { tool: "http.get", args: { url: "https://api.example.com/report", }, outputKey: "report", }, { tool: "slack.post", args: { channel: "#ops", text: "Weekly digest: {{report.body}}", secretName: "SLACK_TOKEN", }, }, ],}Each tool step supports:
tool: stringargs: Record<string, unknown>outputKey?: string
Current tool names visible in the product UI include:
http.gethttp.postslack.postemail.sendlogsleep
code
The shared schemas include a code handler type, but the hosted worker currently rejects it. Do not build production Cloud integrations around hosted code handlers until that runtime is actually shipped.
Schedules
The SDK accepts schedule objects and a constrained schedule string grammar. The API still receives structured ScheduleConfig objects.
Every
schedule: { type: "every", interval: "15m",}Supported duration format:
100ms1s5m1h1d
String form:
schedule: "every 15 minutes"Daily
schedule: { type: "daily", times: ["09:00", "17:00"],}String form:
schedule: "daily at 9am"Weekly
schedule: { type: "weekly", days: ["mon", "wed", "fri"], time: "09:00",}String forms:
schedule: "weekdays at 5pm"schedule: "every friday at 9am"Monthly
schedule: { type: "monthly", day: "last-fri", time: "17:00",}String forms:
schedule: "monthly on the 1st at 9am"schedule: "monthly on the last friday at 9am"Supported day values:
- integers
1..31 "last""last-mon""last-tue""last-wed""last-thu""last-fri""last-sat""last-sun"
Once
schedule: { type: "once", at: "2026-03-15T09:00:00.000Z",}String form:
schedule: "once at 2026-03-15 09:00"Cron
schedule: { type: "cron", expression: "0 9 * * 1-5",}Timezone
Use an IANA timezone string:
timezone: "America/New_York"If omitted, the SDK defaults to UTC.
Execution controls
retryAttempts: 3,retryBackoff: "exponential",retryDelay: "30s",timeout: "5m",Constraints from the shared schema:
retryAttempts:1..10retryBackoff:"linear"or"exponential"retryDelay: duration stringtimeout: duration string
Lifecycle controls
Pause on create
active: falseLimit total runs
maxRuns: 20Expire at a specific time
expiresAt: "2026-06-30T23:59:59.000Z"Metadata
Treat metadata as the bridge between Cronlet and your product data model.
Recommended metadata shape:
metadata: { userId: "user_123", orgId: "org_456", feature: "scheduled-report", entityId: "report_789", createdBy: "agent",}Use stable IDs, not display names.
Patching tasks
await cronlet.tasks.patch(taskId, { schedule: { type: "every", interval: "1m", }, timezone: "UTC", callbackUrl: null, metadata: { userId: "user_123", monitorId: "monitor_456", mode: "degraded", }, maxRuns: null, expiresAt: null,});patch() supports updates for:
namedescriptionhandlerscheduletimezoneretryAttemptsretryBackoffretryDelaytimeoutactivecallbackUrlmetadatamaxRunsexpiresAt
Runs
Task execution creates runs with:
statusattemptdurationMsoutputlogserrorMessage
Read runs via:
await cronlet.runs.list(taskId, 20);await cronlet.runs.get(runId);Best practices
- make task names readable in the dashboard
- use metadata for ownership and reconciliation
- store
task.idin your own database - prefer
webhookwhen your product already owns the business logic - prefer
toolswhen you want Cronlet-hosted step orchestration - use
maxRunsandexpiresAtfor bounded workflows