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

# Commands: every action with its risk and its checks

> Each Omnibeds action is a command with a name, a risk level and an input schema. Learn the checks execute() runs, the risk levels, and how to add one.

In Omnibeds, every action on travel data (search tools, quotes, bookings, trips, travellers, agency settings) is a command defined with `defineCommand` in `lib/app/commands/`. The command carries its own checks, so the web app, a scheduled job and a future AI assistant all get exactly the same rules.

## What a command declares

| Field | What it is |
| - | - |
| `name` | A stable id such as `booking.cancel`. |
| `description` | One sentence on what it does. It doubles as the description in an assistant's tool list. |
| `risk` | How much running it can do. See the table below. |
| `input` | A Zod schema. Input that doesn't match is refused before anything runs. |
| `permission` | The permission needed. Defaults to `travel.use`. |
| `module` | The agency module it belongs to, when the agency can turn it off (today: `quotes`). |
| `confidential` | Returns true when this input touches a confidential supplier's data; presenter mode is then required. |
| `label` | How a failure is named in the log, for example `Cancel · WH-1084`. |
| `run` | The work, usually one call to a domain service. |

## Risk levels

| Risk | Meaning | Examples |
| - | - | - |
| `read` | Looks only. It may still call a supplier. | Check a rate, open a booking live |
| `change` | Changes our own records and can be changed back. | Save quote details, link a trip |
| `money` | Books, rebooks or changes what someone pays. | Book, convert a quote, switch to a cheaper rate |
| `irreversible` | Can't be undone. | Cancel a booking |

> [!NOTE]
>
> The risk level is there for the AI assistant: it may run `read` commands on its own, anything above needs the person's confirmation, and `money` and `irreversible` always come after a preview the person has seen.

## The checks execute() runs

Every call goes through `execute()` in `lib/app/command.ts`, in this order. The first check that fails stops the call.

**1. The module is on**

If the command belongs to a module the agency turned off, it answers `MODULE_OFF` (403).

**2. The user has the permission**

Otherwise it answers `FORBIDDEN` (403). Owners have every permission; admins have all but turning modules on and off; agents have `travel.use`.

**3. The input matches the schema**

Otherwise it answers `INVALID_REQUEST` (400), naming the first bad field and keeping every issue.

**4. Presenter mode, for confidential suppliers**

Inside the agency's scope, if `confidential(input)` is true and presenter mode isn't unlocked, it answers `PRESENTER_REQUIRED` (403).

**5. Run**

`run(input, ctx)` does the work inside the agency's scope. `ctx` holds the actor, whether presenter mode is on, and who is acting: `ui`, `job` or `agent`.

## Routes are one line

A route only connects a URL to a command:

```typescript app/api/cancel/route.ts theme={null}
import { commandRoute } from "@/lib/app/http";
import { cancel } from "@/lib/app/commands/bookings";

export const POST = commandRoute(cancel);
```

`commandRoute` reads the input from the body (or the query string for GET), merges in route params such as `:reference`, runs the command and returns JSON. Two options cover the rest: `from` chooses where the input comes from, and `respond` shapes the answer (a 201 for something created, or a PDF for a document). When one URL serves several steps, such as preview, book and cancel for a rebooking, `stepRoute("step", { ... })` picks the command by a body field.

## Add a command

**1. Define it**

Add it to the right file in `lib/app/commands/` with `defineCommand`, a name, a risk, an input schema and a `run` that calls the domain service.

**2. Give it a route**

Add a one-line route file that calls `commandRoute` (or add it to an existing `stepRoute`).

**3. Register it for screens**

Add its method and path to `ROUTES` in `lib/client/api.ts`. The boundaries test fails until every command has a route there.

**4. Call it from a screen**

Use `api("yourCommand", input, doing)`. The input and answer types come from the command itself.

## The catalogue

`lib/app/catalog.ts` lists every command (`COMMANDS`) and finds one by name (`commandNamed`). It's the list a scheduled job or an assistant uses to know what it can do and how risky each action is.


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