# Agent instructions for Veezee

Non-obvious rules only. Marketing lives at https://veezee.io/, not here.

## Minting a free key

- Every call needs an API key. Mint one free in a single call: `POST https://api.veezee.io/v1/keys/mint` with no body returns a key once, no signup, no card, no human. Skip this and a data call returns `KEY_REQUIRED`, a machine-readable error carrying this exact recipe.
- Every data tool (linkedin_resolve_url, linkedin_get_profile, linkedin_search_people, linkedin_get_company, linkedin_get_posts, reddit_search, reddit_get_subreddit, reddit_get_subreddit_posts, reddit_get_user, reddit_get_post, reddit_resolve_url, x_search, x_get_profile, x_get_tweets, x_get_tweet, x_resolve_url) then works under a shared budget of 200 credits per IP per day. Recent data only, no realtime, no `Idempotency-Key` needed. The key adds no allowance beyond that budget: it is the continuity handle so your setup keeps working after you pay.
- Every error names its fix in `message`; the budget-exhausted error carries `upgrade_url` to hand to your human. Paying is the only step that needs one.
- `freshness: "realtime"` needs a paid key; everything else, `get_usage` included, works on the free key.
- Hit the cap, or need more? Buy a key at https://veezee.io/upgrade (or `POST https://api.veezee.io/v1/checkout`). If you already have a free key, checkout credits that same key. Otherwise `api_key` is shown exactly once on the confirmation page. Store it yourself; Veezee cannot show it again.
- Over REST, send `Authorization: Bearer <api_key>` on every call. Over MCP, add the header to your client's connection config.
- Per-client install snippets (Claude Code, Cursor, VS Code, Codex CLI, and more): https://veezee.io/docs/clients
- MCP endpoints: https://mcp.veezee.io/linkedin (LinkedIn), https://mcp.veezee.io/reddit (Reddit), https://mcp.veezee.io/x (X); or https://mcp.veezee.io/all for every platform on one connection. An unkeyed request gets `401` with OAuth discovery (`WWW-Authenticate` plus `/.well-known/oauth-protected-resource`); a client implementing the MCP authorization spec follows it automatically and opens a browser sign-in, no key involved. Otherwise mint a free key over REST and reconnect with it in the `Authorization` header.
- Full REST contract (every path and parameter): https://veezee.io/openapi.json. `GET https://api.veezee.io/v1/` returns the same pointers if you land there cold. Unknown query parameters are rejected with `INVALID_INPUT` naming the parameter, never silently ignored.

## Worked recipes

- Candidate sourcing, prospect enrichment, company research, brand monitoring, and market research, with per-step credit costs: https://veezee.io/use-cases/candidate-sourcing, https://veezee.io/use-cases/prospect-enrichment, https://veezee.io/use-cases/company-research, https://veezee.io/use-cases/brand-monitoring, https://veezee.io/use-cases/market-research

## Idempotency

- Metered REST calls on a PAID key (GET included; our GETs charge credits) require an `Idempotency-Key` header. Free-key calls never need one.
- Reuse the same key only when retrying the same call after a failure or timeout. A new call needs a new key.
- Reusing a key with different arguments is an error, not a merge.
- MCP paid calls are retry-safe automatically: an identical tool call repeated in the same session within about 10 minutes replays the original receipt instead of charging again, so a timeout retry never double-charges. To force a paid re-run of the identical call, change any argument, reconnect, or send `params._meta["io.veezee/idempotency_key"]` with a fresh value per logical call (reuse the same value to retry it).

## Cost control

- `linkedin_search_people`: one call at `limit=30` costs less than three calls at `limit=10`. A cursor page is priced fresh by its own limit, so prefer one large limit over paging.
- `linkedin_get_profile`: the first 2 sections (about, experience, education, skills) are free with the base fetch; each section past 2 costs 2 credits, up to 4 total.
- `freshness: "realtime"` adds 2 credits, refunded automatically if the live fetch falls back to cached data. Default (`recent`) is free and usually a few hours old.
- Set `max_credits` on a call to cap its own spend; if the quote would exceed it, nothing is charged and the error carries `credits_required` (the exact quote) so you can decide with numbers, not prose. The cap survives into `next_url`, so paging stays capped.

## Results you cannot fetch

- `linkedin_search_people` results with `is_anonymous: true` are private profiles. `linkedin_get_profile` cannot dereference them: treat the match as confirmed and stop, don't retry.

## Identifiers

- `linkedin_get_profile` / `linkedin_get_company` / `linkedin_get_posts` accept a URL, a slug, or a URN directly. Only call `linkedin_resolve_url` first when the URL is dirty or ambiguous; resolving a clean identifier just spends 2 credits for nothing.
- `linkedin_search_people`'s `current_company` / `past_company` filters accept a plain company name; you don't need the id or URN first.

## Free-key and trial limits

- The minted free key IS a `vz_trial_` key; there is one key type on the free tier. While unfunded it spends the shared per-IP day budget (200 credits, recent data only, no realtime), and its `balance_remaining` stays 0: the budget lives on the IP, not the key, so other callers on your IP (CI runners, shared NAT, a second agent) draw from the same pool.
- Trial-plan caps that always apply: 1 concurrent call, 20 calls/minute, search capped at 10 results/call.
- `get_usage` is free and exempt from the rate limit: call it to check budget before a batch, not after INSUFFICIENT_CREDITS. On a trial key it returns `free_tier` (`credits_per_ip_day`, `used_today`, `remaining_today`, `resets`): that block, not `balance_remaining`, is the number you are spending. Free-tier draws do not appear in `recent_receipts`; each successful call reports its own cost inline in `usage`.

## When payment blocks you

- `get_usage` returns an account-bound `upgrade_url`; errors a payment can fix (INSUFFICIENT_CREDITS, BUDGET_EXHAUSTED, TRIAL_CAP_EXCEEDED; 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 that link to your human: purchases credit your account directly with no login (mint a fresh free key first if you don't have one), your key keeps working, and you continue after payment.
- The same errors carry `offer`, upgrade_url's machine-readable twin: pack prices in USD cents, `checkout_url` (the same link as `upgrade_url`), `resume` (how to continue after payment: INSUFFICIENT_CREDITS retries with the same Idempotency-Key, TRIAL_CAP_EXCEEDED and BUDGET_EXHAUSTED with a fresh key, the limit codes clear on their own), and `refund_policy`. INSUFFICIENT_CREDITS also sets `credits_required`: the credits the failed call needed.
- `get_usage` also returns `manage_url`: give it to your human to change or cancel a paid plan in the Stripe billing portal. The link stays valid at least 24 hours; call get_usage again for a fresh one.
- Retrying INSUFFICIENT_CREDITS or BUDGET_EXHAUSTED unchanged does nothing; the balance only moves when credits are added.

## Errors

- `message` is written as the next-turn instruction, not a log line. Read it and act on it instead of retrying blind.
- `is_retriable: true` means retry is safe; `false` means fix the input first.
