Repository navigation
Spend management: personal API keys, attributed spend and budgets - #153
Merged
Merged
Conversation
API keys are now organization keys or members' personal keys, every unit of usage records who caused it, and owners can cap what members, projects and API keys spend each month. Personal keys - `environment_api_key.owner_user_id`: a key belongs to the organization or is assigned to a member (their spend). Members create their own in environments an admin opened (`environment.personal_keys`; new projects open the sandbox environment, existing ones are backfilled); owners and admins create organization keys and assign keys to members (`apiKey:manage`). Better Auth checks `apiKey:create` for every key, so members hold it and `keyCreationAllowed` decides which keys. - Personal keys hold organization-wide permissions (domain changes, billing, audit) only while their owner's role does. They stop working when the owner leaves, is removed (also by SCIM) or is banned, and are deleted on leave/removal. - `coss login`/`coss init` keys are personal keys of the approver. - Keys page: owner column and picker, self-revoke, the environment's "Personal keys for members" switch (audited). Attribution and spend - `recordUsage` also writes `billing_usage_attribution` (project, key, member; a separate table so older tasks' upserts keep working during a rollout). Emails and broadcasts capture the key's owner at acceptance (`api_key_owner_id`). - `allocateUsageCharges` splits each meter's net charge by quantity (largest remainder), so per-source spend adds up to the invoice. - `GET /v1/billing/usage` takes `group_by=project|api_key|user`. Budgets - `billing_budget` (member default, member, project, API key) and `billing_budget_hold`. The worker's usage checks alert at thresholds and pause a source over budget: its live sends get `budget_exceeded` (402, SMTP 550), scheduled sends wait, broadcasts pause; lifted by a higher budget or a new month. Notifications (also to the member), push, and `billing.budget_*` webhook events (SDK types, changeset). - SMS too: messages capture the key's owner, their usage is attributed, and a used-up budget refuses (or holds) a live SMS send. Dashboard - `/[orgSlug]/spend`: spend by member, project, environment and key, per day, with budget progress; members see their own spend. `/[orgSlug]/spend/budgets`: owners manage budgets (audited, re-checked at once). A banner tells a paused member; keys show "Paused". - Docs: personal keys, "Spend and budgets", API and webhook references. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The dashboard's read-only preset for a personal key leaves out the organization-wide reads (billing, audit log) its owner's role lacks, instead of being refused. - A member's own spend page shows the default member budget before they have any live usage this month. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Spend management for coss.com: API keys can belong to a member, everything metered records who caused it, and owners can cap what members, projects and API keys spend each month.
Personal API keys
environment_api_key.owner_user_id); its usage is that member's spend.environment.personal_keys; new projects open their sandbox environment, existing sandbox environments are backfilled by0086). Owners and admins create organization keys and can assign a key to any member (apiKey:manage). Better Auth checksapiKey:createfor every key, so members now hold it andkeyCreationAlloweddecides which keys they may create.authenticateApiKey), and are deleted on leave/removal.coss login/coss initkeys are personal keys of the approving member, so members can use the CLI where personal keys are allowed.Attribution and spend
recordUsagealso writesbilling_usage_attribution(project, API key, member) — a separate table so older tasks' upserts intobilling_usagekeep working during the rollout;billing_usagestays the only rating input. Emails, broadcasts and SMS capture the key's owner at acceptance (api_key_owner_id), so revoking a key never erases a member's spend.allocateUsageCharges(pure engine): each meter's charge, net of meter discounts and write-offs, is split by quantity with the largest remainder, so per-source spend adds up to the invoice's usage lines.GET /v1/billing/usagetakesgroup_by=project|api_key|user; usage rows gainproject_id,api_key_id,user_id(SDK + changeset).Budgets
billing_budget(default member budget, per member — or an exemption —, per project, per API key) andbilling_budget_hold.checkOrganizationSpend, also run right after a dashboard change) alert at each budget's thresholds and pause a source over budget: its live sends fail withbudget_exceeded(402billing_error, SMTP 550), scheduled emails/SMS wait, broadcasts pause and resume by themselves. Lifted by a higher budget or a new month. Organization keys are never paused by a member's budget.billing_budget_paused, webhook eventsbilling.budget_threshold_reached,billing.budget_hold_placed,billing.budget_hold_lifted(SDK types + changeset).Dashboard
/[orgSlug]/spend: spend this month by member, project, environment and key, per day, with budget progress; members see their own spend only./[orgSlug]/spend/budgets: owners manage budgets (audited, re-checked at once). A banner tells a paused member.Docs: personal keys (environments), "Spend and budgets", billing, webhooks, errors, CLI.
Migrations:
0085_spend_management(additive: new tables, nullable/defaulted columns, a push enum value) and0086_open_sandbox_personal_keys(backfill of existing sandbox environments).Testing
keyCreationAllowed, read-only preset.group_byon the usage API, SDK contract.bun run lint && bun run typecheck && bun run test && bun run buildgreen.Review
Codex review, 2 rounds:
readOnlyPermissions(ownerRole)now leaves out what the owner's role lacks. Fixed: a member's own Spend page showed no budget before their first live usage; it now shows the default member budget.🤖 Generated with Claude Code