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

# Grants and sectors

> The six sectors, which sectors a sign-in turns on, what one grant covers, and where every control that changes a grant lives in the Console.

<Info>
  **Before you start**

  * **Every control on this page is in one place.** Open the Console and click
    **Agents & keys**.
  * **A grant is the sector list on one agent's key.** It is not a separate
    object. It exists from the moment you create that key.
  * **A sector is one area of your life: finance, mail, calendar, contacts,
    health, home. It is the smallest thing you can grant.**
  * **A change to a grant cuts that agent's live sessions.** The new sectors
    apply at the agent's next session, never inside the one it holds.
  * **Two neighbouring pages carry the rest.** The key itself — its deadline, and
    how to revoke, rotate or remove it — is on
    [Agent keys](/reference/agent-keys). What an agent sees when a grant says no is
    on [Why an agent was refused](/reference/refusals).
</Info>

## The six sectors

Postern sorts everything it holds into six sectors. The six are fixed: you
cannot add one, rename one or split one.

| Sector     | What an agent reads in it                                     | Sources that fill it      |
| ---------- | ------------------------------------------------------------- | ------------------------- |
| `finance`  | `finance.account`, `finance.transaction`, `finance.holding`   | Plaid, SimpleFIN          |
| `health`   | `health.sample`, `health.workout`, `health.sleep_session`     | WHOOP, Apple Health       |
| `mail`     | message envelopes. Postern never caches bodies or attachments | Gmail, Microsoft, iCloud  |
| `contacts` | one address-book entry per source                             | Google, Microsoft, iCloud |
| `calendar` | event occurrences, one row per instance                       | Google, Microsoft, iCloud |
| `home`     | the last known state of each device or entity                 | Home Assistant            |

A grant names a sector, never a source. You choose what an agent reads when you
create its key, not when you connect a source. Grant `mail` and that key reads
every mail source you have now, and every one you add later. There is no way to
grant one and withhold another. Disconnect a source and the grant does not
change — the sector holds less. A key that holds none of a source's sectors sees
nothing, however many sources you connect.

### Which sectors a sign-in turns on

Postern reads the scope list the provider returns with the token, not the list
Postern asked for. It creates one connection per sector that list covers.

* **Google** — one sign-in becomes two connections, `calendar` and `contacts`,
  because the consent covered two sectors.
* **Microsoft** — Postern requests exactly `Mail.Read`, `Calendars.Read`,
  `Contacts.Read` and `offline_access`, and nothing else on any sign-in. That
  gives three connections. A consent that grants fewer gives fewer connections;
  sign in again and approve the whole set.
* **WHOOP** — `read:recovery`, `read:sleep` and `read:workout` each map to
  `health`. `read:cycles`, `read:profile` and `offline` map to no sector at all.
  A consent that returns none of the three health scopes creates no connection. The
  Console then answers `WHOOP signed in, but granted no sectors. Check the scopes
  on your app, then sign in again.` It can only report the connection it made. It
  cannot name the scope it did not get.

### Two sources inside one sector

SimpleFIN arrives as one connection that holds every bank linked at the Bridge.
The Console has no bank picker. SimpleFIN Bridge decides which banks it covers,
and a bank added or dropped there appears or disappears on the next sync. The
**Sources** row says so, with your own bank count in place of `<n>`:
`One link carries all <n> — which banks are in it is chosen at SimpleFIN Bridge ↗`.

Connect Plaid as well and the same account can arrive through both. Postern's
dedup engine matches on the institution's domain plus the last 4 digits in the
account name, and serves one account rather than two. It merges on its own only
when both providers report a last 4. When one does not, it refuses to guess and
asks `Is this the same account, seen twice?` Answer once with **They're the same
account** or **Keep them separate**, and it stays answered.

### A granted sector with no source behind it

It is still granted, and it answers nothing. `describe_context` returns the
sectors the key was **granted**, not the sectors that hold data. A `query` there
comes back empty rather than refused, and `fetch_live` answers `no active
connection for sector '<name>'`. Neither is a permission problem, and neither
looks like one in the record.

## What a grant covers

A grant works at the level of a whole sector. The same sector list gates reads
and actions, with nothing per-action, per-tier or per-confirmation beneath it.
The Console says so under the sector toggles: `Each granted sector covers read
and act across every provider in it.`

