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

# Read a mandate's scopes

> Turn the scopes on a Stamped Mandate into ceilings you can compare against a request, with readScopeCeiling.

export const waitlists = {
  earlyAccess: "",
  payments: "",
  openBanking: "",
  developers: "",
  verifiers: ""
};

<Note>
  **Private preview:** this is rolling out and may not be available on your account yet.{waitlists.earlyAccess ? <> <a href={waitlists.earlyAccess}>Request early access →</a></> : null}
</Note>

A Stamped Mandate permits only what its [scopes](/concepts/mandates/scopes) say. Before you act, check that the request in front of you is inside them. The scope language, parameters and limits are described in [The mandate language](/concepts/mandates/language) and [Limits and narrowing](/concepts/mandates/limits); this page covers reading them in code.

## Read a spending ceiling

`readScopeCeiling` reads the ceiling one scope states. It works offline on the scope string alone, so you can use it on the `scopes` from the [exchange](/sdk/verify/checks#exchange).

```ts theme={null}
import { isScopeCeilingError, readScopeCeiling } from "@sumvin/sdk";

readScopeCeiling("sr:us:pint:spend:visa_checkout?max=25&currency=USD");
// { kind: "fiat", amount: "25.00", currency: "USD", decimals: 2 }

readScopeCeiling("sr:us:pint:sigil:proof_of_personhood");
// null: this scope states no ceiling
```

`max` is always in the display unit of the currency: `max=25&currency=USD` is 25.00 US dollars, never 25 cents. `amount` comes back as an exact decimal string, padded to the currency's precision. Compare it as a decimal, never as a floating-point number.

<Note>
  Spending scopes belong to payments, which arrive in a fast-follow release. Identity scopes such as `sr:us:pint:sigil:proof_of_personhood` state no ceiling.
</Note>

## Read every scope

The reader handles one scope at a time. Read every scope on the mandate, not only the first.

```ts theme={null}
const ceilings = scopes
  .map((scope) => ({ scope, ceiling: readScopeCeiling(scope) }))
  .filter(({ ceiling }) => ceiling !== null);
```

## When it refuses

The reader refuses rather than guesses. It throws a `ScopeCeilingError` whose `reason` is one of:

| Reason                   | Means                                                                         |
| ------------------------ | ----------------------------------------------------------------------------- |
| `malformed-scope`        | The scope, or its parameters, aren't written the way the language allows.     |
| `invalid-amount`         | `max` isn't a plain positive decimal.                                         |
| `missing-denomination`   | `max` names no currency or asset.                                             |
| `ambiguous-denomination` | It names both a currency and an asset.                                        |
| `unknown-denomination`   | The currency or asset isn't one the SDK knows.                                |
| `over-precise`           | `max` has more decimal places than the currency allows, such as `25.001` USD. |

```ts theme={null}
try {
  readScopeCeiling(scope);
} catch (e) {
  if (isScopeCeilingError(e)) {
    // Unreadable: refuse the request.
  }
}
```

**Treat a refusal as unreadable, never as unlimited.** Refuse the request.

`errand:search` takes an optional `max` but names no currency, and that `max` is not enforced (see [Scopes](/concepts/mandates/scopes)). A `max` written there has no currency to read, so the reader refuses it as `missing-denomination`. The ceiling that counts is on `spend:visa_checkout`.
