Skip to main content
An automation is a script that CallVA hosts and runs for one project. Use it to react to a finished call, sync results into a CRM, send a follow-up SMS, prepare a batch of calls every night, or hand an agent fresh data in the middle of a conversation - without running a server of your own. Automations run on Windmill, the workflow engine embedded in CallVA. You never talk to Windmill directly: you create, deploy, run and read automations through the CallVA API, the CLI or MCP, and CallVA keeps each project’s scripts, variables and runs in a folder of its own.
Permissions: automations.read to list and read automations and runs, automations.create, automations.update (also deploying code), automations.delete, and automations.manage to run an automation and manage its HTTP triggers. Variables have variables.read, variables.create, variables.update, variables.delete. See Authentication.

How an automation runs

There are four ways in: Over HTTP and on demand, the JSON object’s top-level keys are matched to the parameters of the script’s main by name. Key order does not matter, a missing key takes the parameter’s default, and keys main does not declare are carried in the run’s args but not passed in. So a script receiving a call event webhook declares the envelope keys it needs:

The runtime

  • Language. Write TypeScript for Deno and deploy it with "language": "deno". A new automation starts from a TypeScript scaffold; the code endpoint also accepts bun, python3, bash, go and nativets. Deno scripts import npm packages with npm: specifiers, for example npm:windmill-client@1.
  • Sandbox. Outbound fetch() is allowed; there is no host filesystem. Other scripts of the same project can be imported by path.
  • Timeout. Every run has the same ceiling, set by the platform and not per automation. Get the automation runtime reports it as max_timeout_seconds.
  • Versions. Each code deploy is a new immutable version identified by a hash. Roll back by deploying the earlier code again. Right after a deploy, Windmill resolves the script’s dependencies for a few seconds before it can run.
The values above are what the development environment answers; read your own before relying on a timeout.

Variables and secrets

Variables hold the configuration and credentials your scripts read at run time: API keys, base URLs, labels. Never put a secret in a script’s source. Each variable belongs to the project and lives at f/proj_<last 12 hex digits of the project id>/<NAME>. Names are upper case letters, digits and underscores, starting with a letter. is_secret defaults to true; a secret’s value is never returned by the API once stored. Create a variable:
Response 201
List variables returns every variable of the project at once, without pagination. Only non-secret variables carry their value:
Update and delete address a variable by its full path; a path outside the project’s folder is refused with 403. An empty value clears the variable.
A script reads a variable by path with the Windmill client:
To let a script call CallVA, create an API token for the project and store it as a secret variable (for example CALLVA_API_KEY), with the API base URL beside it as a non-secret one (CALLVA_API_URL). The token’s role decides what the script may do.

Build, run and read an automation

1

Create the automation

Create an automation with a name and an optional description. CallVA deploys a scaffold script at a path derived from the name, so the automation can run straight away.
Response 201
The windmill_path is fixed at creation: renaming the automation later does not move it.
2

Deploy your code

Write the script. This one takes the call as a nested call object - the shape that makes its runs findable under the call - reads a variable, logs with phase tags, and returns a structured result:
tag_finished_call.ts
Replace the automation’s code with code and language:
When Windmill refuses the code, the answer carries errors.deployment_state: previous_active means the version that was live before is still the one that runs.
3

Read the code back

Get an automation’s code returns what is live, with its language, hash and timeout:
4

Run it

Run an automation. The arguments go in input; the answer is the run’s job_id as soon as the run is queued. An inactive automation answers 422 Automation is not active.
5

Read the run

Get an automation run returns its arguments, result, logs and timing:
Response (trimmed)
A run still in progress has "type": "QueuedJob" and no result yet; read it again until it is a CompletedJob. A failed run has "success": false; read its result and logs for the error.
The callva CLI wraps the same loop: callva automations create, callva automations deploy <id> --file tag_finished_call.ts, callva automations run <id> --args '{"dry_run":true}', callva automations runs <id> and callva automations run-detail <id> <job_id>. See CLI.

Writing back to the call

An automation that handles a call usually ends by writing what it found onto the call record, through Update a call. The body is the call’s custom fields by key; only the fields you send change.
Return an object with at least a status and enough detail to debug a run without redeploying, and default every parameter so the script also runs from a schedule. A mutating script should accept dry_run and be tried with it first.

HTTP triggers

An HTTP trigger gives an automation a URL. Create an automation trigger:
string
required
post or get.
string
required
async answers at once with the run’s job id. sync waits for the run and answers with its result - use it where the caller needs the answer, such as an agent’s custom config URL or a tool the agent calls mid-conversation.
string
required
none or signature. With signature, secret (at least 8 characters) is required and CallVA stores it as a secret variable of the project.
string
The last segment of the URL: lower-case letters, digits, - and _, up to 100 characters. Defaults to the last 12 characters of the automation id. Route paths are shared across the whole automation engine, so pick one that is specific to you.
Response 201 (trimmed)
webhook_url is the address to call; its host is the automation engine of the CallVA environment you work in, and the API returns the full address. An async trigger answers with the job id; a sync trigger answers with the run’s result:
To run an automation on a call’s events, put its trigger URL in the agent’s custom_webhook_url and choose the events in custom_webhook_events (see Call event webhooks). Each event then starts one run with the envelope as its arguments, and because the envelope nests the call under call, every such run appears under the call. List automation triggers, update one (http_method, request_type, route_path, auth_method, secret), turn one on or off and delete one address it by its path. A disabled trigger answers its URL with 404. A change to a trigger can take a few seconds to reach its URL.
A trigger with auth_method: "none" runs for anyone who knows its URL. A trigger created with auth_method: "signature" currently refuses every request with 404 (Resource ... not found), so it cannot be used yet. Until it can, treat the URL as the secret: use a long, unguessable route_path, and have the script confirm what it is told (for example by reading the call from the API) before it changes anything.

Runs

Three views list runs. Each run carries Windmill’s job fields; the project-wide views add a normalised status (success, failed, running, queued, canceled), a triggered_by (webhook for HTTP-trigger runs; runs started through the API and by a schedule are both reported as schedule; api for the rest, such as the dependency jobs after a deploy), and the automation_id and automation_name it belongs to.

One automation’s runs

List an automation’s runs, newest first. Pages have no total; page with page and per_page (default 20) until a page comes back short. The list also contains the short dependencies jobs Windmill runs after each deploy (job_kind: "dependencies").
Response (trimmed)
This view leaves args empty; read a run’s arguments with the run detail or the project-wide view below.

Every automation’s runs

List all automation runs in the project, with filters:
Response (trimmed)

Runs for one call

List a call’s automation runs finds every run whose arguments contain {"call": {"id": "<call id>"}}, with include_args on by default. It answers 404 when the call is not in the project.
Response (trimmed)
The last run was started with POST /automations/{id}/run and is still reported as schedule; see the note on triggered_by above. Only runs that received the call as a nested call object with an id are found. A run started with a flat call_id, or with no call at all, is not. For an agent tool that starts an automation mid-call, send {"call": {"id": "{{context.call_id}}"}} in the tool’s request body; the voice runtime fills in the live call id. A batch or sweep that touches many calls has no single call to name, so it takes no call parameter.

Manage automations

Response (trimmed)

Webhooks

Call event payloads, and schedules that run an automation at set times.

Custom fields

The call fields an automation writes back to.