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

# Workflow Quickstart

> Create, publish and run your first Bolna workflow with curl: a qualification call with an automatic retry ladder for unreachable contacts.

This quickstart builds a workflow that calls a contact with one of your agents, ends successfully when the call completes, and retries up to three times with a 2-minute gap when the contact is busy or doesn't answer.

You need an existing agent — grab its `agent_id` from the dashboard or the [List Agents API](/docs/api-reference/agent/v2/get_all).

<Steps>
  <Step title="Create the workflow">
    Create the container with the [Create Workflow API](/docs/api-reference/workflows/create). It starts with an empty draft at revision `0`:

    <CodeGroup>
      ```bash request theme={"system"}
      curl --location 'https://api.bolna.ai/workflows' \
      --header 'Authorization: Bearer <api_key>' \
      --header 'Content-Type: application/json' \
      --data '{
        "name": "Lead qualification"
      }'
      ```

      ```json response theme={"system"}
      {
        "id": "1f2a3b4c-5d6e-7f80-91a2-b3c4d5e6f708",
        "name": "Lead qualification",
        "status": "active",
        "latest_published_version": null,
        "created_at": "2026-08-22T10:00:00Z",
        "updated_at": "2026-08-22T10:00:00Z"
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="Save the definition">
    Save the graph with the [Save Draft API](/docs/api-reference/workflows/save-draft). `expected_revision` is the revision you last read — `0` for a fresh workflow — and each save returns the next one.

    The graph: `start` routes to the `agent` call. A completed call ends as `reached`; a busy or unanswered call goes to the `retry` node, which re-runs the call up to 3 times, 2 minutes apart, before giving up as `unreachable`. Anything unhandled falls through to the workflow-wide `on_no_match` ending.

    <CodeGroup>
      ```bash request theme={"system"}
      curl --location --request PUT 'https://api.bolna.ai/workflows/1f2a3b4c-5d6e-7f80-91a2-b3c4d5e6f708/draft' \
      --header 'Authorization: Bearer <api_key>' \
      --header 'Content-Type: application/json' \
      --data '{
        "expected_revision": 0,
        "definition": {
          "entry_node_id": "n_start",
          "on_no_match": "n_end_unhandled",
          "nodes": [
            {
              "id": "n_start",
              "type": "start",
              "config": { "trigger": { "kind": "manual" } },
              "cases": [
                { "when": { "always": true }, "then": { "to": "n_call" } }
              ]
            },
            {
              "id": "n_call",
              "type": "agent",
              "name": "Qualify",
              "config": { "agent_id": "<agent_id>", "timeout_s": 3600 },
              "cases": [
                {
                  "when": { "cmp": "==", "left": { "var": "call.status" }, "right": { "const": "completed" } },
                  "then": { "to": "n_end_reached" }
                },
                {
                  "when": { "cmp": "in", "left": { "var": "call.status" }, "right": { "const": ["busy", "no_answer"] } },
                  "then": { "to": "n_retry" }
                }
              ]
            },
            {
              "id": "n_retry",
              "type": "retry",
              "config": {
                "target_node_id": "n_call",
                "attempts": [
                  { "delay": { "minutes": 2 } },
                  { "delay": { "minutes": 2 } },
                  { "delay": { "minutes": 2 } }
                ]
              },
              "cases": [
                { "when": { "always": true }, "then": { "to": "n_end_unreachable" } }
              ]
            },
            { "id": "n_end_reached", "type": "end", "config": { "label": "reached", "outcome": "success" } },
            { "id": "n_end_unreachable", "type": "end", "config": { "label": "unreachable", "outcome": "failure" } },
            { "id": "n_end_unhandled", "type": "end", "config": { "label": "unhandled", "outcome": "failure" } }
          ]
        }
      }'
      ```

      ```json response theme={"system"}
      {
        "revision": 1
      }
      ```
    </CodeGroup>

    <Note>
      A stale `expected_revision` returns `409 revision_conflict` with the `current_revision` — re-read the draft and retry. This protects you when several editors (or the dashboard canvas) touch the same draft.
    </Note>
  </Step>

  <Step title="Validate">
    Check the graph without mutating anything using the [Validate API](/docs/api-reference/workflows/validate). Issues with `severity` `error` block publish; warnings do not.

    <CodeGroup>
      ```bash request theme={"system"}
      curl --location --request POST 'https://api.bolna.ai/workflows/1f2a3b4c-5d6e-7f80-91a2-b3c4d5e6f708/validate' \
      --header 'Authorization: Bearer <api_key>' \
      --header 'Content-Type: application/json' \
      --data '{}'
      ```

      ```json response theme={"system"}
      {
        "issues": []
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="Publish">
    Freeze the draft as version 1 with the [Publish API](/docs/api-reference/workflows/publish). Publishing opens a fresh draft at revision `0` for future edits:

    <CodeGroup>
      ```bash request theme={"system"}
      curl --location --request POST 'https://api.bolna.ai/workflows/1f2a3b4c-5d6e-7f80-91a2-b3c4d5e6f708/publish' \
      --header 'Authorization: Bearer <api_key>' \
      --header 'Content-Type: application/json' \
      --data '{}'
      ```

      ```json response theme={"system"}
      {
        "version": 1,
        "published_at": "2026-08-22T10:05:00Z",
        "definition": null,
        "warnings": []
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="Run one contact">
    Run a contact through the latest published version with the [Run Workflow API](/docs/api-reference/workflows/run). This places a real call.

    <CodeGroup>
      ```bash request theme={"system"}
      curl --location 'https://api.bolna.ai/workflows/1f2a3b4c-5d6e-7f80-91a2-b3c4d5e6f708/run' \
      --header 'Authorization: Bearer <api_key>' \
      --header 'Content-Type: application/json' \
      --data '{
        "reference_id": "lead_10442",
        "mobile_number": "+919876543210",
        "name": "Asha"
      }'
      ```

      ```json response theme={"system"}
      {
        "execution_id": "exec:9a8b7c6d-5e4f-3a2b-1c0d-e9f8a7b6c5d4:lead_10442",
        "campaign_id": "9a8b7c6d-5e4f-3a2b-1c0d-e9f8a7b6c5d4",
        "workflow_version": 1,
        "status": "pending"
      }
      ```
    </CodeGroup>

    <Note>
      `reference_id` is the contact's identity: running it again returns `409 duplicate_run` with the existing `execution_id`, so the call is safely retryable. To run many contacts at once, use a [campaign](/docs/guides/workflows/campaigns) instead.
    </Note>
  </Step>

  <Step title="Poll the execution">
    Follow the contact with the [Get Execution API](/docs/api-reference/workflow-executions/get) until `status` is terminal (`completed`, `failed`, `cancelled` or `aborted`):

    <CodeGroup>
      ```bash request theme={"system"}
      curl --location 'https://api.bolna.ai/workflow-executions/exec%3A9a8b7c6d-5e4f-3a2b-1c0d-e9f8a7b6c5d4%3Alead_10442' \
      --header 'Authorization: Bearer <api_key>'
      ```

      ```json response theme={"system"}
      {
        "id": "exec:9a8b7c6d-5e4f-3a2b-1c0d-e9f8a7b6c5d4:lead_10442",
        "campaign_id": "9a8b7c6d-5e4f-3a2b-1c0d-e9f8a7b6c5d4",
        "status": "completed",
        "current_node_id": "n_end_reached",
        "termination_reason": "reached",
        "outcome": "success",
        "call_count": 1,
        "nodes": [
          { "node_id": "n_start", "node_type": "start", "attempt": 1, "status": "completed", "matched_case_index": 0, "to_node_id": "n_call" },
          { "node_id": "n_call", "node_type": "agent", "attempt": 1, "status": "completed", "matched_case_index": 0, "to_node_id": "n_end_reached",
            "outputs": { "call.status": "completed", "call.duration_s": 74 } },
          { "node_id": "n_end_reached", "node_type": "end", "attempt": 1, "status": "completed", "to_node_id": null }
        ],
        "events": [
          { "seq": 12, "node_id": "n_end_reached", "event_type": "terminal",
            "detail": { "termination_reason": "reached", "outcome": "success" } }
        ]
      }
      ```
    </CodeGroup>

    `termination_reason` is the `label` of the end node the contact reached, and `nodes` replays the exact path through your graph.
  </Step>
</Steps>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Nodes" icon="circle-nodes" href="/docs/guides/workflows/nodes">
    Add extraction, API and WhatsApp steps to the sequence
  </Card>

  <Card title="Conditions and variables" icon="code-branch" href="/docs/guides/workflows/conditions-and-variables">
    Branch on extracted values, API responses and arithmetic
  </Card>

  <Card title="Campaigns" icon="bullhorn" href="/docs/guides/workflows/campaigns">
    Run this workflow over a CSV of contacts
  </Card>

  <Card title="Workflow APIs" icon="code" href="/docs/api-reference/workflows/overview">
    The full API reference
  </Card>
</CardGroup>
