# Auth.md

REST and CLI calls need an API key, and your agent gets one free in a single call: no signup, no card, no human in the loop. MCP clients that support the MCP authorization spec skip keys entirely: connecting opens a Veezee sign-in in your browser instead.

## Minting a free key

`POST https://api.veezee.io/v1/keys/mint` with no body returns a key, shown once, plus ready-made setup snippets:

```
curl -s -X POST https://api.veezee.io/v1/keys/mint
```

Call without a key and the response is a machine-readable `KEY_REQUIRED` error carrying this exact recipe, so any agent self-heals in one round-trip. The CLI does this for you: `npx @veezee/sdk init` (alias `vz init`) mints and stores the key in `~/.veezee/config`, so it never needs to enter your agent's context again. From plain REST with no CLI: `curl -s -X POST https://api.veezee.io/v1/keys/mint | jq -r .key > ~/.veezee/key`, then send it as `Authorization: Bearer $(cat ~/.veezee/key)`, keeping it out of your context window too.

## Calling with a free key

Every metered tool (linkedin_resolve_url, linkedin_get_profile, linkedin_search_people, linkedin_get_company, linkedin_get_posts) answers under a free per-IP daily budget (200 credits/day, about 6,000 a month) shared by every free key from that IP, recent data only, no realtime, no `Idempotency-Key` needed. The key adds no allowance beyond that budget: it is only the continuity handle so your setup keeps working after you pay. Realtime fetches need a paid key; `get_usage` works on any key and on a trial key reports the free-tier day budget (`free_tier` block). Every free-key response already reports its own cost.

## Connecting over MCP with OAuth (no key at all)

An unauthenticated request to a Veezee MCP endpoint returns `401` with OAuth discovery: a `WWW-Authenticate` header plus `/.well-known/oauth-protected-resource`. A client that implements the MCP authorization spec follows that automatically and opens a Veezee sign-in in your browser: a one-time emailed code, no password, no card. Approving it connects the account; no key is copied into any config file.

This is the default path for Claude Code (`claude mcp add --transport http veezee https://mcp.veezee.io/linkedin`) and claude.ai (Settings > Connectors > Add custom connector), and works the same for ChatGPT developer-mode connectors and any other MCP-authorization-spec client. First consent on a fresh account fires the same one-time grant as claiming a key: 10,000 credits, once per email. The free tier (200 credits per IP per day) spends first; the grant and any purchased balance take over once that's used.

Connected apps are managed at Dashboard > Connected apps; revoking one disconnects it immediately.

Client doesn't support the authorization spec, or you're running headless/CI? Mint a free key over REST (`POST https://api.veezee.io/v1/keys/mint`, empty body, no signup) and reconnect with the key in your client's `Authorization` header, same as any REST call. Per-client details: [/docs/clients](https://veezee.io/docs/clients).

## Getting a paid key

