The Entitlements Problem: Why Auth + Billing + Flags Need to Talk
There’s one question that every SaaS application answers thousands of times per second: can this user do this thing?
It sounds simple. It is not simple.
The Full Question
The real question is almost never just “is this user authenticated?” It’s a composite:
- Is this user authenticated? (who are they?)
- What plan are they on? (what are they allowed?)
- Is this feature enabled for their account or org? (is it rolled out to them?)
- Have they exceeded their quota this period? (have they hit a limit?)
These four sub-questions come from four different systems: auth, billing, feature flags, and rate limiting. In most SaaS stacks today, those systems don’t talk to each other. You’re responsible for stitching them together on every request.
The Glue Code Nightmare
Here’s what that actually looks like in practice. You want to check whether a user can access the new analytics dashboard, which is only available on Pro plans and currently being rolled out to 20% of users:
# Step 1: verify the session
user = auth_client.(.["Authorization"])
if not user:
raise()
# Step 2: fetch their billing plan (network call)
subscription = billing_client.(.)
if subscription.plan not in ["pro", "enterprise"]:
raise("requires Pro plan")
# Step 3: check if the feature is enabled for this user (network call)
flags_client =(())
if not flags_client.("analytics-dashboard", False):
raise("not yet available")
# Step 4: check their rate limit (network call)
quota = rate_limiter.(f"analytics:{.}", limit=100, window="1h")
if quota.exceeded:
raise(retry_after=.)
# Finally, do the actual thing
return(.)Four network calls. Four different error formats to handle. Four different SDKs to keep updated. Four different things to mock in tests. And this is the optimistic version — in practice you’re also caching, handling service unavailability, logging, and writing middleware to avoid repeating this on every endpoint.
This code exists in some form in virtually every SaaS app. It’s not a problem you solve once — it’s a recurring cost you pay every time you add a feature, a plan, or a flag.
The Entitlements Engine
Flux has a different answer. Instead of four separate systems, there’s one call:
let context = flux.entitlements().check(&user, "analytics-dashboard")?;
if !context.allowed {
return Err(context.reason); // "requires Pro plan", "quota exceeded", etc.
}Under the hood, Flux evaluates auth, billing tier, feature flag state, and rate limit quota simultaneously, with full awareness of each. The result comes back in a single response with a typed reason when access is denied.
This works because Flux’s subsystems aren’t separate services — they’re connected. The feature flag system knows what plan a user is on. The rate limiter knows what API key they used to authenticate. The auth layer knows what features their org has access to.
A Real-World Example
Suppose you’re launching a new export feature. You want to:
- Make it available only to Pro and Enterprise customers
- Roll it out to 10% of eligible users first
- Limit exports to 50 per day on Pro, unlimited on Enterprise
In a disconnected stack, you implement this in at least three places: your billing check, your flag config, and your rate limiter — and you have to keep them in sync manually.
In Flux, you configure this once in the entitlements dashboard. The plan-aware flag rollout and the plan-aware rate limit are both expressions of the same underlying entitlement definition. Change the limit on the Pro plan, and it’s reflected everywhere.
The question “can this user do this thing?” deserves a real answer, not a scavenger hunt across four vendor APIs. That’s what we built.
Enjoyed this post? Get updates from the Flux blog.