PPactDocs
Enrichment

Budgets

Multi-scope enrichment spend caps — tenant, team, provider, user, type, and level — with threshold alerts, throttle, and hard-stop auto-suspend.

Multi-scope spend control

Enrichment budgets cap how much a tenant can spend on data enrichment over a period. Unlike a single account-wide limit, a Pact budget can be scoped to any of six dimensions (core.enrichment.budgets.VALID_SCOPES):

tenant · team · provider · user · enrichment_type · level

Every charge is checked against every budget whose scope it falls into, and the most-restrictive remaining headroom wins. Budgets are stored in the enrichment_budgets table (alembic 0049).

Periods and behaviors

  • Periodday, week, or month. Spend resets automatically at the next period boundary (reset_period_if_needed).
  • Behavior — how the budget reacts when spend meets the cap:
    • alert — warn only, never block.
    • throttle — block bulk runs and scheduled sweeps at the cap, but still let a single manual lookup through.
    • block — hard-stop every charge at the cap.

The legacy hard_stop boolean is kept coherent with behavior for older rows — block/throttle imply hard-stop, alert does not.

Threshold alerts and auto-suspend

Each budget carries alert thresholds (default [50, 75, 90, 100] percent). The first time spend crosses a threshold in a period, an alert fires once — tracked in alerts_emitted_json so you're not re-alerted on every call. In production, emit_alerts_and_suspend delivers to email (Resend) and your webhook subscribers.

When a block/throttle budget with auto_suspend reaches 100%, suspended_at is stamped and subsequent charges are rejected until the next period start.

Enforcement path

check_budget is the read-only predicate; enforce_and_alert is the one-stop pre-flight that checks, fires threshold alerts, and raises BudgetExceededError (surfaced as 402 Payment Required) when the charge is denied. A run preview reports will_block and blocking_scope so the UI can warn before you commit.

Legacy tenant cap still applies underneath

Below the multi-scope budgets sits the original per-tenant cap resolved by effective_budget_usd_cents: tenants.limits_json["enrichment_budget_usd_cents"]QUOTA_DEFAULT_ENRICHMENT_BUDGET_USD_CENTS env → 0 (unlimited). Any active scope="tenant" budget overrides it; other scopes stack on top.

Managing budgets

EndpointPurpose
GET /v1/enrichment/budgetsList active budgets with spend-to-date
POST /v1/enrichment/budgetsCreate a budget
PATCH /v1/enrichment/budgets/{id}Update cap, thresholds, behavior, active
DELETE /v1/enrichment/budgets/{id}Soft-delete (sets active=false)
GET /v1/enrichment/budgets/overviewDashboard: budgets + daily spend series + top spenders
GET /v1/enrichment/estimatePre-lookup cost + your remaining headroom
bash
curl -X POST https://api.pact.place/v1/enrichment/budgets \
  -H "Authorization: Bearer $PACT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Q3 enrichment cap",
    "scope": "tenant",
    "period": "month",
    "cap_cents": 500000,
    "behavior": "throttle",
    "alert_threshold_pcts": [50, 80, 100]
  }'

The GET /v1/enrichment/estimate route powers the inline "this lookup will cost ~$0.12" confirmation on the enrich buttons — the headroom returned reflects your scopes, not just the tenant total.