# Checking access

URL: /products/offers/checking-access · Id: `page:products/offers/checking-access`

> Every call is checked against the Entitlements a customer's Offers grant; the check call asks first, with what is left and which Offer lifts the bound.

Every call a [Customer](/business/nouns/customer) makes is checked against the [Entitlements](/business/nouns/entitlement) their [Offers](/business/nouns/offer) grant. The [App](/business/nouns/app), the API and the MCP server run that check before every [Verb](/business/nouns/verb), so a feature locked to a plan stays locked everywhere.

## What is checked for you
You write no access code for the common cases. One check, in four places:

| Where | Without the Entitlement | Near or past a limit |
| --- | --- | --- |
| [App](/products/app) | The button shows locked, with the Offer that unlocks it | A usage bar, then the upgrade offer |
| API | A [402 reply](/code/api/errors/payment-required) that names the Offer covering the call | The same 402, unless the Offer bills `overage` past its [Limit](/business/nouns/limit) |
| MCP | The same 402; an agent can accept the named Offer and retry | The same |
| [Portal](/business/nouns/portal) | The plan comparison | Usage against each Limit, with the reset time |

A call that passes counts against its Limit exactly once. A retried call never counts twice.

## Ask before you act
Use [`Entitlement.check`](/business/nouns/entitlement/check) when your own code or agent needs the answer before it starts: before an export dialog opens, or before a long job runs. It changes nothing and never refuses.

**For agents**

- Guide: `guide:check-what-a-customer-can-do`, status `live`. Page: [/products/offers/check-what-a-customer-can-do.md](/products/offers/check-what-a-customer-can-do.md).
- Approval: none. Run it when the founder asks.
- Leaves the chat: never.
- Needs first: [Lock a feature to a plan](/products/offers/lock-a-feature-to-a-plan).
- Run: call the steps in order, each through the API, SDK, CLI or MCP tab below; a later step passes the `$id` an earlier one returns. Each value marked example is the sample business’s: pass the founder’s own.

| Step | Verb | Inputs (type, required, this guide’s value) | Returns |
| --- | --- | --- | --- |
| 1 | [`Entitlement.check`](/business/nouns/entitlement/check) (`verb:Entitlement.check`) | `customer` Customer (an id), required: example `"customer_riverside"`; `verb` Verb (an id), required: example `"Collection.export"` | `allowed` boolean, `entitlement` Entitlement, `remaining` integer, `resetsAt` datetime, `upgrade` Offer |

Returns: the last call answers with `allowed`, `entitlement`, `remaining`, `resetsAt`, `upgrade` and its `action`, whose `status` is `done`, `pending` (waiting on an Approval or a Worker), `failed`, `canceled` or `refused`:

```json
{
  "api": "https://api.sb/leaseline.app",
  "allowed": true,
  "entitlement": {
    "$id": "https://api.sb/leaseline.app/entitlements/entitlement_1",
    "$type": "Entitlement",
    "verb": "Collection.export"
  },
  "remaining": 1,
  "resetsAt": "2026-10-22T12:00:00Z",
  "upgrade": {
    "$id": "https://api.sb/leaseline.app/offers/offer_1",
    "$type": "Offer"
  },
  "action": {
    "$id": "https://api.sb/leaseline.app/actions/action_1",
    "operation": "Entitlement.check",
    "status": "done"
  },
  "links": {
    "self": "https://api.sb/leaseline.app/actions/action_1"
  }
}
```

Next: offer “Show the reset time next to the export button”, or “Warn me when a customer hits the cap three days running”.

Calls: `Entitlement.check`

Ask:

```text
Can Riverside Property export right now?
How many exports does this customer have left today?
Check this customer’s plan before the call.
```

API:

```bash
# Entitlement.check
curl -X POST https://api.sb/leaseline.app/entitlements/check \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"customer":"customer_riverside","verb":"Collection.export"}'
```

SDK:

```ts
import { $ } from 'startup-builder'

await $.Entitlement.check({ customer: 'customer_riverside', verb: 'Collection.export' })
```

CLI:

```bash
npx startup-builder do Entitlement.check --json '{"customer":"customer_riverside","verb":"Collection.export"}'
```

MCP:

```json
{
  "name": "do",
  "arguments": {
    "code": "return await $.Entitlement.check({ customer: 'customer_riverside', verb: 'Collection.export' })"
  }
}
```

`POST /entitlements/check` answers with what the customer may do now:

```json
{
  "allowed": false,
  "entitlement": "entitlement_export_starter",
  "remaining": 0,
  "resetsAt": "2026-10-24T09:14:00Z",
  "upgrade": "offer_pro_monthly"
}
```

`remaining` and `resetsAt` come back only for a Limit. `upgrade` comes back only when the Offer's `atBound` is `upgrade`.

## Features, limits and resets
An Entitlement with no Limit is a feature, and one with a Limit is a limit. The Entitlement's `kind` says which, computed from the Limit and never set by hand.

A `calendar` Limit resets at the start of each period. A `rolling` Limit counts the last period from now. To change what a plan grants, ask your agent to [lock a feature to a plan](/products/offers/lock-a-feature-to-a-plan) or [cap usage on a plan](/products/offers/cap-usage-on-a-plan).

## What a customer bought, and who may use it
An Entitlement says what the customer's business bought. A [Permission](/business/nouns/permission), held through an [Access Level](/business/nouns/access-level), says which of its people may use it. Both must pass.

A property management firm buys the `pro` plan, so its account may export. Its front-desk staff hold an Access Level without export, so the button stays hidden for them and shows for the manager.