# Decision flows

A flow chains decisions: each answer of a question leads to the next question or to an outcome. You draw it in the [console](https://layahost.com/flows) and call it with one request. Only the questions on the path the text takes are asked, so a run costs one decision per question it passes.

Endpoints

```http
POST https://layahost.com/v1/flows/{flow}/run
GET  https://layahost.com/v1/flows
GET  https://layahost.com/v1/flows/{flow}
```

## Why a flow instead of one big question

Laya works best on narrow questions with a few well-described options: all options of a question share one small token budget, so long label lists leave little room for their descriptions (see [models](https://layahost.com/docs/models)). A flow asks “Is this spam?”, then “What does the customer want?”, then “How urgent is the bug?”, skips the questions that do not apply (no urgency check for spam), and lets you change the routing in the editor without redeploying your code.

## Building a flow

- **Start**: every run begins here with the text you send.
- **Question**: a [template](https://layahost.com/docs/templates) or your own choice, yes/no or score question. Each possible answer is an output you connect to the next step.
- **Outcome**: where a run ends. Its value (for example `billing-queue`) is returned as `outcome`, and your code branches on it.

An answer with no connection also ends the run; the answer itself becomes the outcome. Open [Flows](https://layahost.com/flows) to start from a template (support inbox triage, comment moderation, lead qualification) or from scratch, and use the Test tab to run texts through the canvas before you save.

### Yes/no thresholds

A yes/no question answers `yes` when p(yes) is at least 0.5. Set a different threshold on the step when a wrong yes costs more than a missed one, or the other way round.

### Unsure branches

Turn on the unsure branch of a question and set a minimum probability. When the probability of the chosen answer is below it, the run follows the `_unsure` output instead, for example to a human review queue. The step still reports the answer the model chose.

## Running a flow

Send the text; `{flow}` is the endpoint name shown in the editor (you can change it there). `lang` is optional, as in [/v1/decide](https://layahost.com/docs/decide). Runs use the last saved version.

POST /v1/flows/{flow}/run

```bash
curl https://layahost.com/v1/flows/support-inbox/run \
  -H "Authorization: Bearer $LAYAHOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Hi, I was charged twice this month, please refund one payment."}'
```

POST /v1/flows/{flow}/run

```python
import os
import requests

res = requests.post(
    "https://layahost.com/v1/flows/support-inbox/run",
    headers={"Authorization": f"Bearer {os.environ['LAYAHOST_API_KEY']}"},
    json={"text": ticket_body},
    timeout=30,
)
res.raise_for_status()
run = res.json()

if run["outcome"] == "page-on-call":
    page_on_call(ticket_id)
else:
    move_to_queue(ticket_id, run["outcome"])
```

POST /v1/flows/{flow}/run

```javascript
const res = await fetch("https://layahost.com/v1/flows/support-inbox/run", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LAYAHOST_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ text: ticketBody }),
});

const run = await res.json();
console.log(run.outcome, run.steps.map((s) => `${s.label}: ${s.answer}`));
```

Response · 200

```json
{
  "flow": "support-inbox",
  "version": 1,
  "outcome": "billing-queue",
  "ended_at": "billing",
  "steps": [
    {
      "step": "spam",
      "label": "Spam?",
      "answer": "legit",
      "probability": 0.8049,
      "probabilities": { "legit": 0.8049, "spam": 0.1951 },
      "confidence": 0.288,
      "followed": "legit",
      "latency_ms": 148.95
    },
    {
      "step": "intent",
      "label": "What do they want?",
      "answer": "refund",
      "probability": 0.9977,
      "probabilities": { "refund": 0.9977, "other": 0.0006, "bug_report": 0.0005, "cancellation": 0.0004, "question": 0.0003, "feature_request": 0.0003, "praise": 0.0003 },
      "confidence": 0.9895,
      "followed": "refund",
      "latency_ms": 97.66
    }
  ],
  "usage": { "decisions": 2, "cost_micros": 10, "cost_usd": 1.0e-5 },
  "latency_ms": 275,
  "request_id": "req_pebc6fszrqvqwhetxbxruttk"
}
```

| Field | Meaning |
| --- | --- |
| `outcome` | The value of the outcome the run reached, or the last answer when that answer has no connection. |
| `ended_at` | The id of the step where the run stopped. |
| `steps[].answer` | The answer of each question on the path: an option, a level name, or `yes`/`no`. |
| `steps[].probability` | The probability of that answer. `probabilities` has every option; `confidence` is the model's own entropy-based confidence, as in [/v1/decide](https://layahost.com/docs/decide). |
| `steps[].followed` | The output the run took: the answer, or `_unsure` when the unsure branch was used. |
| `usage` | Decisions asked on this run and their cost. Also in the `x-layahost-cost-micros` header. |

## Billing and logs

Each question on the path is billed and logged as one decision (see [pricing](https://layahost.com/docs/pricing)). The request ID of a run is shared by its steps, with `_1`, `_2`… appended in [Logs](https://layahost.com/logs), where each step shows the flow it belongs to. Test runs in the editor are billed like the playground.

## Errors

A run stops at the first step that fails and returns that step's error with the steps completed so far in `steps`. The questions already asked stay billed; the failed one is not.

| Status | When |
| --- | --- |
| `402` | The balance ran out before or during the run. |
| `404` | No flow with that endpoint name in the account of the API key. |
| `422` | No `text`, or the flow's start is not connected (`error_type: invalid_flow`). |
| `503` / `529` | The model is unavailable or overloaded; retry. See [errors](https://layahost.com/docs/errors). |

## Reading flows

`GET /v1/flows` lists your flows with their endpoint names, number of questions and possible outcomes. `GET /v1/flows/{flow}` returns the graph: its nodes, edges, and the answers each question can give.
