Guides
Tasks
A task dials a list of recipients with one agent, following rules you set up front - budget, calling window, number of attempts. Nothing gets dialed until you start the task yourself.
What a task is
A task (task_...) is an outbound campaign: one agent on the volai engine, a list of recipients with a phone number and optional variables for the system prompt, and rules (budget, calling window, maximum attempts and call duration). Three types by purpose - invoice (payment reminder), appointment (appointment confirmation) and custom (anything else) - for the first two, when source is integration, every recipient additionally carries a reference to the invoice or event (externalRefs) that the agent mentions during the call.
The agent must be on volai's own engine (useForOutboundTasks: true at creation, see POST /v1/agents), not ElevenLabs - the engine supports system-prompt variables, call-duration caps and verification that its configuration hasn't changed while the campaign was running. The agent's draft must be PUBLISHED (PATCH /v1/agents/{id} or POST /v1/agents/{id}/draft/publish) AND the published version must be TESTED (POST /v1/agents/{id}/simulate) - skip either step and start returns agent_not_ready.
Statuses and transitions
In practice a task always goes from `draft` straight to `running` (`start`); deleting (`DELETE`) only works in the marked statuses.
draftPATCH), deletable.readydraft and running that start accepts as a valid starting point - no public endpoint (REST or MCP) actually moves a task into it today, so you won't encounter it in practice. If it did occur, it's editable and deletable just like draft.runningPATCH returns task_not_editable, no exception. Anyone who needs to change variables must stop the task first (POST .../pause) and edit it while paused - and only while no recipient has been dialed yet; after the first attempt recipients can no longer be replaced (items_immutable), so create a new task instead. Cannot be deleted.pausedPOST .../pause); a call already in flight finishes. Editable, deletable, start resumes it.completedneeds_attentionagent_version_changed) or the engine was temporarily unreachable (engine_contract_unavailable). POST .../reconcile reconciles the state; deletable as long as no item is still in_progress.The exact delete condition: the status is one of the marked ones AND no item has status: "in_progress" (a call that started dialing and hasn't finished) - otherwise task_not_deletable.
Recipient import (CSV)
POST /v1/tasks/csv-preview parses a CSV and returns it back with per-row errors - NOTHING is saved, it's just a preview before creating or updating a task. The new-task form in the portal calls the same function.
- The delimiter is detected automatically (comma or semicolon) from the first line.
- A
phonecolumn must exist (case-insensitive) - otherwise the whole CSV fails with aphoneerror. - Every other column is a variable for the agent's system prompt (
{{column_name}}) - at most 20 columns, name up to 64 characters, value up to 512 characters. - The phone number is normalized the same way as everywhere in the API (
normalizePhone) - an invalid or empty number is an error only on THAT row, the rest of the CSV is still processed. - A cell that looks like a spreadsheet formula (starts with
=,+,-or@) is rejected - protection against CSV injection when the file is opened in Excel. - At most 100 rows per CSV (the same cap as a task's recipient count) and 1 MB of raw text.
The response carries headers, delimiter, rows (each row with its own errors), validRows (only the clean ones, in exactly the shape recipients in POST /v1/tasks expects) and a summary errors array. Send validRows straight through as recipients when creating or updating the task.
Budget and calling window
Budget
budgetHal is the spending CAP for the whole task, in hellers. At creation it must cover at least one attempt at the maximum duration (budget_too_low otherwise); at start, before EVERY dial the estimated call price is reserved (reservedBudgetHal), and only an answered call turns it into spentHal. A task never spends more than the budgetHal you gave it.
Calling window
callingWindow is {timezone: "Europe/Prague", start, end, days} - today the only supported timezone is Prague, start/end as HH:MM, and days are numbers 0-6 (0 = Sunday). Without one, weekdays 9:00-18:00 apply. Outside the window, start returns window_closed - either wait for it to reopen, or change the window with PATCH.
Attempts and call duration
maxAttempts (1-3) says how many times an unanswered number is retried; maxDurationSecs (30-300) caps the length of ONE call, after which it ends automatically - both optional, the engine uses sensible defaults without them.
Global throughput
Dialing for every task on the WHOLE platform (not just your account) goes through one cron job that runs every 2 minutes and processes at most 20 items per run - BUT that cap is shared across concurrently running tasks, not something a single task can use up on its own: exactly ONE recipient is dialed from EACH active task per run. So even on a completely empty platform, one task never dials more than one recipient every 2 minutes - 100 recipients therefore doesn't finish in ~20 minutes but in roughly 200 minutes (100 x 2 minutes). Plan the calling window for time-sensitive campaigns accordingly (for example an appointment reminder the day before).
Flow for an agent
A typical run, in this order:
- The task's agent must be PUBLISHED and the published version TESTED (
POST /v1/agents/{id}/draft/publish, thenPOST /v1/agents/{id}/simulate) - otherwise a laterstartreturnsagent_not_ready. - Optionally
POST /v1/tasks/csv-preview- checks the recipient CSV before creating the task. POST /v1/tasks- creates the task indraftstatus. Nothing gets dialed.GET /v1/tasks/{id}- readissues(dry-run validation) and confirm the task is ready to start.- As needed,
PATCH /v1/tasks/{id}- adjust the rules, ALWAYS with therevisionfrom the last read. POST /v1/tasks/{id}/start- starts dialing. Every answered call is billed immediately.POST /v1/tasks/{id}/pauseat any point while it runs;POST /v1/tasks/{id}/startagain resumes it.- After an interruption (an outage,
needs_attention),POST /v1/tasks/{id}/reconcilereconciles the state against the actual calls. DELETE /v1/tasks/{id}at the end, if the task never ran or already finished and you want the record gone.
Almost every write call (PATCH, start, pause, DELETE) takes revision from the last read of the task - the same optimistic concurrency as agent drafts. A mismatch returns revision_conflict: read the task again (GET) and repeat with the current revision. reconcile is the exception - its revision is optional.
MCP
The same seven tools as REST (list_tasks, get_task, create_task, update_task, pause_task, reconcile_task, start_task) - except for one difference at start.
start_task confirms the numbers
MCP start_task additionally takes confirmedRecipients and confirmedBudgetHal - they must match the task's actual recipient count and budget exactly, otherwise it returns validation with the real values spelled out and starts NOTHING. A third-party AI agent acting on one loosely understood instruction can't accidentally launch a paid campaign on made-up numbers this way - REST POST .../start has no such mechanism, because there the account owner calls it directly with their own key.
delete_task and the CSV preview are not in MCP - deletion stays in REST and the portal (an irreversible action that requires typing the name), and the CSV preview is just a form step.
Errors
task_not_found- No task with thisidexists on the account.task_not_editable- The task is running or completed, or isneeds_attentionwithout a way to recover - its rules can't be changed right now.items_immutable- The recipient list can't be replaced once at least one dial attempt has happened.budget_too_low- The budget doesn't cover even one attempt at the maximum call duration - raisebudgetHalor lowermaxDurationSecs.task_not_startable- The task isn't indraft/ready/pausedstatus, or has no recipient left to dial.task_needs_attention- APOST .../reconcileis needed before the next start - something (the agent version, the engine) changed while it ran.agent_not_ready- The agent's draft is either not published or the published version hasn't been tested - publish it (POST /v1/agents/{id}/draft/publish) and then simulate the published version (POST /v1/agents/{id}/simulate) before starting.agent_version_changed- The agent's configuration changed WHILE the task was running - test it and restart viareconcile.engine_required- The task's agent is not on the volai engine (useForOutboundTasks: true) - outbound tasks require the engine today, not ElevenLabs.engine_contract_unavailable- The engine does not currently advertise support for outbound-task variables and caps - try starting again later.window_closed- Outside the configured calling window - wait for it to reopen, or changecallingWindow.task_not_running- Pausing only works on a running task.task_not_deletable- Only a task without an item in progress, indraft/ready/paused/completed/needs_attention, can be deleted.revision_conflict- The task changed in the meantime - read it again (GET) and repeat with the currentrevision.
The full error table for every endpoint is in the Tasks section of the REST API reference.
Related
MCP server
Connect Claude Code, Cursor and other AI editors.
REST API
Complete reference for every endpoint: numbers, calls, SMS, agents and their drafts, tools, recordings, webhooks, do-not-call list, relay, Google Calendar.
Voice agent
System prompt, tools, handoff to a human, recordings and structured call data.