Marketplace billing meters: Credit Reports vs Bureau Report PDFs
Internal reference for the two GHL marketplace meters this app charges against. Not a Mintlify page. Operator-facing behavior (price, wallet gate, free cached re-downloads) is unchanged by which meter a charge lands on.The pieces
Both env vars must be set wherever charge bodies are built:
- Vercel (Next.js):
lib/billing.tsbuilds the pre-provider secured charge forapp/api/credit-report/[requestId]/bureau-pdf/route.tsand the approved-collections replay inapp/api/admin/collect-approved-credit-charges/route.ts. - Convex deployment:
convex/billingRetryQueueNode.tsbuilds the fallback charge body for retry-queue rows that stored no meter.
GHL_MARKETPLACE_APP_ID stays the single app id for both meters.
How the meter is chosen
lib/billing/bureauPdfMeter.ts is the only place that maps a charge to a meter.
The eventId already names the billable product, so marketplaceMeterIdFor(eventId)
returns the PDF meter for bureau-pdf-* events and the Credit Reports meter for
everything else. There is no other routing, no per-location override, and no
pricing logic in that module.
Rules the tests pin (tests/bureauPdfMeter.test.ts):
- New PDF charges → PDF meter. Secured charge and approved-collections replay
both send
meterId = GHL_MARKETPLACE_BUREAU_PDF_METER_IDforbureau-pdf-*keys. - Fail closed, never fall back. A
bureau-pdf-*charge with the PDF meter unset returnsbilling_unconfigured(secured path) orBilling not configured(replay path). It never borrows the Credit Reports meter. Credit pulls are unaffected by a missing PDF meter, and$0complimentary PDFs bypass billing without needing any meter. - Historical rows keep their meter. A
billingRetryQueuerow stores the full charge body it was enqueued with, includingmeterId. The retry drain replays a stored meter as-is; only a row with no stored meter resolves one by product. - Refunds and reconciliation need no meter.
releaseSecuredChargereverses bychargeId(DELETE /marketplace/billing/charges/{chargeId}), and approved-collections claim releases likewise act onchargeId. Whatever meter a charge was created under, its reversal targets that same charge.
Production rollout note
Deploy order matters: setGHL_MARKETPLACE_BUREAU_PDF_METER_ID on Vercel and the
Convex production deployment before shipping code that reads it, or bureau PDF
pulls refuse with a billing-unconfigured error until it lands. Production env
writes are a human step under AGENTS.md Safety. Do not create a second meter or
change pricing on either meter from code; both live in the marketplace app config.
CLI-compatible meter configuration (2026-10-06)
Brock authorized correcting the existing Marketplace app697a568ce94ccaee772a84b0 billing configuration in the Codex conversation after
reviewing the three-field repair. The official Marketplace CLI 1.0.0 API client
updated the two existing tiers; it did not create meters or change subscriptions.
Both pricing-page URLs are
https://myfundingmachine.com. The Credit Reports URL
previously lacked the HTTPS scheme; both minimums were previously zero. The CLI
requires unit-price bounds of 0.000001–200. Defaults, maxima, tier names, IDs,
volume ranges, execution limits, and the free subscription were preserved.
Complimentary work remains free: chargeForCreditPull, chargeForAiOverview, and
secureChargeBeforeProviderCall bypass Marketplace charging at a zero price.
Credit-pull retry enqueueing occurs after that bypass; the legacy webhook and
credit-pull retry drain send $5. Approved collections use chargeForCreditPull.
No application code change was needed for this configuration repair.
Exact API readback matched the prepared configuration, and the full
ghl app pull --with-types now succeeds. Use the official pinned CLI in an
isolated workspace, then ghl app billing validate and ghl app billing diff to
check future billing changes. Never use an action test to verify billing: it can
execute a real endpoint and trigger a billable pull.
The public site’s Pricing page currently omits credit-report and bureau-PDF
rates. Adding that disclosure is separate website work; this repair only
normalized the existing URL and minimums.
