# Scopes

A scope names one kind of access. API keys and registered apps carry scope lists, and every endpoint that reads or
changes data needs one. A call without the scope gets 403 `insufficient_scope`; `detail` names the missing scope.

## Vocabulary

- `read:projects` / `write:projects`
- `read:products` / `write:products`
- `read:vendors` / `write:vendors`
- `read:labor` / `write:labor`
- `read:estimates` / `write:estimates`
- `read:assessments` / `write:assessments`
- `read:bids` / `write:bids`
- `read:quotes` / `write:quotes`
- `read:files` / `write:files`
- `read:conversations` / `write:conversations`
- `read:contacts` / `write:contacts`
- `read:webhooks` / `write:webhooks`
- `read:agents` / `write:agents`
- `read:calls` / `write:calls`
- `read:appointments` / `write:appointments`
- `read:pricebook` / `write:pricebook`
- `read:phone-numbers` / `write:phone-numbers`
- `read:campaigns` / `write:campaigns`
- `read:consent` / `write:consent`
- `read:integrations` / `write:integrations`
- `read:usage`: the company's AI usage (`GET /v1/usage`). An app reads only its own usage.
- `admin:org`: implies every other scope in the key's company. Use sparingly. An app can never ask for it.
- `*`: every scope, including scopes added later.

Only a key that works in its own company (**This company**) can hold `admin:org` or `*`. See
[Company scope](https://developers.exayard.com/apps-and-keys.md#company-scope).

Archiving a resource needs that resource's write scope: `write:projects` for projects and pages, `write:products`
for products and lists, `write:vendors`, `write:labor`. Disconnecting an integration needs `write:integrations`.
Widgets and custom properties are company setup: only an admin of the company, or a key with `admin:org`, can
create, change or remove them. An app is refused with 403 `forbidden` whatever its scopes; an admin can do it in
Exayard.

## API keys

Every key belongs to an app, and a key's scopes are a subset of its app's. When you make a key you pick **All** of the
app's scopes (the default) or **Specific** ones. A key can never hold a scope its app does not ask for. The one
exception is `admin:org` or `*` on a **This company** key, as above.

A call made with a key is checked against the key's own scopes and against what its app holds in that company. So
removing a scope from the app also takes it from every key of that app. See [Apps and keys](https://developers.exayard.com/apps-and-keys.md).

Older `ak_` keys hold exactly the scopes picked when they were made.

## Registered apps: scopes per install

An app lists the scopes it asks for when it is registered (any scope except `admin:org`). Each company that installs
it grants those scopes to its own install, and the app's calls in that company are checked against that grant. For an
app, the 403 `detail` says the install in this company does not grant the scope, and that an admin of the company can
approve it on the Developer page.

- **Adding scopes to your app** does not change existing installs. Each company keeps its current grant until an admin
  approves the new access on its Connected apps row. Your owner company gets the new scopes at once.
- **Removing scopes from your app** removes them from every install at once. A call already in flight under the old
  grant is retried once; if it relied on a removed scope, it gets `insufficient_scope`.
- Calls that name no company (`GET /v1/me`, `GET /v1/installations`, and the company calls on `/v1/organizations` in
  [Accounts for your customers](https://developers.exayard.com/concepts/customer-accounts.md)) hold no company scopes.

## Project-limited installs

When an admin installs an app, they choose **All projects** or **Only these projects** (1 to 500 of the company's
projects). They can change the choice later by installing again. With a project list:

- Listing projects returns only the listed ones.
- Reading, updating and archiving a listed project works, and so do its pages, files and symbols, and search within
  it. A project outside the list, or a file in no project, is refused, mostly as not found.
- Operations that act on the whole company, rather than on one project, are refused with 403 `forbidden`.

## Session tokens and personal connections

The Exayard web app's own session, and OAuth tokens from clients that are not registered apps (personal connections
such as ChatGPT, Claude or Cursor), are not checked against scopes. They act with the permissions of the person who
signed in, in that person's companies. See [Personal connections](https://developers.exayard.com/authentication.md#personal-connections).

## MCP

On the MCP server every tool needs a scope. A tool that mirrors a REST endpoint needs that endpoint's scope. A
registered app sees and can call only the tools its install grants. A **This company** key acts in its own company
without naming it. Any other app call names its company with the `Exayard-Organization-Id` header or an
`organizationId` argument. Tools that only a person may use are never available to apps.