There is no confirmation step between an agent's decision and your house, and a
hosted agent asks no permission per call. Your instruction to the agent is the
authorization. The sectors you tick at creation are the whole boundary between
that agent and your data, for as long as the key lives.

### Which sectors an agent can act in

| Sector                                              | Can an agent act in it?                                                                                                                         |
| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `home`                                              | **Yes.** Home Assistant serves a live action catalogue, built from its own entity-and-service registry.                                         |
| `finance`, `health`, `mail`, `contacts`, `calendar` | **No.** Every connector in these five declares an empty action list, so `list_actions` returns nothing and `invoke_action` has nothing to call. |

The **Mint a key** form states it before you grant anything: `A grant covers read
and act, but only Home has a catalogue to act on today; Finance, Mail, Calendar,
Contacts, Health are read-only.`

So no agent can move money through Postern, whatever its grant says. Google's
`calendar` and `contacts` are read-only twice over: the two scopes Postern
requests are read-only, and the connector refuses to run an action. WHOOP is the
same.

That is a statement about connectors, not about the permission model. If a source
adds actions later, keys that already hold that sector can use them. Nothing in a
grant names an action, so Postern re-issues nothing and asks nobody again.

### What the grant decides, tool by tool

| Tool                  | What the grant decides                                                                            |
| --------------------- | ------------------------------------------------------------------------------------------------- |
| `describe_context`    | Answers with the granted sector list, and only connections inside it                              |
| `get_schema`          | Objects in ungranted sectors are absent rather than refused, so nothing leaks about what exists   |
| `query`, `get_record` | Denied before Postern composes any SQL                                                            |
| `fetch_live`          | Denied before any SQL, and before Postern touches anything upstream                               |
| `list_actions`        | Lists the granted sectors; a named ungranted one is denied                                        |
| `invoke_action`       | Every action name starts with its sector (`home.light.turn_on`), and that sector decides the call |

