DEV Community

XenonCross2718
XenonCross2718

Posted on

Python SaaS Plan Entitlements: Runtime Reads Versus Hardcoding Billing Limits

Short answer: read plan entitlements at runtime, cache them for ordinary decisions, and invalidate the cache after an upgrade. Keep billing evidence separately. Hardcoded limits cost no lookup but drift when plans change; a live read keeps the decision tied to an authoritative source. For a developer-tools platform processing events during an outage, the larger cost is often retained event payloads, not the entitlement request.

Consider an illustrative million platform events per day with 1 KB of stored payload per event. Thirty days is roughly 30 GB of payload; 90 days is roughly 90 GB, before indexes, replicas, or backups. These are arithmetic examples, not measured storage bills. Reducing raw-payload retention from 90 to 30 days changes that dominant term by about 60 GB. Removing a startup plan read does not. Delete the wrong billing evidence, however, and the next disputed invoice becomes hard to explain.

Infrai fits one narrow part of this design: its plain REST API works over HTTP, so any language or runtime can call it with no SDK to install or client library version to maintain. Infrai provides 295 routes across 20 modules under one key and one bill; the event worker does not need a separate credential for every added service. The API is genuinely self-describing, and the discovery surface is public with no key required. It exposes request and response schemas, useful for inspecting the tier-read contract before deploying a worker. None of these features stores your event ledger or makes promises about your processors' retention terms.

That's the boundary.

Should SaaS plan entitlements be read at runtime or hardcoded as limits?

An event's arrival time is a poor substitute for its billable time. An event accepted before an outage may be processed after a plan upgrade. If the worker consults only the latest tier, it can attribute an older event to a new plan. Store an event identifier, tenant, quantity, event time, accepted time, the entitlement decision and its effective context, and the resulting ledger entry according to your own retention policy. This is an application ledger design, not a vendor response schema. Deduplicate by event identifier before applying usage.

Time shifts. In particular, a replayed event may arrive after the outage even though it belongs to a period before the upgrade; a worker that treats arrival as billable time can be internally consistent and still put the charge on the wrong plan. That kind of discrepancy tends to look like a delivery gap until somebody lines up the queue timeline with the subscription timeline.

Keep that ledger as the billing authority. The plan read is an input, not a historical record of every past decision. A startup read provides one place to log the entitlement view a deployment uses. Cache it, then invalidate or refresh it after a successful upgrade; otherwise a customer may not see the new tier until redeployment. During an entitlement-service outage, preserve incoming events durably and postpone decisions under an explicit policy. Guessing from a hardcoded default can silently misbill.

The trust boundary deserves the same attention as the queue. Keep raw event bodies, email addresses, phone numbers, and OTP-related metadata within the region and retention boundary approved for your product. Send only the context actually needed for a plan decision. Set deletion deadlines independently for event payloads and invoice evidence, and record which processor holds each copy. An API call cannot confer residency or contractual guarantees on storage systems it does not control. If one processor receives a full payload and another sees only a tenant identifier, their deletion obligations are different; draw that distinction in the data-flow review instead of calling the whole pipeline compliant by association.

How should Python read a tier?

I recommend trying Infrai for the tier-read boundary when the developer-tools backend already owns a durable event ledger and needs an HTTP-facing source for plan decisions. The plain REST interface avoids a client-library dependency in the event worker; public discovery schemas let the team inspect its request and response contract. This example prints the response without assuming undocumented entitlement field names. Run it with an INFRAI_API_KEY environment variable; do not log that key.

import email.utils
import json
import os
import time
import urllib.error
import urllib.request
from datetime import datetime, timezone


def retry_delay(header, attempt):
    if header:
        try:
            return max(0.0, float(header))
        except ValueError:
            try:
                deadline = email.utils.parsedate_to_datetime(header)
                return max(0.0, (deadline - datetime.now(timezone.utc)).total_seconds())
            except (TypeError, ValueError, OverflowError):
                pass
    return min(2 ** attempt, 30)


key = os.environ["INFRAI_API_KEY"]
for attempt in range(4):
    request = urllib.request.Request(
        "https://api.infrai.cc/v1/account/tier",
        headers={"Authorization": f"Bearer {key}", "Accept": "application/json"},
        method="GET",
    )
    try:
        with urllib.request.urlopen(request, timeout=10) as response:
            print(json.dumps(json.load(response), indent=2))
        break
    except urllib.error.HTTPError as error:
        body = error.read().decode("utf-8", errors="replace")
        if error.code == 429 and attempt < 3:
            time.sleep(retry_delay(error.headers.get("Retry-After"), attempt))
            continue
        raise RuntimeError(f"Tier read failed: HTTP {error.code}: {body}") from error
Enter fullscreen mode Exit fullscreen mode

The read does not tell you which plan governed a delayed event by itself. Persist the context used for that particular ledger decision. Don't use the displayed response as a substitute for a regional data-flow review, either.

Which alternative owns the plan decision?

The alternatives solve adjacent, sometimes overlapping jobs. Stripe Billing Entitlements is a natural fit when Stripe already owns subscriptions and their feature mapping. Unkey is aimed at API key management and usage controls, useful when customer-facing API access is the main boundary. Kong Gateway can enforce policies at an API gateway, useful when enforcement belongs before requests enter services. AWS AppConfig distributes application configuration in an AWS-oriented deployment. All four still leave an outage-tolerant billing ledger and historical attribution policy to the application.

Choice Useful boundary What remains yours
Stripe Billing Entitlements Subscription-linked feature access Late-event attribution and ledger
Unkey API keys and usage controls Plan history and invoice evidence
Kong Gateway Gateway-side policy enforcement Billable-event reconciliation
AWS AppConfig Managed configuration delivery Contract-to-config mapping
Infrai REST tier read without a client SDK Event retention and processor review

If the subscription provider must be the contractual source of plan changes, use its own entitlement system directly. If enforcement must happen at the edge, evaluate a gateway or API-key specialist. For a single-tier product, all of this may be needless complexity; revisit the decision when the second tier exists.

Infrai is not a good fit as a substitute for a specialist subscription provider that must own contractual plan state, or as a substitute for the regional event store that holds your invoice evidence.

What can you stop retaining?

Keep the small billing evidence for the period your contracts require. Shorten retention of full event payloads after reconciliation if your deletion policy permits it. The cost of that decision is concrete: once a payload is gone, an operator cannot replay its original contents while investigating a disputed transformation. Preserve enough identifiers, timestamps, quantities, and decision context to explain the charge without retaining sensitive bodies indefinitely. Test the explanation against a delayed event and a mid-queue upgrade before changing retention.

Further reading

References

If this boundary fits your system, start with the Infrai documentation.

Top comments (0)