---
title: "Enforce every plan limit on the server · Agent Wranglers"
url: https://agentwranglers.se/playbook/pricing/entitlements/plan-entitlements
id: PRC-14
level: Required
applies: payments
risk: money
updated: 2026-10-27
build: a2a6bdfed830
tokens: 1081
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Enforce every plan limit on the server from one entitlement table

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [Entitlements](https://agentwranglers.se/playbook/pricing/entitlements.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers.md). Updated 2026-10-27.

- ID: PRC-14
- Level: Required
- Chapter: [Entitlements](https://agentwranglers.se/playbook/pricing/entitlements.md)
- Rule: Keep the features and limits of every plan in one table, keyed by plan and feature. Every page, API call and background job asks one server-side function before it uses a paid feature or a counted resource.
- Check: An API test signs in as a free account and calls every paid endpoint directly. Each one is refused. A test creates resources up to the plan limit from 10 parallel requests, and the count never passes the limit. Search the code for plan names compared outside the entitlement function. None are found.
- Risk: Money lost or taken twice
- Applies when: The project takes payments.
- How: A check that lives only in the browser hides a button but leaves the API open, so a free account calls the paid feature directly. Store each entitlement as a flag or a number, such as exports allowed or 10 projects. The function works out the account's plan from [Give access from the subscription status](https://agentwranglers.se/playbook/pricing/entitlements/entitlements-from-status.md). It answers allowed or not, with the limit and current use. Count limits in the same transaction that creates the resource, so two requests at once cannot both pass. Answer a refused call with HTTP 403 or 402 and the name of the plan that unlocks it. Per-customer overrides, such as a sales deal, are rows in the same table with an end date. The browser may read the table to hide buttons, but never decides.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### TypeScript example

```ts
const ENTITLEMENTS: Record<string, Record<string, boolean | number>> = {
  free: { exports: false, projects: 3 },
  team: { exports: true, projects: 50 },
}

export async function checkEntitlement(db, account, feature: string) {
  const plan = await db.currentPlan(account.id)
  const limit = (await db.override(account.id, feature)) ?? ENTITLEMENTS[plan][feature]
  if (typeof limit === 'boolean') return { allowed: limit }
  const used = await db.countForUpdate(account.id, feature)
  return { allowed: used < limit, limit, used }
}
```

### Python example

```python
ENTITLEMENTS = {
    "free": {"exports": False, "projects": 3},
    "team": {"exports": True, "projects": 50},
}

def check_entitlement(db, account, feature: str) -> dict:
    plan = db.current_plan(account.id)
    limit = db.override(account.id, feature)
    if limit is None:
        limit = ENTITLEMENTS[plan][feature]
    if isinstance(limit, bool):
        return {"allowed": limit}
    used = db.count_for_update(account.id, feature)
    return {"allowed": used < limit, "limit": limit, "used": used}
```

## Sources

- [OWASP API5:2023 Broken Function Level Authorization](https://owasp.org/API-Security/editions/2023/en/0xa5-broken-function-level-authorization/)

## Related rules

- PRC-3 [Give access from the subscription status](https://agentwranglers.se/playbook/pricing/entitlements/entitlements-from-status.md) in Pricing, plans and entitlements

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/pricing/entitlements/plan-entitlements">Agent Wranglers pricing, plans and entitlements playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Map every store subscription state to access](https://agentwranglers.se/playbook/pricing/entitlements/store-subscription-states.md)
- Next: [Count seats from active members and check them at invite](https://agentwranglers.se/playbook/pricing/entitlements/seat-counting.md)
- [Entitlements](https://agentwranglers.se/playbook/pricing/entitlements.md)