Nothing is narrower than a sector. There is no per-connector, per-tool or
per-call gating underneath a grant. Parameters and returns are on
[MCP tools](/reference/mcp-primitives).
[Refused, or quietly filtered](/reference/refusals#refused-or-quietly-filtered)
carries the exact wording an agent gets back in each row of that table.

An agent whose key opens `finance` is served the accounts, transactions and
holdings in the local cache, plus each connection's health. Once Plaid reports
one, Postern also serves the date that bank's consent expires.

## Read a grant, and change it

**To read what a key holds.** Click **Agents & keys**, then the agent's name. The
lit toggles under **Grants** are its sectors. The line under the name reads
`never expires` or `expires in 60d`. The section headed **Recent passage** lists
that key's last calls.

<Warning>
  A change to a grant cuts that agent's live sessions. Postern applies the new
  sectors at the agent's next session, never inside the one it holds. If the
  agent is at work, expect to watch it stop.
</Warning>

**To change the sectors.** On that same page, tap a sector under **Grants**.
Postern saves at once — there is no Save button. The hint under the toggles then
reads `Tap to toggle; the change applies at the agent's next session.`

**To create a key.** Click **Agents & keys**, then **Mint a key**. Type a name.
Tap sectors under **Grant which sectors**. Pick a chip under **Expires**. Press
**Mint the key**. Postern cannot show you this key again. It keeps a scrambled
copy it can check against, never the key itself.

One agent name holds one grant, and the record stores that name against every
call the key makes. A second key under an existing name is a rotate — see
[Revoking, rotating, and removing](/reference/agent-keys#revoking-rotating-and-removing).

**Through the API instead.** `POST /api/agents` creates or replaces a grant, and
`PATCH /api/agents/<name>` edits one. Both take a `sectors` array of strings and
an optional `expiresInDays` between 1 and 3650. Both answer only on the machine
Postern runs on.

The deadline on the **Expires** row, and the four verbs that end a key, are on
[Agent keys](/reference/agent-keys).

## Default-deny

A new key can reach nothing until you tick a sector.

The sector list starts empty. A key with an empty list resolves, authenticates
and connects, and then every sector check denies it. A freshly installed Postern
answers on the agent port and grants nothing at all.

Postern does not treat that as an error, and the Console does not block it:
**Mint the key** needs a name and nothing else. The form says so three times.

| Where on the form        | What it reads                                                                             |
| ------------------------ | ----------------------------------------------------------------------------------------- |
| Under the sector toggles | `Nothing is granted until you pick it. Tap a sector to toggle it.`                        |
| Beside the recap         | `no sectors — this key is denied everything`                                              |
| Under **Mint the key**   | `No sectors granted yet — this key would be denied everything until you edit its grants.` |

That is the intended shape rather than a defect, and it is why a connection that
succeeds can still answer nothing.

<Warning>
  A key granted no sectors connects successfully and is denied on every read. The
  connection is fine and the answers are empty, so nothing in your agent looks
  wrong. `describe_context` gives it away: it comes back with `sectors: []`. To
  fix it, click **Agents & keys**, click that agent's row to open its own page,
  and look under **Grants**. No sector switched on is the fault. Switch one on
  there. Nothing about the agent's config changes.
</Warning>

Two behaviours of the stored list matter if you write one through the API.

* **A sector name Postern does not recognise is dropped, silently.** Postern
  intersects the stored list with the six real sectors when the key resolves. A
  grant of `finance` and `bogus` grants finance and raises nothing. The endpoint
  checks only that the field is an array of strings. If a sector you wrote does
  not appear on the key, check its spelling against the six above.
* **`*` expands to every sector.** A stored grant of `["*"]` resolves to all six
  at connection time. The Console cannot write one, because its toggles are built
  from the six named sectors. A wildcard grant can only arrive through
  `POST /api/agents`.

## What Postern cannot express

A grant row holds a user, an agent name, a hash of the key, an array of sectors,
a creation stamp, a revocation stamp and a deadline. Nothing else. So there is:

* no grant of one source inside a sector while another in the same sector is
  withheld;
* no per-object, per-field or per-row grant;
* no read-only grant — read and act travel together, and the only thing that
  limits action today is that five of the six sectors have no action catalogue
  behind them;
* no per-call approval, no confirmation tier, no rate limit, no budget, no
  time-of-day window;
* no per-sector deadline. One deadline covers the whole key;
* one grant per agent name, so two policies mean two agents.

The record is the compensating control for all of that. It tells you what an
agent did, after it did it. It never stops anything.

Per-user separation is real in the code. Every read is scoped to a user id, and a
key that belongs to one user cannot reach another's rows. But a self-hosted
Postern seeds a single operator user and treats its id as a constant. Multi-user
is future work, not shipped behaviour, and this page describes the single-user
shape you have.

## Confirm it works

* Console → **Agents & keys** lists the agent, and its line reads `never expires`
  or `expires in <n>d`.
* Click the agent. The toggles under **Grants** are lit for exactly the sectors
  you meant to grant.
* Ask the agent what it can see. It names those same sectors and no others.
* Ask the agent for something in a sector you did not grant. It is refused, and a
  row appears under **Recent passage** with `deny`.

## If something went wrong

| What you see                                                                                        | What to do                                                                                                               |
| --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| The agent connected, and every read comes back denied                                               | The key has no sectors. Click **Agents & keys**, click the agent, tap a sector under **Grants**, then restart the agent. |
| A sector is granted and every query comes back empty, or `no active connection for sector '<name>'` | Nothing is connected in that sector. Click **Sources** and connect one. This is not a permission problem.                |
| You asked an agent to act and it reports there is nothing to act on                                 | Only `home` has an action catalogue today. The other five sectors are read-only, and no grant changes that.              |
| The agent worked yesterday and is refused today, with nothing changed                               | The key met its deadline, not a grant. [Expiry](/reference/agent-keys#expiry) carries the fix.                           |

## What you have now

The six sectors, which sectors a sign-in turns on, what one grant covers, and
where every control that changes a grant lives in the Console. A grant covers
read and act, and only `home` has anything to act on today. Nothing is granted
until you tick it.

## Next

<Columns cols={2}>
  <Card title="Agent keys" href="/reference/agent-keys">
    the deadline on a key, and the four verbs that end one: revoke, rotate, edit
    and remove
  </Card>

  <Card title="The audit log" href="/reference/audit-log">
    what one row holds, what gets a row, and the honest limits of a record the
    database does not enforce
  </Card>

  <Card title="Connect an agent" href="/start/connect-an-agent">
    a running Postern and a key you created · a few minutes, plus a restart of
    whatever client you are connecting
  </Card>
</Columns>
