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

# Agent scripts: guided branching reply flows for digital conversations

> Author step-by-step scripts with branching decision paths for chat, email, and messaging conversations — from the Conversations → Agent Scripts settings page or the /api/v1/inbox/agent-scripts API.

# Agent scripts: guided branching reply flows for digital conversations

An agent script is a small, guided decision tree your agents walk during a live digital conversation. Each script is a graph of **steps** — a prompt the agent reads, a question to ask the customer, an internal note, or a final disposition — and each step can carry **branch options** that route the agent to the next step based on the customer's answer. The console follows whichever branch matches; the agent never has to remember the flow.

Scripts are the digital counterpart to the voice queue-script console: static macros give agents prewritten replies, and AI suggestions draft replies, but a script is the tool for a *process* — a billing dispute, a cancellation, an eligibility check, a KYC intake — where the right next question depends on the customer's answer.

**Base path:** `/api/v1/inbox`

**Dashboard surface:** **Settings → Conversations → Agent Scripts** (owners and admins; the write API is owner/admin-only, reads are open to any teammate so the agent console can fetch the graph).

***

## Where scripts fire

Agent scripts apply to the digital channels the inbox serves: web chat, email, WhatsApp, SMS, RCS, Viber, Instagram, Messenger, LINE, Telegram, Apple Messages, WeChat, KakaoTalk, and Zalo. A script can target a subset of those channels, or every channel when the channel list is left empty.

