> ## Documentation Index
> Fetch the complete documentation index at: https://docs.callkaro.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Build a Workflow

> The canvas, and what each step does

## The canvas

A new workflow opens with a **Start** node. Click **+** on a node (*Add the next step here*) to add the step that follows it. Click any node to open its settings on the right, where you can also **Delete node**.

| Control | Use |
| - | - |
| **Workflow Name** | Required before saving. |
| **Find a node…** | Jumps to a node by name, wherever it sits on the canvas. |
| **Re-arrange** | Lays every node out left to right so none overlap. |
| **Zoom in / Zoom out / Reset view** | Move around a large workflow. |
| **Copy workflow ID** | Available once the workflow has been saved. |
| **Save** | Checks the workflow, then saves it. |

### What Save checks

Save refuses a workflow that cannot run, and says which node to fix:

* There is exactly **one Start** node.
* At least one path leads from **Start** to an **End**.
* Every connection points at a node that still exists.
* Each node's own settings are complete, e.g. a Send Call has an agent and an API Call has a valid URL.

It also **warns**, without blocking, about nodes nothing leads into, nodes that cannot be reached from Start, and branches with nothing after them. A run that reaches a step with nothing after it simply stops there.

## Steps

### Send Call

Calls the contact with a voice agent and waits for the call to end.

| Setting | Meaning |
| - | - |
| **Voice Agent** | The agent that places the call. |
| **Number of retries** | How many more times to call if the contact does not connect. |
| **Gap between retries in minutes** | One gap per retry. Leave a later one blank to reuse the previous gap. The default is 30 minutes. |

The run shows **Awaiting call** until the call, and every retry it needed, has finished. Steps after it can then branch on the outcome (connected, voicemail, hangup reason, duration, conversion status) and read the agent's post-call variables. If the call cannot be placed at all, the run moves straight on to the next step, and the error is shown in the run's timeline.

### Send WhatsApp Template

Sends an approved template. Choose the **Provider** and the **Sending number**, then the template. Bind each `{{placeholder}}` in it to a value, such as the contact's name from the CRM. A **Preview** shows the message as it will be sent.

<Note>
  A sending number is required. A workflow may message someone before any call has happened, so there is no number to infer.
</Note>

### Send WhatsApp Message

Sends free text. Type the **Message** and use `{{variable}}` placeholders anywhere in it, each bound to a value. Free text works with the WhatsApp and Heltar providers, and needs an open conversation window with the contact. To reach someone cold, use a template.

### Wait

Pauses the run for a set number of minutes (15 by default, at least 1), then continues.

### Conditions

Sends the contact down one of several paths. Add **Branch Rules**, each with its own exit. The first rule that matches decides where the contact goes, and **Otherwise** catches everyone who matched none. See [Conditions and values](/workflows/conditions-and-values).

### API Call — Basic

Calls your API.

| Setting | Meaning |
| - | - |
| **API URL** | Supports `{{variable}}` placeholders in the query string, e.g. `https://api.example.com/lead?name={{name}}`. |
| **URL Variables** | Binds each placeholder in the URL to a value. |
| **Headers** | Each header is a saved **Secret** or a **Custom** value. |
| **Request Body** | JSON, either typed as-is with placeholders, or built field by field. |
| **Body Variables** | Binds each placeholder in the body to a value. |

The response (`ok`, `status` and `body`) is available to later steps as a **Function Response**, so a Conditions step can branch on what your API said.

### API Call — Advanced

Runs your own JavaScript function in a secure sandbox. The function receives one argument, `context`, with everything the run knows:

```javascript theme={null}
async function checkServiceability(context) {
  const pincode = context.lead.pincode;            // the contact's CRM attributes
  const interested = context.post_call.interested; // the last call's post-call variables
  const res = await axios.get(`https://api.example.com/serviceable/${pincode}`, {
    headers: { Authorization: `Bearer ${x_secrets.MY_API_KEY}` },
  });
  context.post_call.serviceable = res.data.ok;     // kept for later steps
  return { serviceable: res.data.ok };
}
```

| `context.` | Holds |
| - | - |
| `lead` | The contact's CRM attributes |
| `post_call` | The last call's post-call variables |
| `call_metadata` | Data the contact was enrolled with, plus the call's metadata |
| `call_duration`, `hangup_reason`, `conversion_status`, `callSid` | The last call's outcome |
| `user_phone_number` | The contact's phone number |
| `nodes` | Earlier steps' results, by node id |
| `userId`, `workflow_id`, `run_id` | Identifiers for this run |

`axios`, `moment`, lodash (`_`), `sendEmail` and your secrets (`x_secrets`) are available. There is no file system or `require`. A run of the function is limited to 30 seconds.

What it returns is available to later steps as a **Function Response**. Changes it makes to `context.post_call` are kept for the rest of the run.

### Update Contact

Writes values onto the contact in the CRM. Add a row per attribute and choose where each value comes from: a fixed value, a post-call variable, call metadata, and so on. A value that does not fit the attribute's type is skipped, and so is any attribute someone set by hand in the CRM. See [Keeping contacts up to date](/crm/keeping-contacts-updated).

### End

Finishes the run. A workflow can have several, one per path.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.