Scheduled Outbound Calls
Scheduling an outbound call runs a published workflow at a chosen time, so a Dial node inside it places the call and the workflow handles whatever answers.
Use this when a call should happen later rather than now — a callback requested through a web form, a reminder before an appointment, or a follow-up after an event.
Key concepts
Section titled “Key concepts”| Term | Definition |
|---|---|
| Schedule | The timer. Holds when to run, which workflow version to run, and the data to run it with. |
| Identifier | What the schedule does when it fires. Use llm-chain.workflow.execute to run a workflow. |
| Run at | The moment the call should be placed, read in the schedule’s timezone. |
| Payload | The workflow version to run, plus the context the workflow reads as it executes. |
| Context | Free-form data available to the workflow as ${context....} — typically the person’s number. |
What you can do
Section titled “What you can do”| Action | Description |
|---|---|
| Schedule a call | Create a one-time schedule pointing at a workflow that contains a Dial node. |
| Check what happened | Read the schedule and its execution history to see whether the call was placed. |
| Cancel a pending call | Delete the schedule before its run time. |
Before you start
Section titled “Before you start”The workflow must be published and must contain a Dial node. Note its workflow version ID — the schedule runs a specific version, so a call scheduled today runs the workflow as it exists today, not as it may be edited tomorrow.
The Dial node’s To field should read from context, for example ${context.customer.phone}, so one workflow serves every customer.
Schedule a call
Section titled “Schedule a call”curl -X POST "$OCTO_BASE_URL/schedules" \ -H "Authorization: Bearer $OCTO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Callback for Alex Morgan", "identifier": "llm-chain.workflow.execute", "scheduleType": "one_time", "runAt": "2026-08-20T14:30:00Z", "payload": { "workflowVersionId": "workflow_version_123", "context": { "customer": { "phone": "+14155550123", "name": "Alex Morgan" } } } }'| Field | Required | Notes |
|---|---|---|
name | yes | Human-readable label. |
identifier | yes | llm-chain.workflow.execute to run a workflow. |
scheduleType | yes | one_time for a callback. recurring takes a cron instead of a runAt. |
runAt | yes | When to call. See below. |
timezone | conditional | Required when runAt has no UTC offset. |
payload.workflowVersionId | yes | The published workflow version to run. |
payload.context | no | Free-form data the workflow reads. |
Choosing the run time
Section titled “Choosing the run time”runAt takes one of two forms, and they behave differently.
An exact moment — 2026-08-20T14:30:00Z, or with an offset such as +05:30. Stored as UTC. Any timezone sent alongside it is ignored.
A local wall-clock time — 2026-08-20T09:00:00 — must be paired with an explicit timezone:
{ "runAt": "2026-08-20T09:00:00", "timezone": "America/New_York" }This is stored as written, so it stays at 9am local even if that zone’s daylight-saving rules change before the call is due. Prefer it for anything expressed as a local hour.
A local time sent without a timezone is rejected rather than assumed. Guessing wrong there means calling someone in the middle of the night.
Two further rules apply: runAt must be in the future, and no more than two years ahead.
What happens when it fires
Section titled “What happens when it fires”- The schedule starts the workflow version named in the payload.
- Steps before the Dial node run first, so a CRM lookup or ticket can complete before the phone rings.
- The Dial node places the call, and the workflow pauses while it rings.
- The workflow resumes on the branch matching the outcome — Connected, Voicemail, No answer, or Failed.
A one-time schedule marks itself complete after it fires, so the same request cannot place a second call.
Check what happened
Section titled “Check what happened”curl "$OCTO_BASE_URL/schedules/schedule_123" \ -H "Authorization: Bearer $OCTO_API_KEY"The response carries the schedule’s status — active while it waits, completed once it has fired — along with its run time and payload. Execution history for the schedule records whether each run succeeded and the error if it did not.
Cancel a pending call
Section titled “Cancel a pending call”curl -X DELETE "$OCTO_BASE_URL/schedules/schedule_123" \ -H "Authorization: Bearer $OCTO_API_KEY"Deleting a schedule before its run time disarms the timer, and no call is placed.
Error handling
Section titled “Error handling”| Situation | Result |
|---|---|
runAt is in the past, or more than two years ahead | Rejected when the schedule is created. |
Local runAt with no timezone | Rejected — send an offset or an explicit zone. |
| An identical schedule already exists | Rejected, so a double submission does not book two calls. |
| Caller ID is not a number you own | The call is not placed; the workflow takes the Failed branch. |
| Nobody answers | The workflow takes the No answer branch. Nothing retries automatically. |
Does the schedule call the customer, or does the workflow? The workflow does. The schedule only decides when the workflow runs; the Dial node inside it places the call.
Can one schedule call several people? No. One schedule runs one workflow once. Create a schedule per call, or design a workflow that loops.
What if the workflow is edited after I schedule the call? The schedule runs the workflow version it was given, so later edits do not change a call that is already booked. Point a new schedule at the new version to pick up changes.
Can I schedule a recurring call?
scheduleType: "recurring" with a cron expression is supported, but recurring automated calls to the same person are heavily regulated. Prefer one-time schedules for customer contact.
Related pages
Section titled “Related pages”- Dial Node - the node that places the call
- Versions and Publishing - publish the workflow version a schedule runs
- Phone Numbers - purchase and manage caller IDs