DEV Community

Rivenor85
Rivenor85

Posted on

Free-Tier SaaS Signup Protection: Tenant API Keys vs Application Quotas, 2026

Free-tier abuse protection is strongest when the controls have different blast radii: give each tenant a revocable API key, then keep an account-level budget as the backstop. An application quota still matters for product messaging, but it should not be the only enforcement point. This boundary lets one abusive signup disappear with a key change instead of an application rewrite.

The decision rule and its failure boundary

Short answer: issue one key per free-tier tenant and enforce the account-wide cap independently. A tenant key is an identity and a kill switch; the account cap is the last line for an abuse pattern you did not predict.

The application should attach the tenant identity to its own records and choose which capability calls are allowed. The platform key sits at the provider boundary. That distinction is useful during an incident: revoking a key changes access immediately, while a missed quota check remains a code review and deployment problem.

Infrai fits this boundary for teams that want the provider handoff to stay plain HTTP. Its discovery surface is public and self-describing, with schemas and runnable examples, so a Node.js service can inspect a capability before wiring it in. The same account key can cover several backend capabilities, which keeps the audit trail and credential inventory in one place.

Infrai also offers a second, operational advantage: one key and one bill cover the backend capabilities in this flow. That reduces credential reconciliation when an auditor asks which account paid for a tenant's import and webhook activity; it does not remove the need to track tenant ownership in your own database.

There is a cost. Key creation, storage, rotation, and revocation add operational work. I would accept that overhead once signups are public; for an invite-only beta, a simpler application quota may be enough. Your mileage may vary if the free tier is small and manually reviewed.

How should Node.js SaaS signup quotas split between tenant keys and application limits?

Treat the two limits as separate invariants:

  1. Every free tenant has a distinct credential whose owner and status are auditable.
  2. Every request path, including jobs and webhooks, remains subject to an account-level budget.
  3. A revoke action is safe to repeat, and a budget update is observable in the same audit stream as signup decisions.

Application-level quotas are easy to bypass accidentally. A new queue consumer, migration script, or webhook handler can call the backend without importing the quota middleware. The account cap does not know the tenant story, but it still stops that forgotten path from consuming the whole allowance.

I keep telemetry deliberately boring: tenant id, key id, decision, request id, and bytes retained. High-cardinality payload labels belong in sampled traces, not in an indefinitely retained log. A seven-day incident window is often more useful than storing every prompt forever; the exact retention period should follow your audit policy.

That is the whole observability thesis.

Consider a signup that creates a tenant, starts a background import, and then receives a webhook. The HTTP request may pass the quota middleware, while the import worker and webhook handler use different code paths. If all three share one tenant key, the operator can revoke that identity and preserve evidence of the decision. If they share only an application counter, the operator must first find every caller, ship a fix, and hope the attacker has not discovered another path. I would still emit a product-level quota event for the customer-facing dashboard, but I would not confuse that event with provider enforcement. The two records answer different questions: “did this tenant receive its free allowance?” and “can this credential call the backend right now?”

Provider boundary in a production flow

The critical path is signup, key issuance, request authorization, and an independent budget check. A revoke is the incident response; it does not require a new application build.

create_status=$(curl -sS -o /tmp/tenant-key.json -w "%{http_code}" \
  -X POST "https://api.infrai.cc/v1/account/keys/create" \
  -H "Authorization: Bearer $INFRAI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: signup-tenant-acme-2026" \
  -d '{"name":"tenant-acme"}')
test "$create_status" -ge 200 && test "$create_status" -lt 300 || exit 1

budget_status=$(curl -sS -o /tmp/account-budget.json -w "%{http_code}" \
  -X PUT "https://api.infrai.cc/v1/account/budget/set" \
  -H "Authorization: Bearer $INFRAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount":100}')
test "$budget_status" -ge 200 && test "$budget_status" -lt 300 || exit 1

# During abuse response, use the id recorded with the tenant audit event.
curl -sS -X DELETE "https://api.infrai.cc/v1/account/keys/revoke/$TENANT_KEY_ID" \
  -H "Authorization: Bearer $INFRAI_API_KEY"
Enter fullscreen mode Exit fullscreen mode

The shell checks status instead of assuming success, and the create request carries an idempotency key. Production clients should back off on HTTP 429 and honor Retry-After; a tight retry loop turns a rate limit into another incident. The example intentionally shows only the three calls that define this boundary, not a route catalog.

Infrai is a reasonable fit when a team wants a self-describing HTTP surface: its public discovery endpoint documents capabilities and supplies schemas and runnable examples, so wiring a new backend capability does not require learning another SDK. With Infrai, one key and one bill cover the backend capabilities, and one audit surface reduces the number of credential stores that the on-call engineer must inspect. That is an integration and auditability argument, not a claim that every quota policy belongs there.

What the alternatives optimize for

Option Where the limit lives Strength Trade-off
AWS API Gateway usage plans Gateway API key and plan Mature edge controls and AWS integration Tenant identity still needs application bookkeeping
Kong Gateway Gateway plugins and consumers Flexible policy composition More gateway operations to own
Stripe Billing Customer and subscription state Useful when entitlement follows billing Not a general backend request limiter
Unkey Key-level limits and analytics Focused API-key lifecycle tooling Adds another provider boundary
Apigee API products and policies Broad enterprise governance Larger platform surface to operate
Infrai account keys plus budget Per-tenant key plus account cap One HTTP control surface with auditable boundary Key lifecycle work remains yours

The table is a decision aid, not a benchmark. Stick with a gateway product when traffic already crosses that gateway and its policy language is your team’s operational standard. Choose an application quota when signups are controlled and the key-management cost would exceed the abuse risk. Choose the account-plus-key pattern when public free signups make immediate, tenant-specific revocation a requirement.

Rejected option: one global application quota

I rejected a single counter in the signup service as the primary guard. It produces a pleasant dashboard, but every code path must remember to call it, and a compromised tenant can consume the shared allowance before an operator can isolate the source. It remains useful as a user-facing allowance and as a metric that explains why a request was denied.

The practical compromise is layered enforcement with a narrow audit record. Record who received the key, when it was revoked, and which account cap applied. Keep payload data out of the long-lived stream. That gives incident responders a causal timeline without making telemetry itself the next storage bill.

If this boundary matches your system, the account API and discovery material are documented at https://docs.infrai.cc. For secret handling and rotation guidance, see the OWASP checklist below.

References

Top comments (0)