One-Off Jobs
When to use one-off jobs
Section titled “When to use one-off jobs”Use a one-off job when you need something to happen once at a specific time, not on a recurring cadence.
- Delayed action: send a follow-up email in 2 hours
- Scheduled event: expire a trial on a specific date
- Deferred work: process an upload after a 30-second buffer
- Immediate background task: offload work from the request path
If the task recurs, use a schedule instead.
The Node.js examples use a client from the Node.js SDK (0.2.0 or later):
import { Chronos } from '@chronos.sh/sdk';
const chronos = new Chronos({ apiKey: process.env.CHRONOS_API_KEY! });The Go examples use a client from the Go SDK (Go 1.26 or later), inside a function that returns an error. They also use the standard library’s context, errors, fmt, os and time packages:
import chronos "chronos.sh/go"
client, err := chronos.NewClient(os.Getenv("CHRONOS_API_KEY"))if err != nil { return err}ctx := context.Background()Timing options
Section titled “Timing options”Immediate
Section titled “Immediate”Omit both delay and scheduled_for. The job runs as soon as possible:
curl -X POST https://api.chronos.sh/v1/jobs \ -H "Authorization: Bearer chrns_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "name": "Process upload", "handler": "process-upload", "payload": { "fileId": "file_abc123" } }'await chronos.jobs.create({ name: 'Process upload', handler: 'process-upload', payload: { fileId: 'file_abc123' },});_, err = client.Jobs.Create(ctx, chronos.JobCreateParams{ Name: "Process upload", Handler: new("process-upload"), Payload: map[string]any{"fileId": "file_abc123"},})if err != nil { return err}Relative delay
Section titled “Relative delay”Use delay to run after a duration from now:
curl -X POST https://api.chronos.sh/v1/jobs \ -H "Authorization: Bearer chrns_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "name": "Send follow-up", "handler": "send-email", "delay": { "value": 2, "unit": "hour" }, "payload": { "userId": "usr_123", "template": "follow-up" } }'await chronos.jobs.create({ name: 'Send follow-up', handler: 'send-email', delay: { value: 2, unit: 'hour' }, payload: { userId: 'usr_123', template: 'follow-up' },});_, err = client.Jobs.Create(ctx, chronos.JobCreateParams{ Name: "Send follow-up", Handler: new("send-email"), Delay: &chronos.Interval{Value: 2, Unit: chronos.IntervalUnitHour}, Payload: map[string]any{"userId": "usr_123", "template": "follow-up"},})if err != nil { return err}Available units: second, minute, hour, day, week
Absolute time
Section titled “Absolute time”Use scheduled_for to run at a specific moment:
curl -X POST https://api.chronos.sh/v1/jobs \ -H "Authorization: Bearer chrns_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "name": "Expire trial", "handler": "expire-trial", "scheduled_for": "2027-02-15T00:00:00Z", "payload": { "accountId": "acc_456" } }'await chronos.jobs.create({ name: 'Expire trial', handler: 'expire-trial', scheduled_for: '2027-02-15T00:00:00Z', payload: { accountId: 'acc_456' },});_, err = client.Jobs.Create(ctx, chronos.JobCreateParams{ Name: "Expire trial", Handler: new("expire-trial"), ScheduledFor: new(time.Date(2027, 2, 15, 0, 0, 0, 0, time.UTC)), Payload: map[string]any{"accountId": "acc_456"},})if err != nil { return err}The time must be in the future (ISO 8601 format).
Idempotency keys
Section titled “Idempotency keys”Prevent duplicate jobs with idempotency_key:
curl -X POST https://api.chronos.sh/v1/jobs \ -H "Authorization: Bearer chrns_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "name": "Charge invoice inv_789", "handler": "charge-invoice", "delay": { "value": 1, "unit": "hour" }, "idempotency_key": "charge-inv_789", "payload": { "invoiceId": "inv_789" } }'import { ChronosApiError } from '@chronos.sh/sdk';
try { await chronos.jobs.create({ name: 'Charge invoice inv_789', handler: 'charge-invoice', delay: { value: 1, unit: 'hour' }, idempotency_key: 'charge-inv_789', payload: { invoiceId: 'inv_789' }, });} catch (err) { // The job already exists, so a retried create can treat this as success. if (!(err instanceof ChronosApiError && err.code === 'idempotency_key_conflict')) throw err;}_, err = client.Jobs.Create(ctx, chronos.JobCreateParams{ Name: "Charge invoice inv_789", Handler: new("charge-invoice"), Delay: &chronos.Interval{Value: 1, Unit: chronos.IntervalUnitHour}, IdempotencyKey: new("charge-inv_789"), Payload: map[string]any{"invoiceId": "inv_789"},})// The job already exists, so a retried create can treat this as success.if apiErr, ok := errors.AsType[*chronos.APIError](err); ok && apiErr.Code == chronos.ErrorCodeIdempotencyKeyConflict { err = nil}if err != nil { return err}If a job with the same idempotency_key already exists on your account, the API returns 409 with code idempotency_key_conflict instead of creating a duplicate. The Node.js SDK throws it as a ChronosApiError, and the Go SDK returns it as an *APIError.
Use this when the trigger for job creation might fire multiple times (webhook retries, user double-clicks, distributed systems with at-least-once delivery).
The key is scoped to your account and persists for the lifetime of the job. Max 255 characters.
Push delivery for one-off jobs
Section titled “Push delivery for one-off jobs”One-off jobs support the same delivery configuration as schedules:
curl -X POST https://api.chronos.sh/v1/jobs \ -H "Authorization: Bearer chrns_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "name": "Send webhook notification", "handler": "notify", "delay": { "value": 5, "unit": "minute" }, "delivery": { "type": "push", "http": { "url": "https://your-app.com/hooks/chronos", "method": "POST" } }, "payload": { "event": "subscription.created", "data": { "id": "sub_123" } } }'await chronos.jobs.create({ name: 'Send webhook notification', handler: 'notify', delay: { value: 5, unit: 'minute' }, delivery: { type: 'push', http: { url: 'https://your-app.com/hooks/chronos', method: 'POST' }, }, payload: { event: 'subscription.created', data: { id: 'sub_123' } },});_, err = client.Jobs.Create(ctx, chronos.JobCreateParams{ Name: "Send webhook notification", Handler: new("notify"), Delay: &chronos.Interval{Value: 5, Unit: chronos.IntervalUnitMinute}, Delivery: &chronos.Delivery{ Type: chronos.DeliveryTypePush, HTTP: &chronos.HTTPDelivery{URL: "https://your-app.com/hooks/chronos", Method: "POST"}, }, Payload: map[string]any{"event": "subscription.created", "data": map[string]any{"id": "sub_123"}},})if err != nil { return err}If delivery is omitted, pull is the default. Your worker picks it up.
Configuring retries and timeout
Section titled “Configuring retries and timeout”Override defaults per job. The same fields work in chronos.jobs.create, and as Timeout and MaxRetries in JobCreateParams:
{ "name": "Critical charge", "handler": "charge-invoice", "delay": { "value": 1, "unit": "hour" }, "timeout": 60, "max_retries": 5, "payload": { "invoiceId": "inv_789" }}| Field | Default | Description |
|---|---|---|
timeout |
30 |
Seconds before execution is considered timed out |
max_retries |
3 |
Number of retry attempts on failure. 0 = no retries. |
Querying one-off jobs
Section titled “Querying one-off jobs”To list only one-off jobs, filter by origin=api. Each example returns the first page, newest first:
curl "https://api.chronos.sh/v1/jobs?origin=api" \ -H "Authorization: Bearer chrns_your_api_key"const { data, paging } = await chronos.jobs.list({ origin: 'api' });page, err := client.Jobs.List(ctx, chronos.JobListParams{Origin: chronos.JobOriginAPI})if err != nil { return err}fmt.Println(len(page.Data), page.Paging.Total)