# Keys and environments

> Owner, agent and app keys; production and development; who may do what.

**Agents build and run; they never hold a production secret.** Production values go from Vibiz straight into the app's host. Only a person, the owner, can read them, and only to paste them into a host outside Vibiz. Agents and laptops work against a separate development environment that holds none of production's data.

## The keys

| Key | Looks like | Held by | Can |
| --- | --- | --- | --- |
| **Agent key** | `vz_sk_…`, `access: agent` | the CLI, the MCP server, CI | create projects, deploy, promote, run actions, send email, use AI, read usage, get the development env, everything in development. Never the production env, keys, removing a domain or live money |
| **Owner key** | `vz_sk_…`, `access: owner` | a person, rarely | all of the above, plus the production env, keys, removing domains, connecting hosts, live refunds and withdrawals |
| **Console session** | a cookie | a person signed in at app.vibiz.app | the access of their account: whoever opened the account is its owner |
| **App key** | `vz_app_…` (`VIBIZ_APP_KEY`) | the running app | its own project and environment only: env, AI, actions, email, events, payments for its users |
| **Project AI key** | `vz_ai_…` | the app (`VIBIZ_AI_API_KEY`) | the AI gateway, billed to the project |

New keys are agent keys unless an owner asks otherwise:

```bash
vibiz keys create growth-agent          # an agent key, shown once
vibiz keys create ci --owner            # an owner key (owner keys only)
vibiz keys revoke key_…
```

```bash
curl https://app.vibiz.app/v1/api-keys -H "Authorization: Bearer $OWNER_KEY" \
  -H 'content-type: application/json' -d '{"name":"growth-agent","access":"agent"}'
# → 201 { "id": "key_…", "secret": "vz_sk_…", "access": "agent", … }   the secret is returned once
```

Name a key per person or agent: usage breaks down by key (`vibiz usage --by api_key`).

## Keys and environments

- **Agent key** (`vz_sk_…`, access `agent`): what the MCP server and the CLI usually hold. Builds and runs: projects, deploys, actions, AI, usage, the development env, everything in development. Never a production secret, never live money: production refunds, cancels, withdrawals, payment settings, keys, removing domains and connecting hosts answer `403 owner_only`. The fix for `owner_only` is a person: the owner does it in the console (or with an owner key), not a retry.
- **Owner key** (access `owner`): all of that plus production secrets, keys, removing domains and connecting hosts. Rarely given to agents.
- **App key** (`vz_app_…`, `VIBIZ_APP_KEY`): the running app's one secret, per environment.

**Development env** (`vibiz_dev_env`, `vibiz dev`): its own database branch with the app's tables and no production rows, its own sign-in (trusts `http://localhost:3000`), its own AI key, and `VIBIZ_WEBHOOK_SECRET` once a development webhook is set. `write_to` puts the values in an env file without showing them.

**The project id**: `./vibiz.json` `{ "project_id": "prj_…" }` in a linked app folder, or `vibiz_project_status` for the list. Read it instead of asking the user.

## Environments

Every project has two environments. `env` (or `--dev` in the CLI) picks one; it defaults to **production for owner keys and development for agent keys**.

| | production | development |
| --- | --- | --- |
| Database | the real one | a branch with the same tables and **no rows** |
| Sign-in | real users; trusts the live domains | its own users; trusts `http://localhost:3000` |
| AI | the project's key | a second key, billed to the project |
| Payments | live money, 7-day hold | test mode, test cards, no hold, `simulate` |
| Company | a real filing, real fees | nothing filed, nothing charged, `simulate` |
| Who | owner for secrets and money | anyone on the account, agents included |

Hosting, analytics, replay and email have no development copy (a local run shouldn't count as visits or send mail from the live address).

```bash
vibiz dev                     # create it once and write .env.development.local (names printed, values not)
vibiz dev -- npm run dev      # pass the values to a command, no file
vibiz dev --reset             # throw the copy away; the next `vibiz dev` branches again
```

Over HTTP: `POST /v1/projects/:id/dev` (create, `202`), `GET /v1/projects/:id/dev` (`status: none | provisioning | ready | failed`), `GET /v1/projects/:id/dev/env` (the values, `409` until ready), `DELETE /v1/projects/:id/dev`.

## Getting the production env to the app

1. **Vibiz hosting**: automatic. Every key is pushed to the host and kept in sync.
2. **A connected host**: the owner connects Vercel, Netlify, Render, Railway, Fly.io, Heroku, Cloudflare Workers or Pages, or GitHub Actions once (`vibiz hosts add <host> <app>`); Vibiz writes the env there whenever it changes, rotations included. Vercel is verified live; the others are in beta.
3. **One key, any host**: put only `VIBIZ_APP_KEY` in the host and load the rest at boot.

```ts
import { vibiz } from '@vibiz/app';
await vibiz.loadEnv(); // the app's env into process.env
```

```bash
eval "$(curl -fsS -H "Authorization: Bearer $VIBIZ_APP_KEY" \
  https://app.vibiz.app/v1/app/env?format=shell)" && exec node server.js
```

`GET /v1/app/env?format=json|dotenv|shell|docker`. Before any of these, the app's domain is added (`vibiz domains add app.example.com --elsewhere`; an agent key may, and sign-in then trusts it once its TXT record proves it or the owner confirms it) so sign-in and analytics accept it. The project needs no Vibiz hosting at all: any one service works alone ([Use one service](https://app.vibiz.app/docs/use-one)).

## The app key

- One per project **and** environment, put in that environment's env with `VIBIZ_API_URL`. A development key reads only the development env, and its events are not recorded.
- It works only for its own project: account routes answer `401`.
- Rotate with `vibiz app-key rotate` (production needs an owner). The new key is pushed to Vibiz hosting and every connected host; the old one keeps working for a day by default (`--grace <seconds>`, `0` ends it now). The new secret is never printed.

## What agents can and can't do

| | Agent key | Owner key |
| --- | --- | --- |
| Projects, deploys, actions, AI, email, usage | yes | yes |
| Development env values | yes | yes |
| Production env values (`GET /projects/:id/env`) | names only (`?names=1`) | yes |
| Add the app's domains | yes (sign-in trusts one once its TXT record proves it) | yes |
| Create or revoke keys, remove domains, connect hosts | no | yes |
| Rotate the production app key | no | yes |

An agent hitting an owner route gets `403 forbidden` with `details.reason: "owner_only"` and `details.next_step`, the console step to tell the person. Retrying with the same key never works.

## Errors

| Code | Status | When | Next step |
| --- | --- | --- | --- |
| `unauthorized` | 401 | missing, unknown or revoked key; an app key on an account route | check `VIBIZ_API_KEY` / `VIBIZ_APP_KEY` |
| `forbidden` + `owner_only` | 403 | an agent key on an owner route | pass `details.next_step` to the owner |
| `dependency_not_ready` | 409 | the development copy isn't ready yet | poll `GET /projects/:id/dev` |
| `not_found` | 404 | the project isn't on this account | check `./vibiz.json` |
