Scheduling — api.scheduleNextStep()
api.scheduleNextStep() queues the next step to execute after a delay. Calling it multiple times in the same step creates a fan-out — parallel branches that each run independently.
Signature
Section titled “Signature”await api.scheduleNextStep({ delay, action, payload });| Option | Type | Description |
|---|---|---|
delay | number | string | Seconds until the next step runs. Accepts a number or a duration string. Minimum: 10 seconds. Maximum: 400 days |
action | string | The method name on your Workflow class to call |
payload | any | Data passed as the data argument to the next step |
Duration strings
Section titled “Duration strings”Instead of raw seconds, you can pass a human-readable string:
| String | Duration |
|---|---|
"30 sec" / "30 seconds" | 30 seconds |
"5 min" / "5 minutes" | 5 minutes |
"2 hr" / "2 hours" | 2 hours |
"1 day" / "3 days" | 1 or 3 days |
"1 week" / "2 weeks" | 1 or 2 weeks |
Simple linear chain
Section titled “Simple linear chain”class Workflow { async start(data, headers, api) { // Schedule the next step to run in 10 minutes await api.scheduleNextStep({ delay: '10 min', action: 'sendReminder', payload: { orderId: data.id, email: data.email }, }); }
async sendReminder({ orderId, email }, headers, api) { api.log('Sending reminder for order', orderId, 'to', email); // ... send email via fetch() }}Fan-out (parallel branches)
Section titled “Fan-out (parallel branches)”Call scheduleNextStep() multiple times in one step to launch parallel branches. Each call is an independent branch. The run is not marked complete until all branches finish.
class Workflow { async start(data, headers, api) { // Process each line item in parallel for (const item of data.line_items) { await api.scheduleNextStep({ delay: 10, action: 'processItem', payload: { itemId: item.id, title: item.title }, }); } // start() completes here — all N branches are now running concurrently }
async processItem({ itemId, title }, headers, api) { api.log('Processing item:', title); // Each branch runs independently }}Chain depth
Section titled “Chain depth”Each call to scheduleNextStep() increments the chain depth. Fan-out branches at the same level all share the same depth counter — a fan-out of 100 branches from start has depth 1, not 100.
There is no per-plan chain depth limit. A platform-wide circuit breaker prevents runaway infinite loops.
Deduplication — api.dedupe()
Section titled “Deduplication — api.dedupe()”api.dedupe() prevents a step from running more than once for the same logical event within a time window. It is useful for idempotent guards at the start of your start step.
const { locked } = await api.dedupe(unique_id, delay);| Argument | Type | Default | Description |
|---|---|---|---|
unique_id | string | required | An identifier that should be unique per logical event |
delay | number | 120 | How long (in seconds) to hold the lock before it expires |
Returns { locked: boolean, key: string }. If locked is true, another invocation already acquired the lock — skip processing.
class Workflow { async start(data, headers, api) { const { locked } = await api.dedupe(`order-${data.id}`, 300); if (locked) { api.log('Duplicate event for order', data.id, '— skipping'); return; }
// Safe to process — this invocation holds the lock await api.scheduleNextStep({ delay: 1, action: 'processOrder', payload: data }); }}