> ## Documentation Index
> Fetch the complete documentation index at: https://docs.joinbankroll.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Restrictions

> Where, and from what age, your app takes real money — one policy, evaluated on your server against the verified session.

A **restriction policy** says where your app may take real money and from what
age. It is one JSON document with two parts: `geo`, the countries and regions
that are open or blocked, and `age`, the minimum age by country and region.
Your server evaluates it against the [verified session](/build/session) before
every paid action, and refuses when it says no.

An app built in Bankroll's builder receives its policy from Bankroll in its
`BANKROLL_RESTRICTIONS` setting. A self-hosted app sets the same setting
itself.

## The policy

```json theme={null}
{
  "version": 1,
  "geo": {
    "default": "allow",
    "countries": {
      "US": { "block": ["ME"] },
      "CA": { "default": "block", "allow": ["QC"] }
    }
  },
  "age": {
    "default": 18,
    "countries": { "CA": { "default": 19 } }
  }
}
```

The codes above are an example of the shape, not a policy.

| Field                      | Meaning                                                                  |
| -------------------------- | ------------------------------------------------------------------------ |
| `geo.default`              | `allow` or `block`, anywhere no country rule speaks.                     |
| `geo.countries.XX.default` | The action for the country's other regions. Falls back to `geo.default`. |
| `geo.countries.XX.allow`   | Regions open for paid play, as suffixes: `"CA"` under `"US"`.            |
| `geo.countries.XX.block`   | Regions closed for paid play. A region is never in both lists.           |
| `age.default`              | The minimum age anywhere no country rule speaks. 18 when absent.         |
| `age.countries.XX.default` | The minimum age in the country's other regions.                          |
| `age.countries.XX.regions` | Minimum ages by region suffix.                                           |

Countries are ISO 3166-1 alpha-2 codes and regions are ISO 3166-2 suffixes,
checked by shape only, so every region of every country is accepted. Letter
case does not matter. A full code such as `"US-CA"` under `"US"` is accepted
and reduced to its suffix. A policy with an unknown key, a bad code, or a
region in both lists is refused when it is read.

## How it is evaluated

* **No policy means no restriction.** An unset or empty setting restricts
  nothing. Whether the player has verified at all is a separate gate.
* **The age rules come first.** A session with no verified age is refused. A
  policy with no `age` part holds the player to 18.
* **The location rules fail closed.** A country with `allow` or `block`
  regions needs the session's region; without it the location is unknown and
  paid play is off. A policy with any country rule needs the session's country.
* **The age rules ask for the region only when it could change the answer.**
  A 30-year-old whose location resolved only to `US` passes under a policy
  that says 21 in one state, because 30 clears every minimum in the country.
  A 20-year-old does not, and is refused until the region resolves.

The result is one verdict:

| `reason`            | Meaning                                                           |
| ------------------- | ----------------------------------------------------------------- |
| `null`              | Paid play is allowed.                                             |
| `age_unknown`       | The session carries no verified age.                              |
| `age_under_minimum` | The player is younger than the minimum where they are.            |
| `location_unknown`  | The policy needs a region or country the session did not resolve. |
| `location_blocked`  | The policy closes the player's location.                          |

`minimumAge` comes with it: the minimum the player was held to, when the
policy resolves one.

## Use it in your app

`@joinbankroll/sdk/restrictions` reads the setting and evaluates a session. It
has no dependencies.

```ts theme={null}
import { getSession } from "@joinbankroll/sdk/next";
import { restrictionFor, restrictionPolicyFromEnv } from "@joinbankroll/sdk/restrictions";

export async function POST(request: Request) {
  const session = await getSession(request);
  if (!session?.user.identity) return Response.json({ error: "unauthorized" }, { status: 401 });

  const { reason, minimumAge } = restrictionFor(session, restrictionPolicyFromEnv());
  if (reason) return Response.json({ error: reason, minimumAge }, { status: 403 });
  // ... take the payment
}
```

Decide on the server, from the verified session. Never from a location or an
age the browser sent. The starter wraps the two calls as
`paidPlayRestriction(session)` in `src/lib/restrictions.ts`, its `/api/me`
route returns the verdict as `restriction` so the UI can disable paid actions
and say why, and the P2P engine's example route refuses on it.

`restrictionPolicyFromEnv()` throws on a malformed setting instead of allowing
play. `readRestrictionPolicy(document)` reads a policy from anywhere else, in
canonical form.