Scripts do not apply to voice. Voice queues have their own scripting surface (queue scripts on a voice queue's Scripts tab), which walks an agent through a decision tree during a live call instead of a messaging thread.

A script is guidance only: it never sends anything to the customer by itself. The agent reads the step, sends or paraphrases it, and picks the branch that matches the customer's answer.

***

## Building a script in the dashboard

Open **Settings → Conversations → Agent Scripts** and click **New script**. The editor works top to bottom:

1. **Name and description.** The name is what agents see in the console picker; the description reminds supervisors what the flow is for.
2. **Channels.** Toggle the digital channels the script applies to. Leave all untoggled to surface it on any channel.
3. **Active.** Only active scripts surface in the agent console — keep a draft inactive while you build it (see [Roll out a script](#roll-out-a-script-draft-to-live)).
4. **Steps.** Add steps in the order you first imagine them; each step has a kind, an optional title, and a body — what the agent reads, asks, or does:
   * `prompt` — a message the agent reads or sends verbatim (a greeting, a policy statement).
   * `question` — the decision node: ask the customer, then pick the branch that matches their answer.
   * `note` — internal-only guidance shown to the agent, never meant for the customer.
   * `disposition` — a terminal outcome the agent records when the flow ends (resolved, handed off, tagged).
5. **Branches.** On any step, add branch options: a label (the customer's likely answer) and the next step it leads to. Leave the next step on **End flow** to terminate cleanly.
6. **Start step.** Pick the first step explicitly, or leave it on **Auto** to start at the first step in the list.

Terminal actions at the end of a flow are ordinary steps: a `disposition` step instructs the agent to resolve the conversation, hand it off to a queue or teammate, or tag and park it. Give the step a clear title like "Resolve — refund issued" so the end of every branch is obvious in the console.

Save the script. Each save — create or edit — bumps the script's `version` counter, and every create, update, and delete lands in your audit log with the acting user.

***

## The schema the API accepts

The REST shape is the same one the dashboard posts: an object with `name`, and optionally `description`, `channels`, `enabled`, `start_step_id`, and `steps`. All writes require an owner or admin API key; reads only need an authenticated key.

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/inbox/agent-scripts" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Late-order troubleshooting",
    "description": "Walk the agent from symptom to resolution for a late order.",
    "channels": ["web_chat", "whatsapp"],
    "enabled": true,
    "start_step_id": "s-greeting",
    "steps": [
      {
        "step_id": "s-greeting",
        "kind": "prompt",
        "title": "Open with empathy",
        "body": "Apologize for the delay and confirm the order number before anything else.",
        "options": [
          { "label": "Order number confirmed", "next_step_id": "s-check-status" }
        ]
      }
    ]
  }'
```

Per field:

| Field           | Type                             | Notes                                                                                                                                                                                                                     |
| --------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`          | string, 1–120 chars              | Required on create.                                                                                                                                                                                                       |
| `description`   | string, ≤4,000 chars, nullable   | Optional.                                                                                                                                                                                                                 |
| `channels`      | array of channel names, optional | Valid values are the digital channel slugs: `email`, `web_chat`, `whatsapp`, `sms`, `rcs`, `viber`, `instagram`, `messenger`, `line`, `telegram`, `apple_messages`, `wechat`, `kakao`, `zalo`. Empty means every channel. |
| `enabled`       | boolean, optional                | Defaults to `true`. Only enabled scripts surface in the agent console.                                                                                                                                                    |
| `start_step_id` | string, nullable                 | The step the console opens on. Omitted means "first step in `steps`".                                                                                                                                                     |
| `steps`         | array, ≤500 steps                | Replaced **in full** on every PATCH — to change one step, resend the whole array.                                                                                                                                         |

Each step object:

| Field     | Type                                              | Notes                                                    |
| --------- | ------------------------------------------------- | -------------------------------------------------------- |
| `step_id` | string, 1–120 chars                               | Unique within the script; branch targets reference this. |
| `kind`    | `prompt` \| `question` \| `note` \| `disposition` | Optional; defaults to `prompt`.                          |
| `title`   | string, ≤200 chars, nullable                      | Short label shown in branch pickers.                     |
| `body`    | string, 1–20,000 chars                            | The text the agent reads, asks, or does.                 |
| `options` | array, ≤20 entries                                | Branch options for this step.                            |

Each option object:

| Field          | Type                | Notes                                                          |
| -------------- | ------------------- | -------------------------------------------------------------- |
| `label`        | string, 1–200 chars | The answer the agent picks (e.g. "Customer already verified"). |
| `next_step_id` | string or `null`    | The step to advance to; `null` or omitted ends the flow.       |

**Graph rules** — enforced on every create and update before anything is saved:

* Every `step_id` must be unique (a duplicate makes a branch reference ambiguous).
* Every option `next_step_id` (when set) must resolve to a step in the same script.
* `start_step_id` (when set) must resolve to a step in the script.

A violated rule returns `422 INVALID_SCRIPT_GRAPH` with the reason (for example, `"option on step \"s-check\" points at unknown step \"s-verify\"`). Nothing is persisted on a failing graph. Other endpoints in the surface:

| Method   | Path                       | Purpose                                      |
| -------- | -------------------------- | -------------------------------------------- |
| `GET`    | `/inbox/agent-scripts`     | List all scripts (any authenticated caller). |
| `GET`    | `/inbox/agent-scripts/:id` | One script with its full step graph.         |
| `PATCH`  | `/inbox/agent-scripts/:id` | Update; `steps` fully replaces the graph.    |
| `DELETE` | `/inbox/agent-scripts/:id` | Remove a script permanently.                 |

A PATCH with no fields at all returns `422`; hitting the per-tenant limit of 200 scripts returns `409 LIMIT_EXCEEDED`.

Only two roles can write: **owner** and **admin**. Agents can always read the scripts so the console can walk them.

***

## Roll out a script: draft to live

1. **Draft it inactive.** Create the script with `enabled: false` (or the **Active** switch off in the dashboard). An inactive script is saved, versioned, and invisible to the agent console.
2. **Dry-run it before enabling.** Walk the tree yourself: trace every path from the start step to a terminal `disposition` step and confirm no branch lands on a dead end, no step title reads ambiguously in the branch picker, and every flow ends with an explicit resolve/handoff/tag instruction instead of an abrupt stop. Have a supervisor run through a real conversation with the script left inactive to review the wording.
3. **Publish.** Set `enabled: true` — via PATCH or the **Active** switch — and the script immediately surfaces in the agent console for the channels it targets.
4. **Version on every change.** Every PATCH bumps `version`, so the console can tell an agent mid-flow that the script was refreshed under them and reload. Use the returned `version` on the PATCH 200 response to confirm the publish landed.

Deleting a script is permanent and removes it from the console right away; there is no archive step, so keep dormant scripts inactive instead of deleting them.

***

## What the agent sees in the console

During a live conversation on a channel the script targets, the agent opens the script from the console's script picker (only `enabled` scripts appear). The console renders one step at a time:

* **The current step.** Its kind badge (prompt / question / note / disposition), title, and body text — exactly what to read or do.
* **Detected answers.** On a `question` step, the branch options you authored render as pickable choices. The agent clicks the one matching the customer's answer and the console advances to that step.
* **The transcript dump.** The console keeps the path the agent walked, so a supervisor reviewing the thread sees which branches were taken, not just the messages that went out.

On a `disposition` step the agent records the terminal outcome — resolve, handoff, tag — which closes out the flow deliberately instead of leaving the conversation hanging.

***

## Two worked examples

### Example 1 — "My order is late" troubleshooting

Rendered tree:

```
[start] s-greet (prompt: apologize + confirm order number)
   └─ "Order confirmed" → s-check (question: where should it be?)
        ├─ "Still showing as shipped" → s-courier (question: which courier?)
        │    ├─ "Courier X" → s-resolve (disposition: file courier claim, resolve)
        │    └─ "Courier Y" → s-resolve …
        ├─ "Never shipped" → s-warehouse (note: check warehouse hold reasons)
        └─ "Wrong address" → s-readdress (prompt: confirm the corrected address)
             └─ "Address corrected" → s-reship (disposition: reship + tag 'address-fix')
```

As API JSON:

```json theme={null}
{
  "name": "Late order — troubleshooting",
  "channels": ["web_chat", "whatsapp"],
  "enabled": true,
  "start_step_id": "s-greet",
  "steps": [
    {
      "step_id": "s-greet",
      "kind": "prompt",
      "title": "Open with empathy",
      "body": "Apologize for the delay; confirm the order number before anything else.",
      "options": [
        { "label": "Order number confirmed", "next_step_id": "s-check" }
      ]
    },
    {
      "step_id": "s-check",
      "kind": "question",
      "title": "Where is it stuck?",
      "body": "Ask: does the tracking show it as still-shipped, never-shipped, or do they suspect a wrong address?",
      "options": [
        { "label": "Still showing as shipped", "next_step_id": "s-courier" },
        { "label": "Never shipped", "next_step_id": "s-warehouse" },
        { "label": "Wrong address", "next_step_id": "s-readdress" }
      ]
    },
    {
      "step_id": "s-courier",
      "kind": "question",
      "title": "Which courier?",
      "body": "Ask which courier the tracking lists.",
      "options": [
        { "label": "Courier claim filed", "next_step_id": "s-resolve" }
      ]
    },
    {
      "step_id": "s-warehouse",
      "kind": "note",
      "title": "Warehouse hold",
      "body": "Internal: check the hold reason — payment flag, stock, or manual review — before promising a date.",
      "options": [
        { "label": "Hold reason known", "next_step_id": "s-resolve" }
      ]
    },
    {
      "step_id": "s-readdress",
      "kind": "prompt",
      "title": "Correct the address",
      "body": "Read back the corrected address and confirm it with the customer before reshipping.",
      "options": [
        { "label": "Address corrected", "next_step_id": "s-reship" }
      ]
    },
    {
      "step_id": "s-resolve",
      "kind": "disposition",
      "title": "Resolve with a stated outcome",
      "body": "Resolve the conversation with the outcome filed: courier claim, warehouse follow-up, or refund."
    },
    {
      "step_id": "s-reship",
      "kind": "disposition",
      "title": "Reship and tag",
      "body": "Mark the conversation resolved with a reship; tag it 'address-fix' for reporting."
    }
  ]
}
```

### Example 2 — KYC intake

Rendered tree:

```
[start] s-id (question: identity document type)
   ├─ "Passport" → s-poa (question: proof of address?)
   ├─ "National ID" → s-poa
   └─ "No document" → s-fallback (disposition: hand off to compliance queue)
s-poa:
   ├─ "Utility bill" → s-done (disposition: resolve, tag 'kyc-complete')
   ├─ "Bank statement" → s-done
   └─ "None available" → s-handoff (disposition: hand off to compliance queue)
```

As API JSON:

```json theme={null}
{
  "name": "KYC intake",
  "description": "Collect identity document + proof of address, or hand off to compliance.",
  "channels": ["email"],
  "enabled": true,
  "start_step_id": "s-id",
  "steps": [
    {
      "step_id": "s-id",
      "kind": "question",
      "title": "Identity document",
      "body": "Ask which identity document the customer can provide: passport or national ID.",
      "options": [
        { "label": "Passport", "next_step_id": "s-poa" },
        { "label": "National ID", "next_step_id": "s-poa" },
        { "label": "No document available", "next_step_id": "s-fallback" }
      ]
    },
    {
      "step_id": "s-poa",
      "kind": "question",
      "title": "Proof of address",
      "body": "Ask for proof of address: utility bill or bank statement, dated within the last 3 months.",
      "options": [
        { "label": "Utility bill received", "next_step_id": "s-done" },
        { "label": "Bank statement received", "next_step_id": "s-done" },
        { "label": "None available", "next_step_id": "s-handoff" }
      ]
    },
    {
      "step_id": "s-done",
      "kind": "disposition",
      "title": "Complete",
      "body": "Resolve the conversation and tag it 'kyc-complete'."
    },
    {
      "step_id": "s-fallback",
      "kind": "disposition",
      "title": "Hand off — no ID",
      "body": "Hand off to the compliance queue; do not promise verification without a document."
    },
    {
      "step_id": "s-handoff",
      "kind": "disposition",
      "title": "Hand off — no address proof",
      "body": "Hand off to the compliance queue with the ID document already collected attached."
    }
  ]
}
```

Note the handoff branches end in a `disposition` step that names the target queue — the agent executes the handoff in the console, the script tells them where it goes.

***

## Troubleshooting

| Symptom                                                                   | Cause                                                                                                   | Fix                                                                                                                                          |
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `422 INVALID_SCRIPT_GRAPH` on save                                        | A branch option points at a step id that does not exist in the script (often a deleted step or a typo). | Re-point the option in the editor — every option's next step must resolve to a surviving step. The error names the dangling step and target. |
| `422 INVALID_SCRIPT_GRAPH` on save                                        | `start_step_id` names a step that is not in the graph.                                                  | Pick an existing start step, or leave it on Auto/first-step.                                                                                 |
| Console shows "End flow" earlier than intended                            | An option's `next_step_id` was left empty.                                                              | Set the branch target; only leave **End flow** on the final disposition branch.                                                              |
| Save rejected with `duplicate step_id: ...`                               | Two steps share a `step_id`, so a branch reference would be ambiguous.                                  | Rename one step id; ids must be unique across the script.                                                                                    |
| Handoff disposition names a queue that does not exist                     | The script step text refers to a queue/team that was renamed or deleted.                                | Update the disposition body to the current queue name; the script itself carries no queue reference, so the text is the source of truth.     |
| The agent picks a branch that does not match the customer's actual answer | Option labels are too similar ("Verify later" vs "Verified").                                           | Rewrite labels so each reads as a distinct answer; the console has no way to disambiguate overlapping choices.                               |
| PATCH silently drops a step                                               | `steps` full-replaces the graph — a PATCH that lists only the changed step removes the rest.            | Resend the whole `steps` array on every PATCH (`GET` the script first if needed).                                                            |
| Script invisible in the agent console                                     | `enabled` is false, or the conversation's channel is not in `channels`.                                 | Enable it, and confirm the channel list either contains that channel or is empty (all channels).                                             |

***

Every capability on this page is a tenant-owned control: scripts, steps, channels, and rollout live entirely inside your workspace, and the audit log records who created, changed, or deleted each script. Nothing in a script contacts the customer on its own — the agent stays in the loop on every step.
