# Implementation Plan: API Access, Member Dashboard & Admin Dashboard **Status:** Planning (not yet implemented) **Last updated:** September 13, 2026 ## Goal Give members direct API access to the cooperative's models (alongside chat), a dashboard to view their usage and manage their own API keys, and an admin dashboard for co-op-wide monitoring. ## Guiding principles 1. **One unified budget.** Chat and API draw from the same $15/month credit pool. No separate accounting. 2. **The member surface holds no master key.** A member-facing app must never touch the LiteLLM master key, the Cloudron token, or the Open Collective token. Key creation happens through a tightly-scoped broker. 3. **Leverage LiteLLM's native primitives.** Teams for budgets/spend, spend-log endpoints for usage. Don't reinvent what the gateway already does. 4. **No commercial dependencies for now.** Stay on the open-source LiteLLM build. ## Verified facts (from research) - **LiteLLM Teams** (open-source) provide: per-team budget, per-team spend tracking, and key attribution to both user and team. This is the abstraction layer for "unified account + multiple keys." - **Clarification on "teams":** we use LiteLLM's open-source **team budget** feature (a budget container, referenced as `team_id`). We do **not** use the Premium **`team_admin` role / team-member permissions** — those are the paid features that would let members self-manage keys. We act as the "team admin" ourselves via the broker/master key instead. - **Member-facing vocabulary:** members see "your account" / "your budget" / "your API keys" — never "team" (that's an internal implementation term). - **Spend endpoints** are live on our gateway: `/global/spend`, `/spend/logs`, `/user/info`, `/team/list` (all return 200). - **LiteLLM is v1.74.0, with Postgres** — so budgets and teams are fully supported. - **Key self-serve requires Premium.** The `team_admin` role and "team member can create keys" permission are both **✨ Premium Features**. On the OSS build, only the master key can mint keys. Hence the broker. - **Team members can still *view* key info on OSS** — read-only usage/key visibility is viable without Premium. ## Target architecture ``` Member (browser) ──SSO──▶ Member Dashboard (new Cloudron app, "members" group) │ read-only: my spend, my keys, my usage │ writes: "create/revoke key" → Broker ▼ Broker (portal or admin app; holds master key) │ mints/revokes a key scoped to the member's team ▼ LiteLLM Gateway (Teams + budgets + spend logs) ``` - **Chat key:** portal-held, per member, under their team, hidden from the member. - **API keys:** member-managed, under their team, visible in the member dashboard. - Both draw from the same team's $15/30d budget. ## Phases ### Phase 1 — Teams migration (foundation) - Create a **Team per member** (`team_alias: member:`) with `$15/30d` budget and the three models. - Two keys per team: - **Chat key** — created/handled by the portal injector (existing `member:` key becomes this). - **API key(s)** — created via the broker on member request. - Update `provision_member()` to create the team + chat key (instead of a bare key). - Migrate existing members (Nathan, Liz, Dan, Ed, Lee). - Verify: chat still works end-to-end; spend now attributed per-team. ### Phase 2 — Broker (key management) - A token-protected endpoint that, given a member's email + a requested key name: - Verifies the caller (SSO identity or portal JWT). - Mints a key scoped to that member's team (via master key). - Revokes a key on request. - Lives in the **portal** (which already holds the master key) or the **admin app**. - This is the only place the master key is used for key creation. ### Phase 3 — Member dashboard (new Cloudron app) - Installed in the Cloudron **"members"** group → SSO-gated (same pattern as OpenWebUI/Loomio). No custom auth to build; trust the forwarded user identity. - **Read-only views** (via team-scoped endpoints, no master key): - My current spend vs. $15. - My API keys (list, with the chat key hidden). - Recent usage (from `/spend/logs` scoped to my key/team). - **Write actions** (call the broker): - Create API key. - Revoke API key. - Holds **no** master key, Cloudron token, or OC token. ### Phase 4 — Admin dashboard - Co-op-wide view (admin-gated, holds master key): - All members + status (active/inactive/founder). - Aggregate spend + per-model breakdown. - Budget adjustment, key/team management. - Either a section of the portal or a distinct app. Decision deferred until Phase 3 is built (the admin view is lower urgency — LiteLLM's built-in UI already covers some of it). ### Phase 5 — Documentation - Document the API endpoint (`https://gateway.inference.coop/v1`), OpenAI-compatible format, and "shares your $15 budget with chat." - Document the member dashboard (how to get/rotate keys). - Add an open question to `open-questions.md`: how to split/communicate the chat-vs-API budget if members want separate limits (future governance decision). ## Security model (the load-bearing part) | Component | Holds master key? | Notes | |-----------|-------------------|-------| | Portal (control plane) | Yes | Existing: provisioning, key injection, webhook, admin | | Broker | Yes | Scoped: only mints/revokes team keys | | Member dashboard | **No** | SSO-gated, team-scoped, read-only + broker calls | | Admin dashboard | Yes | Admin-only | The invariant: **a member's session can only ever (a) read their own team's data, or (b) ask the broker to act on their own team.** No member-path code can enumerate or touch another member's keys. ## Open decisions (to resolve before/at build time) 1. **Broker location** — portal (simplest, already has master key) vs. admin app. Leaning portal. 2. **Member dashboard framework** — a small custom app (FastAPI, reusing the portal's patterns) vs. an app-store tool. Leaning custom: the app is a thin read/write wrapper, not a BI dashboard, and existing tools (Metabase/Superset/Baserow) don't fit "create my key." 3. **Key naming/metadata** — what a member sees when they create a key (name, purpose tag) for their own cost tracking. 4. **Premium later?** If the co-op grows an enterprise offering, LiteLLM Premium unlocks true key self-serve (drop the broker). Deferred. ## Risks - **Broker is a new attack surface** — must be narrowly scoped and rate-limited. - **Migration touches existing members** — needs careful idempotency (reuse existing keys by alias). - **Spend-log scoping** — must verify `/spend/logs` can be filtered per-key/per-team so the member dashboard can't see others' usage. ## Stepwise development process Build order with dependencies and verification gates. Each step must be verified in isolation before the next begins — no batching that obscures which step broke. ### Step 1 — Confirm spend-log scoping (the riskiest unknown) **COMPLETED (2026-09-13). Outcome: the member dashboard cannot read LiteLLM directly — all reads AND writes must go through the broker/portal.** Findings: - **Member keys cannot read spend endpoints.** A member's own key (both `sk-` virtual keys and legacy raw-hash tokens) gets `401 "Only proxy admin"` on `/spend/logs`, `/spend/keys`, and `/user/info`. Only the master key can query spend. - **Consequence:** the member dashboard must route *reads* through the portal (which holds the master key) too, not just writes. This actually *simplifies* the security model — the member dashboard holds **no LiteLLM credential at all**; it just calls the portal, which already authenticates members and already holds the master key. - **`/spend/keys` is the clean per-member read primitive** — it returns one row per key with `key_alias` (`member:`), `spend`, and `team_id`. Filtering by alias gives a member's spend directly. - **`/spend/logs` filtering is inconsistent:** `api_key=` filters correctly, but `team_id=` is silently ignored (returned all 92 rows for a bogus team id). Do not rely on `team_id` filtering on `/spend/logs`; prefer `/spend/keys` + `key_alias`. - **Data inconsistency found (must fix in Step 2):** three members (Dan, Ed, Nathan) have **raw-hash key tokens** (not valid LiteLLM virtual keys), while four (Lee, Joseph, Liz, Roz) have `sk-` keys. The raw-hash tokens are a legacy artifact; the Teams migration will recreate all keys consistently as `sk-` virtual keys. - **`/user/info?user_id=`** works with the master key but shows `spend: 0` and empty `keys`/`teams` because current keys have `user_id: null` (key-only, not user-bound). After the Teams migration binds keys to teams, per-team spend becomes queryable. Revised architecture (all LiteLLM access via the portal): ``` Member (browser) ──SSO──▶ Member Dashboard (new Cloudron app, "members" group) │ holds NO LiteLLM/Cloudron/OC credentials │ calls the portal for everything ▼ Portal (holds master key; authenticates member) │ read: /spend/keys?key_alias=member: │ write: mint/revoke key via master key ▼ LiteLLM Gateway (Teams + budgets + spend logs) ``` ### Step 2 — Teams migration (Phase 1) - Extend the portal: create a team per member, move/create the chat key under it, keep `provision_member()` idempotent. - Write a one-off migration script; dry-run it against the DB, then run against live members (Nathan, Liz, Dan, Ed, Lee). - **Gate:** chat works end-to-end for a test member; `/team/list` shows one team per member; spend attributes per-team. ### Step 3 — Broker (Phase 2) - Add token-protected `create-key` / `revoke-key` endpoints in the portal, scoped to a member's team, rate-limited. - **Gate:** mint a key for a test member, confirm it draws from their team budget and appears under their team; revoke works. ### Step 4 — Member dashboard (Phase 3) - New Cloudron app in the "members" group (SSO-gated). Read-only (spend/keys/usage) + broker calls for create/revoke. - **Gate:** a member logs in, sees only their own data, creates and revokes a key end-to-end, and the app holds no master key (verify via env inspection). ### Step 5 — Admin dashboard (Phase 4) - Co-op-wide view, admin-gated. Build after the member dashboard stabilizes (lower urgency — LiteLLM's UI covers some of it). - **Gate:** admins see all members + aggregate spend; members cannot reach it. ### Step 6 — Documentation (Phase 5) - API endpoint + format + budget-sharing note; member dashboard how-to; add the budget-split open question. - **Gate:** a brand-new member could follow the docs to retrieve a key and make an API call without help. ### Rollback - Every phase is additive; a failed phase can be reverted by re-pointing the injector at the prior key scheme (Step 2) or removing the new app (Steps 4–5). No data loss: keys are re-creatable, teams are idempotent by alias. ### Definition of done - A member can: chat, retrieve an API key, make a direct `curl` call to `gateway.inference.coop/v1`, see their own usage, and create/revoke keys — all within one $15 budget, with no member path touching the master key. - An admin can see co-op-wide membership, spend, and per-model breakdown.