Skip to main content
A service key is the one long-lived credential in this path. It never touches your agent, and it never leaves your backend. vendo.tokenFor() spends it for you — this page is what is behind that call.

Where the key lives

On a local door (no key). The key is any opaque string you generate. It lives in your own environment as VENDO_SERVICE_KEY, the composition lists it, and vendo init --service-key writes both for you — this is what it generates:
lib/vendo.ts
The empty-string fallback is deliberate: a door with no key still opens for browser-based agents, it just cannot mint for your backend. More than one key can be live at once, which is what makes rotation a deploy rather than an outage. On Vendo Cloud. The key is provisioned with your tenant on first use — init does not ask, and there is nothing to copy. The console lists it, and rotates and revokes it. Either way your agent never sees the key. It only ever gets the short-lived token minted from it, and tokenFor is the only place your own code touches either.

Who the audit says acted

The exchange binds two identities to every call the token makes: the person the token names, and the key that asked. The key rides as a client id of the form svc:<hash8>, the first eight hex of the presented key’s digest. The audit row names which key acted without the key value going anywhere near it. Your policy sees the same person your own UI sees. A write still parks for approval, and the approval names the tool and the user, not the agent.

Rotating a key

1

Create the replacement

A new key alongside the one you are retiring — a second entry in serviceAuth.keys on a local door, or a new key in the console on Cloud.
2

Ship it

Update VENDO_SERVICE_KEY where your backend runs and deploy.
3

Retire the old one

Drop it from the list, or delete it in the console. Tokens it already minted keep working until they expire, which is at most ten minutes.

When the mint refuses

The first row is deliberately one answer for two causes. Anything narrower would tell whoever is guessing which half of the credential they got right. The 401 is the one that looks like a key problem and is not. The exchange never checks the user id, so a token for a stranger mints fine and dies at principal() on the first real request.

What the broker owns

A broker is an authorization server in front of your door. On Cloud, your VENDO_API_KEY is the whole configuration — the runtime reads the broker URL and the federation secret from Cloud and provisions the tenant on first use. To point at a broker you run yourself, set both values where you deploy. An explicit override always wins over the Cloud default.
.env
VENDO_MCP_BROKER_URL decides two things at once. Its origin becomes the issuer your door trusts, and the URL itself becomes the audience every token must carry. The door then stops serving /authorize, /token and /register, and verifies each inbound bearer as a JWT the broker signed. Keys come from the issuer’s own metadata, so ordinary rotation at the broker needs nothing from you. VENDO_MCP_FEDERATION_SECRET answers the broker’s login handshake. The broker owns the sign-in surface and your app owns the users, and that shared secret is how the two agree on an answer. VENDO_BASE_URL has to name an HTTPS origin either way — Vendo Cloud refuses to register a forwarding address that is not https, and init refuses that pair before it writes. Who has to reach that origin depends on the path. Signing a person in is a browser redirect: the broker sends the user’s own browser to your door’s /federate, and the door reads their session cookie there, so the door has to be reachable by whoever signs in — public for a public agent’s users. The backend-agent path reaches nothing inward: tokenFor calls the broker outbound from your own backend, so a private HTTPS origin your backend and agent can already reach is enough for it. Everything else about the door is unchanged by the posture, the client URL included — see the quickstart for both postures end to end.