> ## Documentation Index
> Fetch the complete documentation index at: https://vendo-mintlify-24213046.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# How the door works

> Who is calling, how a call reaches your host tools, and what comes back.

One bearer arrives, one user is named, and every call after that answers to the
same guard your own UI answers to.

<svg viewBox="0 0 760 118" role="img" aria-label="A request path: your agent, the door, your guard, your host API" style={{ width: "100%", height: "auto", margin: "1.5rem 0" }}>
  <g fill="none" stroke="currentColor" strokeOpacity="0.2">
    <rect x="1" y="1" width="164" height="52" rx="11" />

    <rect x="199" y="1" width="164" height="52" rx="11" />

    <rect x="397" y="1" width="164" height="52" rx="11" />

    <rect x="595" y="1" width="164" height="52" rx="11" />
  </g>

  <g fill="none" stroke="#6c3bff" strokeWidth="1.5" strokeLinecap="round">
    <path d="M169 27h20" />

    <path d="M367 27h20" />

    <path d="M565 27h20" />
  </g>

  <g fill="#6c3bff">
    <path d="M189 23l7 4-7 4z" />

    <path d="M387 23l7 4-7 4z" />

    <path d="M585 23l7 4-7 4z" />
  </g>

  <g fill="currentColor" fontSize="13.5" fontWeight="600" textAnchor="middle">
    <text x="83" y="32">Your agent</text>
    <text x="281" y="32">The door</text>
    <text x="479" y="32">Your guard</text>
    <text x="677" y="32">Your host API</text>
  </g>

  <g fill="currentColor" fillOpacity="0.55" fontSize="11.5" textAnchor="middle">
    <text x="83" y="76">bearer for user\_1904</text>
    <text x="281" y="76">principal + consent</text>
    <text x="479" y="76">allow · ask · block</text>
    <text x="677" y="76">called as that user</text>
  </g>
</svg>

## Who is calling

An agent that turns up with nothing gets a `401`, and the challenge on it points
at your broker. Vendo Cloud runs that broker for you: the person signs in there
with your app's own login, and the agent leaves with a short-lived bearer. The
door itself serves no `/authorize` or `/token` on that posture, which is why the
exchange in [Service keys & broker](/outside-agents/service-keys-and-broker)
posts to the broker instead. A door init wired for local dev is its own
authorization server and does advertise both, at `/api/vendo/mcp/authorize` and
`/api/vendo/mcp/token`.

The bearer names one of your own user ids, in your own spelling. The door
verifies its signature, then asks your auth code to turn that id into a live
user — on every request, not once per session. Return `null` there and the token
is dead on the next call, sessions included. That is the kill switch, and
[Wire auth](/howto/auth) is where it lives.

## What the agent sees

`tools/list` answers with your enabled tools whose `audience` is `end-user` or
unset. Operator and internal tools stay off it. Names, descriptions, and input
schemas are served verbatim from your registry, and a tool's `title` is what a
client shows a person instead of the wire name.

Vendo's own `vendo_*` tools ride along: `vendo_make` for a generated screen, the
saved-apps viewer, and the caller's own files. The file tools are scoped to the
bearer's subject and take no subject argument, so a token for `user_1904` reaches
that person's files and no one else's. See [User data](/customize/user-data) for
what saves and what reads back.

Each listing carries MCP `annotations` derived from the tool's risk label, so a
client can warn before a write without re-reading prose.

| Risk          | `readOnlyHint` | `destructiveHint` |
| ------------- | -------------- | ----------------- |
| `read`        | `true`         | `false`           |
| `write`       | `false`        | `false`           |
| `destructive` | `false`        | `true`            |
| `ungraded`    | *omitted*      | *omitted*         |

An `ungraded` tool asserts neither hint, which leaves the client on MCP's own
cautious reading of a tool it knows nothing about.

## Curate the menu

For a shorter, deliberate menu, name the tools you want in `.vendo/overrides.json`:

```json .vendo/overrides.json theme={null}
{
  "format": "vendo/overrides@3",
  "surfaces": {
    "mcp": { "tools": ["host_listAccounts", "host_transferMoney"] }
  }
}
```

The door then lists exactly those, and any other name comes back not-found. The
file resolves once at boot, so edit it and restart. A menu is curation, not a
permission boundary: your policy and approvals still decide what may run, and the
`vendo_*` tools bypass the filter.

## How a call reaches your host tools

The agent has no session in your product, so the inbound bearer is never
forwarded to your host routes. Door calls reach your API through the `actAs`
seam: Vendo hands `actAs` the caller and the consent recorded for that client,
and uses the auth material you return. Without `actAs` configured, the tool
returns a `not-implemented` error.

The signed-in user is the authority. Scopes on the token buy no exemption, and
the door adds none.

## What comes back

Five outcomes, all of them in-band so a retry never re-executes a committed
write.

| Outcome            | What the agent gets                                  |
| ------------------ | ---------------------------------------------------- |
| `ok`               | the tool's output, as text plus `structuredContent`  |
| `error`            | `code: message` from your handler                    |
| `blocked`          | your guard's reason, verbatim                        |
| `pending-approval` | the approval id and where to resolve it              |
| `connect-required` | which account to connect in your product, then retry |

`vendo_make` answers with an id, a title, a status, and one line to say out loud.
The screen itself lands in your product, never in the agent's transcript. That
line is `say`, written for the person rather than the model, so an agent relays
it close to verbatim and reports nothing else.

| `status`   | What the agent has                                                    |
| ---------- | --------------------------------------------------------------------- |
| `ready`    | the screen is on the person's page                                    |
| `building` | the build was accepted and has not finished                           |
| `partial`  | the screen is up, and the server-side work its plan called for is not |
| `failed`   | nothing painted. One narrower retry on the same `app`, then stop      |

If the chat calling `vendo_make` is your own frontend, `<VendoAppEmbed>` puts the
app inline in it, off the receipt's own id — see
[Embeds in your chat](/existing-agent/embeds).

## Where a screen lands

A new screen goes to the person's own list of views. Pass `slot` on `vendo_make`
and it takes a named place on your page instead — slot ids belong to your
product, so an agent passes one it was told rather than one it invented.
[Generate screens](/howto/screens) is where slots come from.

`slot` is for something new. `vendo_apps_pin` and `vendo_apps_unpin` move an app
the person already has, and since a slot holds one app, a pin replaces and names
what it evicted.

## Approvals wait for the person

On the `cautious` preset, reads answer straight away and writes park. The agent
gets an error naming the approval and where it is waiting.

Approving does not resume the call. The agent has to call again, and it has to be
on the same MCP session — a client that reconnects between attempts parks a
second time instead of running. [Approve actions](/howto/approvals) is where the
grading lives.
