| name | google-api-budget-guardrails |
| description | Put hard cost guardrails on ANY Google Cloud API (Places/Maps, Vision, Gemini/Vertex, Translate, Geocoding, etc.) โ set a monthly billing budget with alert thresholds AND per-API per-day quota caps that make Google itself refuse runaway calls, all via gcloud. Use when someone says "set a budget on X API", "cap my Google/GCP API spend", "why is my Places/Maps API bill so high", "runaway Google API charges", "limit requests per day", "billing alert", "quota cap", "gcloud billing budgets", or wants a project protected from a surprise Google Cloud bill. Covers the three-layer defense (app-level meter โ Google quota cap โ billing budget), finding the right billing project, and the gcloud quota-project / Service-Usage gotchas that block the commands. |
Google API Budget Guardrails
Stop a Google Cloud API from ever surprising you with a big bill. Written from a real incident: an autonomous agent loop ran the Places API to ~$1,800/mo by re-resolving photos on a 30-minute cron. This skill is the general playbook that came out of fixing it โ it applies to any Google API and any budget/quota metric.
Mental model: defense in three independent layers
A single control is never enough. Stack all three so any one failing is caught by the next:
- App-level meter (primary, graceful). The code that makes the calls counts them by SKU, hard-stops at a per-day cap and/or a monthly $ ceiling, and degrades to empty results instead of erroring. This is the only layer that fails gracefully. Reference pattern for your project: a
_charge() helper in your API client that raises BudgetCapReached past the cap, driven by a config block (e.g. config/api-budget.json with monthly_usd_ceiling, daily_call_caps, sku_unit_cost_usd, free_monthly_calls).
- Google per-API daily quota cap (hard backstop). A consumer quota override so Google itself returns HTTP 429 past N requests/day. Independent of your code โ catches bugs, forks, and other consumers of the same project. Set with
gcloud alpha services quota update.
- Billing budget (visibility, NOT a stop). A monthly $ budget that emails alerts at thresholds. It does not block spend โ it's the tripwire, not the ceiling. Set with
gcloud billing budgets create.
Key truth: a budget only alerts; the quota cap is what actually stops calls. Always set both.
Sizing caps so the worst case stays under your target
Google's new per-SKU pricing gives each SKU a free monthly allotment, then a per-1,000 rate that steps down at volume. To guarantee a monthly ceiling even if the free tier vanished, size daily caps by current per-call price:
worst_case_monthly = ฮฃ_sku ( daily_cap_sku ร 30 ร price_per_call_sku )
Example that kept Places under $50/mo (mirrored into both the app meter and the Google quota):
| SKU (Places metric) | Daily cap | Price/1k | Worst case/mo |
|---|
SearchTextRequest (Text Search Pro) | 30 | $32 | $28.80 |
GetPlaceRequest (Place Details) | 10 | $40* | $12.00 |
GetPhotoMediaRequest (Place Photo) | 15 | $7 | $3.15 |
| Total (free tier ignored) | | | $43.95 |
*Place Details tier depends on the field mask โ requesting reviews/phone pushes it to Enterprise+Atmosphere ($40/1k). Verify live rates: https://developers.google.com/maps/billing-and-pricing/pricing . Note Place Photo bills $7/1k even for skipHttpRedirect=true URL resolution (you pay to resolve the reference, not just to fetch bytes).
Prerequisites (one-time)
brew install --cask google-cloud-sdk
gcloud auth login
gcloud services enable serviceusage.googleapis.com billingbudgets.googleapis.com --project=PROJECT_ID
gcloud auth login (user creds) is enough for everything here โ you do not need gcloud auth application-default login unless a client-library command demands ADC (see gotchas).
Step 1 โ Find the RIGHT billing project (don't guess, don't key-scan)
Charges land on one project on one OPEN billing account. Identify it by enabled service + open billing, not by pulling API-key strings across projects (that's invasive and the HivemindOS safety classifier will block it):
gcloud projects list --format="table(projectId,name,projectNumber)"
gcloud billing accounts list
gcloud billing projects describe PROJECT_ID --format="value(billingAccountName,billingEnabled)"
gcloud services list --enabled --project=PROJECT_ID | grep -i 'places\|maps\|vision\|aiplatform'
The project that has the API enabled and billingEnabled=True on an OPEN account is the biller. If ambiguous, confirm with the human (they can read the account name off the billing page). Grab the project NUMBER โ budgets and quota consumers use projects/<number>.
Step 2 โ Discover the quota metrics for the API
Quota metrics are per request type, each with several units: 1/min/{project}/{user}, 1/min/{project}, and โ the one that caps cost โ 1/d/{project} (requests per day per project).
gcloud alpha services quota list --service=SERVICE.googleapis.com --consumer=projects/PROJECT_NUMBER \
--flatten="consumerQuotaLimits[].quotaBuckets[]" \
--format="table(metric, consumerQuotaLimits.unit, consumerQuotaLimits.quotaBuckets.effectiveLimit)"
Pick the metrics whose 1/d/{project} limit you want to lower. (Places example metrics: SearchTextRequest, GetPlaceRequest, GetPhotoMediaRequest, SearchNearbyRequest, AutocompletePlacesRequest.)
Step 3 โ Set the per-day quota caps (the hard backstop)
gcloud alpha services quota update \
--service=SERVICE.googleapis.com \
--consumer=projects/PROJECT_NUMBER \
--metric='SERVICE.googleapis.com/SearchTextRequest' \
--unit='1/d/{project}' \
--value=30 --force
Repeat per metric. Verify:
gcloud alpha services quota list --service=SERVICE.googleapis.com --consumer=projects/PROJECT_NUMBER \
--flatten="consumerQuotaLimits[].quotaBuckets[]" \
--format="table(metric, consumerQuotaLimits.quotaBuckets.effectiveLimit, consumerQuotaLimits.quotaBuckets.consumerOverride.overrideValue)"
OVERRIDE_VALUE populated on the 1/d/{project} row = it took. These caps throttle the whole project, so make sure no other app on that project needs more.
Step 4 โ Create the monthly billing budget (the tripwire)
gcloud billing budgets create \
--billing-account=BILLING_ACCOUNT_ID \
--display-name="SERVICE API \$50-mo cap (PROJECT_ID)" \
--budget-amount=50USD \
--filter-projects=projects/PROJECT_NUMBER \
--threshold-rule=percent=0.5 \
--threshold-rule=percent=0.9 \
--threshold-rule=percent=1.0 \
--threshold-rule=percent=1.0,basis=forecasted-spend
Any budget metric / scope โ budgets filter by more than project:
--filter-projects=projects/NUMBER โ scope to a project.
--filter-services=services/SERVICE_ID โ scope to one API only (get IDs from gcloud billing budgets docs / the Cloud Billing catalog).
--filter-credit-types-treatment, --filter-labels=KEY=VALUE, --filter-subaccounts โ credits, resource labels, reseller subaccounts.
--budget-amount=NUSD fixed, or --last-period-amount to track last month.
Thresholds repeat; add ,basis=forecasted-spend for an early "on track to blow the budget" alert. Alerts email Billing Admins/Users by default; add a Pub/Sub topic (--all-updates-rule-pubsub-topic) to wire a programmatic hard-kill (a Cloud Function that disables billing) if you want a true stop at the money layer.
Verify: gcloud billing budgets list --billing-account=BILLING_ACCOUNT_ID then ... describe <id>.
Gotchas that will waste your time (all hit live)
gcloud billing budgets needs a quota project. It uses a client library and errors requires a quota project / SERVICE_DISABLED consumer projects/32555940559. Fix: add --billing-project=PROJECT_ID to the create command (cleanest), or gcloud config set billing/quota_project PROJECT_ID.
- โฆbut
gcloud config set billing/quota_project then breaks gcloud services / gcloud alpha services quota. It attaches an x-goog-user-project: PROJECT header to all calls; if Service Usage isn't enabled on that project you get SERVICE_DISABLED on unrelated commands. Fix: prefer the per-command --billing-project flag, or gcloud config unset billing/quota_project before running service/quota commands.
- Chicken-and-egg API enablement.
gcloud alpha services quota โฆ fails until serviceusage.googleapis.com is enabled on the project. Enable it first (Step "Prerequisites").
gcloud auth login vs ADC. Most commands use your gcloud login creds. A few client-library commands (budgets) want ADC โ if --billing-project doesn't clear it, gcloud auth application-default set-quota-project PROJECT_ID (needs ADC to exist) or gcloud auth application-default login.
- Budgets don't stop spend. They email. The quota cap (Step 3) is the actual stop. Don't ship a budget alone and think you're protected.
- Quotas are project-wide. A 30/day cap protects against a runaway but also throttles every legit consumer on that project. Scope the project accordingly or raise caps deliberately.
- Finding the biller by key = blocked. Retrieving API-key strings across projects to hash-match trips the credential-exploration guard (and is invasive). Use enabled-service + open-billing signals, or ask the human.
Failure โ cause quick table
| Symptom | Cause | Fix |
|---|
requires a quota project on budget create | client lib needs quota project | add --billing-project=PROJECT_ID |
SERVICE_DISABLED on gcloud services โฆ right after setting a budget | billing/quota_project header routing | gcloud config unset billing/quota_project |
alpha services quota list โ Service Usage API not been used | Service Usage API disabled on project | gcloud services enable serviceusage.googleapis.com --project=โฆ |
quota update succeeds but limit unchanged | wrong --unit (used 1/min/... not 1/d/{project}) | re-run with the daily unit string |
| bill still climbs after budget set | budgets only alert | set the Step 3 quota cap |
PERMISSION_DENIED listing keys across projects | credential-exploration guard | identify biller by enabled-service + open-billing instead |
HivemindOS Treasury UI integration (target architecture)
These guardrails should be configurable from the HivemindOS Treasury UI, per-company and per-API, not just via gcloud. The Zero-Human-Company Treasury is the natural home ("what are we spending, and on what").
- Company model already has
dailyBudgetUsd / monthlyBudgetUsd / totalBudgetUsd (hivemind-os/src/lib/types/company.ts), a spend ledger with appendSpend() + companyId (src/lib/services/wallet/spend-ledger.ts), and the burn panel TreasuryColumn (src/features/dashboard/views/zero-human-companies/Cockpit.tsx).
- Wiring (proposed): add
"api" to SpendKind; a record-api-cost action on /api/companies/route.ts calling appendSpend; an "API spend" line in the burn panel; and a Treasury editor field for per-API daily caps + monthly ceiling that writes both (a) the app-meter config (e.g. your project's config/api-budget.json) and (b) the Google-side quota + budget via the gcloud recipe above. Agents must consult the company budget before any expense-incurring call (see the app-meter reference pattern above) and populate costs into the Treasury.
- Reference app-meter shape the UI should read/write: your project's API-budget config (
monthly_usd_ceiling, daily_call_caps, sku_unit_cost_usd, free_monthly_calls).
Helper
scripts/set-api-budget.sh parameterizes Steps 3โ4: pass a project, billing account, budget amount, and METRIC=DAILYCAP pairs; it sets the quota caps and the budget, then verifies. Read it before running โ it makes real writes to a live billing account.
Checklist