Genie
Documentation

Budgets & caps

Four-layer credit-spend caps stack across org → application → member → user-global. Most-restrictive-wins. Every decision is audited; rollouts run in shadow before enforcing.

The four layers

Every /v1/* request runs a pre-flight gate that walks four caps. Any one trips → 402.

1. Organization (org-wide hard ceiling)

  • Organization.dailyBudget, Organization.monthlyBudget.
  • Set in /settings/billing by the org owner. Always blocking; can't be soft-warned.

2. Application (per-project tenant)

  • dailySpendCap, weeklySpendCap, monthlySpendCap, totalSpendCap.
  • onCapHit = block | notify-only | soft-warn.
  • Each downstream product (e.g. fedoverwatch, sup.video) is one Application. Keys minted with applicationId bill against its caps.
  • Optional alertWebhookUrl + HMAC secret for cap-hit pings.

3. Member (per-person within org)

  • Same four windows on OrgMember.
  • Set per-member by org admin via PATCH /api/orgs/:orgId/members/:userId/budget.

4. User (global personal cap, BUDGET)

  • User.perUserDailyCredits — applies across every org the user is in. Cross-org safety net.

Cap-hit response

HTTP/1.1 402 Payment Required

{
  "error": "Budget cap reached: application_monthly_cap",
  "code": "budget-cap-hit",
  "capLayer": "application",
  "capWindow": "monthly",
  "resetsAt": "2026-06-01T00:00:00.000Z"
}

The capLayer + capWindow tell you exactly which cap fired. resetsAt is the UTC start of the next window (omitted for total lifetime caps).

Behaviors

  • block — refuse the call, return 402, emit BudgetEvent.decision = 'refuse'.
  • notify-only — allow the call (200), emit BudgetEvent.decision = 'allow_near_cap', optionally fire the alert webhook. Customer-facing response is unchanged.
  • soft-warn — allow + set X-Genie-Budget-Remaining response header so callers can self-throttle.
Shadow rollout. Set GENIE_BUDGET_MODE=shadow in env to AUDIT every decision without enforcing. BudgetEvent rows accumulate; nothing returns 402. Watch /admin#budget for refusal-rate-over-time, then flip to on.

Audit

Every decision (allow / allow_near_cap / refuse) writes a BudgetEvent row with the orgId, userId, reason, mode, estimatedCredits, and a context blob. Same audit trail BUDGET already powered, widened to include the newapplication_* + member_* reasons.

Setting caps via API

# Application monthly cap
curl -X PATCH https://api.genie.tech/api/orgs/{orgId}/applications/{appId} \
  -H "Content-Type: application/json" \
  -d '{"monthlySpendCap": 1000, "onCapHit": "block"}'

# Per-member cap
curl -X PATCH https://api.genie.tech/api/orgs/{orgId}/members/{userId}/budget \
  -H "Content-Type: application/json" \
  -d '{"dailySpendCap": 50, "monthlySpendCap": 500}'