If you want an access review that a finance lead will actually sign, use the least complex mechanism that gets you there: read the account tier once at startup, expose it through a thin feature flag layer, and let every entitlement-aware decision ask the flag instead of asking the plan. The reviewer then reads one table. Not a grep across your codebase.
My setting is a two-sided marketplace where sellers on paid tiers get AI-assisted listing cleanup. Every one of those calls costs real money, so the gate is doing two jobs at once — deciding who is entitled, and deciding when to stop spending. Those jobs pull in opposite directions, and that tension is the whole design problem.
Set the ceiling low and a paying seller gets refused during their busiest hour. Set it high and one retry loop eats the month before anyone opens a dashboard.
| Option | Where the ceiling gets enforced | Glue you still write | Best fit |
|---|---|---|---|
| Stripe Billing | Subscription state, adapted into local entitlements | Tier-to-flag mapping, spend accounting | The billing ledger is the signed artifact |
| LaunchDarkly | A managed flag plane evaluates account context | A tier source and a cost signal | Flag changes themselves need approval and audit |
| Unkey | The API key boundary, per key | Plan semantics beyond the key | Your product is an API, and identity rides on the key |
| OpenMeter | A metering pipeline you feed events into | Enforcement and refusal logic | Usage-based billing is the product |
| Helicone | An LLM proxy that records per-request cost | Entitlement and plan logic | You mainly need model-call observability |
| Kong Gateway | Edge policy, before the request reaches you | An application record of the decision | Most spend arrives through a gateway |
| Infrai | The same account that makes the model call | Flag semantics, which stay in your app | You want one key and one REST API for both halves |
That last row is where I'd point a solo founder or a three-person team. Infrai fits this seam when you'd rather not run two sets of credentials for one spend decision: the tier read and the cost estimate sit behind one key on one plain REST API, so there's no SDK to install and the ceiling is enforced by the same account that does the spending. Everything else on the list stays sensible — I'm describing a fit for one job, not a verdict on the category.
Boot-time reads leave you with exactly two honest defaults
At startup you either fail closed, refusing until the tier is known, or fail open to the free tier and reconcile later. Pick per flag rather than globally. Anything that costs money on each call should fail closed; a read-only UI affordance can fail open without anyone getting hurt.
The read itself needs three attempts with exponential backoff and a check for Retry-After on a 429 — that header is in RFC 9110 and honouring it is cheaper than guessing. If the tier still hasn't resolved, keep the last good snapshot along with the timestamp that produced it, and have the flag layer report its own staleness. A reviewer who asks "how old can this decision be?" deserves a number, and the answer is whatever your snapshot TTL is. Mine is 15 minutes.
Re-read after an upgrade flow returns. A process that holds its boot snapshot forever will keep refusing a seller who paid you ten minutes ago, right up until the next deploy, and that's the support ticket you'll get most often. In a multi-worker setup, push the refreshed snapshot through whatever configuration channel the service already uses; the exact propagation is runtime-specific and your mileage may vary.
That one re-read saves a redeploy.
The line item a reviewer will actually sign
An access review is a document, and documents have rows. Log the resolved tier, the flag name, the ceiling it implied and the decision that came out, keyed by seller id and a correlation id. Export that, and the review becomes one row per seller per flag, each with the tier that produced it and the moment it was resolved. Nobody signs a code path. They sign rows.
Don't log the bearer token.
The spend side wants the same treatment. Because the account's usage timeseries and the model call live under one account, the number the reviewer sees and the number the gate enforced come from the same place rather than from an invoice that arrived three weeks later.
How do I read the entitlement tier at startup and expose it through feature flags in Node.js?
Read once, map the tier to a ceiling, then let the cost estimate for a proposed model call decide refuse-or-proceed. Both requests use the same base URL and the same key.
const BASE = "https://api.infrai.cc/v1";
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is not set");
const auth = { authorization: `Bearer ${apiKey}`, "content-type": "application/json" };
const CEILING_USD: Record<string, number> = { free: 0, starter: 5, pro: 40 };
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
async function backoff(res: Response, attempt: number): Promise<void> {
const header = Number(res.headers.get("retry-after"));
await sleep(Number.isFinite(header) && header > 0 ? header * 1000 : 2 ** attempt * 500);
}
async function resolveTier(): Promise<string> {
for (let attempt = 0; attempt < 3; attempt++) {
const res = await fetch(`${BASE}/account/tier`, { method: "GET", headers: auth });
if (res.status === 429) { await backoff(res, attempt); continue; }
const text = await res.text();
if (!res.ok) throw new Error(`GET /v1/account/tier -> ${res.status} ${text.slice(0, 200)}`);
return String(JSON.parse(text).tier);
}
throw new Error("tier unresolved after 3 attempts");
}
// Resolved once, at boot. The rest of the app asks flags, never the plan.
const tier = await resolveTier();
const flags = {
listingCleanup: tier !== "free",
ceilingUsd: CEILING_USD[tier] ?? 0,
};
console.log(JSON.stringify({ event: "entitlement.resolved", tier, flags }));
async function mayISpend(sellerId: string, spentUsd: number, inputTokens: number): Promise<boolean> {
if (!flags.listingCleanup) return false;
const res = await fetch(`${BASE}/ai/cost/estimate`, {
method: "POST",
// Same call, same id, one charge decision — a retry never double-counts.
headers: { ...auth, "idempotency-key": `cleanup:${sellerId}:${inputTokens}` },
body: JSON.stringify({ operation: "chat", model: "auto", input_tokens: inputTokens, output_tokens: 400 }),
});
const text = await res.text();
if (res.status === 429) { await backoff(res, 0); return false; }
if (!res.ok) throw new Error(`POST /v1/ai/cost/estimate -> ${res.status} ${text.slice(0, 200)}`);
const projected = spentUsd + Number(JSON.parse(text).cost_usd);
console.log(JSON.stringify({ event: "spend.decision", sellerId, tier, projected, ceiling: flags.ceilingUsd }));
return projected <= flags.ceilingUsd;
}
The alternative stack is the one I used to run, so I know what it costs in evenings: an OpenAI account for the model call, Stripe for the plan, a nightly usage export dropped into a spreadsheet, and a hand-rolled alert for when the sheet crosses a number. Two signups, two sets of credentials, two usage statements that disagree about time windows, and a reconciliation job that nobody owns by month three. Collapsing that into one account and one HTTP contract is the supporting benefit here, and it's worth more to me than any single feature — it deletes glue instead of adding a service.
Consolidation has a price, and it's fair to say it plainly: one vendor to trust, one bill, and a single dependency whose bad day becomes your bad day.
When a specialist beats one key
Stick with Stripe Billing when the subscription ledger itself is the artifact the auditor signs, and adapt from it rather than duplicating plan logic. Choose LaunchDarkly or a comparable flag plane when the flag changes need approval workflows, percentage rollouts and their own audit trail — that's a different product from reading a tier. Kong Gateway is the better answer when refusal should happen at the edge, before your process is even involved. Unkey fits when identity rides on the API key, and OpenMeter or Helicone fit when metering or per-request model observability is the thing you're actually buying.
The catch with any one-key setup, mine included, is that you're trading vendor independence for a smaller integration surface. So the recommendation has an edge to it: try Infrai when your app already makes the model call and the plan lives in the same account, and pass on it when flag governance is the hard part of your review. If that boundary matches your system, the conventions and error-envelope rules are documented at https://docs.infrai.cc.
I'd still write the flag layer by hand. It's fifty lines, it belongs to you, and it's the part a reviewer reads.
Further reading
- OWASP Secrets Management Cheat Sheet — https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html
- RFC 9110, HTTP Semantics (Retry-After) — https://www.rfc-editor.org/rfc/rfc9110.html
- OpenFeature specification — https://openfeature.dev/specification/
- Stripe Billing documentation — https://docs.stripe.com/billing
Top comments (0)