Buy a key at [https://veezee.io/upgrade](https://veezee.io/upgrade) (or `POST https://api.veezee.io/v1/checkout` with `{ "pack": "flex" | "production" }`). If you already have a free key, checkout credits that same key, nothing to reconfigure. Otherwise the confirmation page shows a new `api_key` exactly once; store it yourself, Veezee cannot show it again.

- Send it as `Authorization: Bearer <api_key>` on every REST call, plus an `Idempotency-Key` header on metered routes.
- Over MCP, add the header to your client's connection config (syntax varies by client; see [/docs/clients](https://veezee.io/docs/clients)).

## The model: mint, claim, sign in, connect

There is no sign-up. Four verbs cover everything:

- **Mint** (agent, anonymous): creates a key and its account in one call. The key is the spend authority.
- **Claim** (human, once): attach an email to an account. That starts governance (recovery, dashboard, rotation, alerts) and stakes a claim on the payload below, but nothing is granted yet. Checkout claims as a side effect; unclaimed keys keep working and never expire, so claiming is a value add, not a deadline.
- **Sign in** (human, repeatable): prove the email at [https://veezee.io/login](https://veezee.io/login) (a one-time emailed code; no password exists) to get a browser session or a `vzm_` management token. Signing in with a new email IS registration: it creates a real account on the trial plan, zero balance, no key yet, email already confirmed. That account's first key comes from the dashboard's create-key button, or from `vz init` while signed in. The claim grant fires at whichever comes first: confirming a claimed key by signing in, or creating a trial account's first key while signed in. Either way it's 10,000 one-time credits, spent after the free daily budget, once per email and once per key.
- **Connect** (MCP, browser sign-in, no key): point an MCP client that supports the MCP authorization spec at a Veezee MCP endpoint; it follows the 401 discovery response and opens the same sign-in as above. Approving it is both claim and first sign-in at once: same trial account, same one-time grant.

Mint, claim, sign in, and connect converge on the same account shape no matter the order: one account, one balance, as many keys as you make. The key spends; the identity governs. A spend key can never rotate keys, change billing, or lock out the owner.

## Signing in from a CLI or MCP client

`vz login` opens the browser to /login, and the callback stores a fresh `vzm_` management token in `~/.veezee/config` (90-day TTL, revocable in the dashboard). If the CLI holds a minted trial key, sign-in offers to attach that key's account to your email: one explicit confirm screen, skippable. Headless boxes use `vz login --device`: the CLI prints a short code, you approve it at [https://veezee.io/activate](https://veezee.io/activate) from any browser, and the CLI picks up the token.

A `vzm_` token in an Authorization header unlocks the management surface anywhere:

- REST: `GET /v1/keys`, `POST /v1/keys` (`{ label }`), `POST /v1/keys/rotate` and `POST /v1/keys/revoke` (`{ key_id }`), `POST /v1/alert` (`{ threshold }`), `POST /v1/portal`.
- MCP: the same mounts grow list_keys, create_key, rotate_key, revoke_key, set_balance_alert, and billing_portal when the connection's Bearer is a `vzm_` token. Spend-key connections keep the data-only catalog.
- CLI: `vz keys list|create|rotate|revoke` and `vz alert`.

Management calls with only a spend key return `AUTH_REQUIRED` naming the remedy. When one email governs several accounts, pass `account_id` (the error lists the valid ids); a `vzm_` token spends directly only when exactly one account matches.

## Claiming (recovery)

Sign-in claiming (above) is the main path for minted keys. `get_usage` returns `claim_url` on legacy trial-plan accounts only; opening it attaches an email the same way. Accounts bought at [/upgrade](https://veezee.io/upgrade) are claimed by the checkout email automatically.

## Recovering a lost key

`POST https://api.veezee.io/v1/recover` with the claimed email sends a one-time re-key link to that address. Following it (or `POST https://api.veezee.io/v1/rekey` with the link's token) issues a new key and revokes the old one; balance, plan, and receipts are unchanged. Signed-in humans can also rotate a single key from the dashboard. An account that was never claimed cannot be recovered: write to hello@veezee.io.

## Upgrading

`get_usage` returns an account-bound `upgrade_url`; errors a payment can fix (INSUFFICIENT_CREDITS, TRIAL_CAP_EXCEEDED, and BUDGET_EXHAUSTED; on trial accounts also RATE_LIMITED and CONCURRENCY_LIMIT) carry it too. On free-key calls, the same errors carry the public `https://veezee.io/upgrade` link instead. Give the link to your human: purchases credit the account directly with no login (mint a fresh free key first if you don't have one), and the key keeps working after payment.

## What this is not

REST and the CLI still have no OAuth: every call needs a `vz_` spend key or a `vzm_` management token, minted or signed in as described above, no client id or secret to register by hand, no scopes, no token exchange, no expiring spend keys. MCP is the one exception: a client that implements the MCP authorization spec gets a real OAuth flow (401 discovery, browser consent), so a hosted agent never sees or handles a key. Two bearer families still cover every REST and CLI call: `vz_` spend keys (per account, as many named ones as you like) and `vzm_` management tokens (per identity, 90-day TTL).
