# US-039 — Caller Credit granted once per account; chat requires account keys Status: planned Priority: Critical (blocks friends-test inference — every request 402s) Stage: Designed ## Context `DEFAULT_STARTING_CREDIT = 0.0` and nothing can raise it: no CLI flag, no admin credit endpoint. The only funding path is the on-chain USDT deposit watcher — so on a fresh public tracker every inference request returns `402 insufficient balance`, including keys created through the working account flow (`/v1/auth/register` → `/v1/account/keys`, also in the dashboard). Separately, the billing gate accepts **any** bearer string (only revoked keys are rejected), so wiring starter credit into `BillingLedger.ensure_client` would let anyone mint unlimited free credit by inventing keys. Decision (grilled 2026-07-06): **Caller Credit is granted once per account**, when the account creates its first API key; chat requests on an accounts-enabled tracker must present a real active `sk-mesh-` key. Rejected: credit-any-bearer (free-inference farming against volunteers' GPUs on the open internet) and manual-admin-credit-only (operator toil, no self-serve testing). ## Design 1. **`BillingLedger.grant_caller_credit(api_key, account_id, amount) -> bool`** Appends a credit event with the deterministic id `caller-credit-{account_id}` and note `caller-credit`. Already-seen id → no-op returning False. The deterministic id makes the once-per-account rule idempotent locally *and* across hive gossip (event dedupe is by id), so a second key, a retried request, or a replicated event can never double-grant. 2. **`AccountStore.is_active_key(api_key) -> bool`.** The chat billing gate (`_handle_proxy_chat`) rejects keys that are not active account keys with `401 invalid_api_key` — but only when an accounts store is configured, so embedded/test trackers without accounts keep today's any-key behavior. 3. **`--starting-credit N`** (USDT) on `meshnet-tracker start`. Default is the single constant `DEFAULT_CALLER_CREDIT_USDT = 1.0` in `server.py` (devnet-friendly alpha, revised 2026-07-06; set 0 on mainnet). Threaded through `TrackerServer` → `_TrackerHTTPServer`; registration and `_handle_account_key_create` call `grant_caller_credit` when billing is on and the amount is positive. The ledger's own `_starting_credit` stays 0 — implicit per-key grants in `ensure_client`/`charge_request` remain dead code paths. 4. **Stack variable `STARTING_CREDIT`** in `deploy/portainer/meshnet-tracker-nobuild-stack.yml` (default 1). 5. **Docs.** QUICKSTART gains "Get an API key" (register/login/create key via dashboard or curl, what 402 means); DEPLOY_PORTAINER's stale "any key is accepted and starts with the configured starter credit" paragraph replaced. ## Acceptance criteria - Fresh account → first key → key has `--starting-credit` balance; chat succeeds - Second key on the same account → no additional credit - Revoke-and-recreate keys → still no additional credit (deterministic event id) - Random bearer string on an accounts-enabled tracker → 401, never 402/free work - Tracker without accounts store: gate behavior unchanged - `--starting-credit 0` disables the grant entirely (mainnet posture) - `python -m pytest` passes from repo root