# Permissions reference

> Every AY Inbox API permission scope and what it allows, the presets such as Agent: read and draft, and the bitmask constants for building a scoped API key.

Source: https://docs.ayinbox.com/api/permissions/

AY Inbox uses a bitmask system for API permissions. Each permission is one bit in a `uint64`, so a single integer can express any combination of the permissions below.

These same permissions are the [OAuth](/api/oauth/) scopes, lowercased: `READ_EMAILS` is the scope `read_emails`. An OAuth access token carries a bitmask of granted permissions and is checked through the identical gates as an API key.

Permission values [#permission-values]

| Permission           | Bit | Value    | Category | Description                                                                                            |
| -------------------- | --- | -------- | -------- | ------------------------------------------------------------------------------------------------------ |
| `READ_EMAILS`        | 0   | 1        | read     | View email accounts and settings                                                                       |
| `READ_CAMPAIGNS`     | 1   | 2        | read     | View campaigns and sequences                                                                           |
| `READ_CONTACTS`      | 2   | 4        | read     | View contact lists, segments, notes, and activities                                                    |
| `READ_UNIBOX`        | 3   | 8        | read     | Access unified inbox                                                                                   |
| `READ_ANALYTICS`     | 4   | 16       | read     | View analytics and statistics                                                                          |
| `WRITE_EMAILS`       | 5   | 32       | write    | Modify email account settings                                                                          |
| `WRITE_CAMPAIGNS`    | 6   | 64       | write    | Create and modify campaigns and sequences                                                              |
| `WRITE_CONTACTS`     | 7   | 128      | write    | Create and modify contacts, segments, notes, activities                                                |
| `WRITE_UNIBOX`       | 8   | 256      | write    | Mark emails as read/unread and send replies                                                            |
| `BULK_CONTACTS`      | 9   | 512      | bulk     | Bulk import/export/delete contacts                                                                     |
| `BULK_CAMPAIGNS`     | 10  | 1024     | bulk     | Reserved for bulk campaign operations; no route checks it today                                        |
| `REALTIME_SUBSCRIBE` | 11  | 2048     | special  | Subscribe to realtime events                                                                           |
| `WEBHOOKS`           | 12  | 4096     | special  | Manage webhook endpoints                                                                               |
| `API_KEYS`           | 13  | 8192     | special  | Create and manage API keys                                                                             |
| `SEND_CAMPAIGNS`     | 14  | 16384    | write    | Start and stop campaigns, and approve, edit or reject first emails in a review queue (sends real mail) |
| `READ_TEMPLATES`     | 15  | 32768    | read     | View reply templates                                                                                   |
| `WRITE_TEMPLATES`    | 16  | 65536    | write    | Create and modify reply templates                                                                      |
| `READ_CRM`           | 17  | 131072   | read     | View pipelines, deals, and CRM tasks                                                                   |
| `WRITE_CRM`          | 18  | 262144   | write    | Create and modify pipelines, deals, CRM tasks                                                          |
| `READ_AUDIT_LOGS`    | 19  | 524288   | read     | View organization audit logs                                                                           |
| `INTEGRATIONS`       | 20  | 1048576  | special  | Connect and manage third-party integrations                                                            |
| `WARMUP_ROUTING`     | 21  | 2097152  | special  | Manage warmup routing rules                                                                            |
| `AI_AGENT`           | 22  | 4194304  | special  | Run the AI assistant and MCP tools                                                                     |
| `AI_RESEARCH`        | 23  | 8388608  | special  | Run AI contact research                                                                                |
| `READ_WORKSPACE`     | 24  | 16777216 | read     | View the workspace profile, plan, limits, labels and credit balance                                    |
| `WRITE_WORKSPACE`    | 25  | 33554432 | write    | Update the workspace profile and what the AI is grounded on                                            |
| `AI_DRAFT`           | 26  | 67108864 | special  | Have the AI write reply drafts, charged to credits and never sent                                      |

`READ_WORKSPACE` covers `GET /organization/current`, its limits, the plan and trial at `/subscription`, and the label lists. The credit balance, usage, transactions and spend settings under `/subscription/credits` also need the member behind the credential to hold `manage_billing`. `WRITE_WORKSPACE` covers `PATCH /organization/current`, which also sets the product description, ideal customer notes and voice that every AI writing surface is grounded on, so it needs `manage_settings` and is never granted to a self-registered MCP client.

`AI_DRAFT` means "have the AI write a draft, charged to credits, never sent". It gates the `generate_reply_draft` tool on [`/v1/mcp`](/api/mcp/) and [`/ai/tools`](/api/agent-tools/), and nothing else. It is separate from `WRITE_UNIBOX`, which also sends replies, and from `AI_AGENT`, which also writes playbooks, so a key that drafts is never a key that sends.

`AI_AGENT` gates the web and playbook tools (`search_web`, `fetch_url`, `load_skill`) on [`/v1/mcp`](/api/mcp/) and [`/ai/tools`](/api/agent-tools/); every other tool there checks its own scope. It also gates the [AI skills](/api/endpoints/#ai-skills) endpoints for keys; creating, editing or deleting a playbook with a key further needs the member who created the key to hold `manage_settings`.

`SEND_CAMPAIGNS` is intentionally separate from `WRITE_CAMPAIGNS`: editing a campaign draft and starting one that actually transmits mail are different blast radii, so a key can be granted the first without the second. An [inbox placement test](/guides/placement-tests/) sends real mail from a real mailbox too, so starting or cancelling one, and scheduling a campaign's placement monitor, takes `SEND_CAMPAIGNS`, while reading results takes `READ_ANALYTICS`. A [Looot lead job](/guides/looot-leads/) that adds its leads to a campaign which is already sending takes it too. Over the [agent tools](/api/mcp/#what-is-exposed), linking a segment to a sending campaign, and widening a segment that already feeds one, take it as well. Contact writes do not: a contact that comes to match a segment linked to a sending campaign is enrolled, on REST and over the tools alike, so linking the segment is the decision that needs `SEND_CAMPAIGNS`. Approving a first email in a campaign's [review queue](/guides/campaigns/#review-first-emails-before-they-send) lets that email go out, so `approve`, `edit`, `reject` and `bulk-approve` take `SEND_CAMPAIGNS` (for a member, `send_campaigns`), and so does turning `review_first_emails` off on a campaign that has it on, which releases every waiting email. Listing the queue takes `READ_CAMPAIGNS`. Turning on [auto-reply](/guides/auto-reply/), for the workspace or for a campaign, and changing what a campaign on automatic may send, take `SEND_CAMPAIGNS` (for a member, `send_campaigns` and a recent sign-in confirmation); `WRITE_CAMPAIGNS` can edit a campaign's auto-reply settings only while it is off or Supervised, and can lower an automatic campaign to Supervised or off. Every queue call also takes `READ_CONTACTS` (for a member, `view_contacts`), because each message shows its recipient's address, name and personalized text. Recording a contact's [prior consent](/guides/contacts-crm/#prior-consent) with `PUT /contacts/:id/consent` takes `WRITE_CONTACTS`, and the workspace's consent country list at `/outreach/consent` takes `WRITE_CAMPAIGNS` with `manage_settings`, like the other outreach settings.

Some scopes can spend a balance. Starting a [Looot lead job](/guides/looot-leads/), through its endpoint or the `find_leads` tool, takes `INTEGRATIONS` and `BULK_CONTACTS` and spends the instance's Looot balance. The `find_leads` tool also needs `WRITE_CAMPAIGNS` (and `manage_campaigns` for the member) when it names a campaign, the same as `add_segment_to_campaign`. A campaign that is sending (active, finished, or parked for undeliverable leads) mails what it gets, so naming one, through the endpoint or the tool, also needs `SEND_CAMPAIGNS` (and `send_campaigns` for the member), which a self-registered MCP client is never granted. The `verify_email` tool takes `WRITE_CONTACTS`, because a paid verifier charges for each check. Starting a [first-line job](/guides/ai-contact-research/#write-first-lines-in-bulk) with `POST /contacts/first-lines` takes `AI_RESEARCH` and `BULK_CONTACTS`, spends the instance's Looot balance and charges the workspace's AI credits. The `generate_reply_draft` tool takes `READ_UNIBOX` and `AI_DRAFT` and charges the workspace's AI credits, like the dashboard reply draft. A self-registered MCP client can be granted all of these except `SEND_CAMPAIGNS`, so approve them only for a client you trust to spend.

A new route or tool that spends takes a scope that is not read-only. Three published routes are older than that rule and keep their read scopes: `POST /unibox/reply/draft` and `POST /unibox/compose/draft` (`READ_UNIBOX`) and `POST /emails/verify` (`READ_EMAILS`).

Categories [#categories]

Read [#read]

Read-only data access. Safe for monitoring, reporting, BI sync.

Write [#write]

Resource mutation, but no campaign starts. A key with the full write set can edit drafts but cannot turn on a campaign.

Bulk [#bulk]

High-volume operations. These can touch large numbers of rows in a single request, so they're broken out from the per-record write permissions.

Special [#special]

Realtime subscriptions, webhooks, integrations, warmup routing, AI assistant / MCP access, AI contact research, AI reply drafts, and self-service key management. Grant individually.

What a role can delegate [#what-a-role-can-delegate]

An API key or an OAuth grant acts for a member of the workspace, so it never holds a permission that member's role does not cover. Each API permission maps to the [team role permission](/guides/team-roles/) it needs; the workspace owner covers every one.

| API permission                                         | Member needs                 |
| ------------------------------------------------------ | ---------------------------- |
| `READ_EMAILS`, `READ_CAMPAIGNS`, `READ_TEMPLATES`      | `view_campaigns`             |
| `READ_CONTACTS`, `READ_CRM`                            | `view_contacts`              |
| `READ_UNIBOX`, `WRITE_UNIBOX`                          | `access_unibox`              |
| `READ_ANALYTICS`, `READ_AUDIT_LOGS`                    | `view_analytics`             |
| `WRITE_EMAILS`                                         | `manage_emails`              |
| `WRITE_CAMPAIGNS`, `WRITE_TEMPLATES`, `BULK_CAMPAIGNS` | `manage_campaigns`           |
| `WRITE_CONTACTS`, `WRITE_CRM`, `BULK_CONTACTS`         | `manage_contacts`            |
| `SEND_CAMPAIGNS`                                       | `send_campaigns`             |
| `WEBHOOKS`, `WARMUP_ROUTING`                           | `manage_settings`            |
| `API_KEYS`                                             | `manage_api_keys`            |
| `INTEGRATIONS`                                         | `use_integrations`           |
| `AI_AGENT`                                             | `use_ai`                     |
| `AI_RESEARCH`                                          | `view_contacts` and `use_ai` |
| `WRITE_WORKSPACE`                                      | `manage_settings`            |
| `AI_DRAFT`                                             | `access_unibox` and `use_ai` |
| `REALTIME_SUBSCRIBE`, `READ_WORKSPACE`                 | any member                   |

Where it applies:

* **Creating or editing an API key** from the dashboard refuses a permission outside the member's role with `403` `api_key_permissions_exceed_caller`.
* **Creating or editing an API key with an API key** keeps the result inside the calling key: no permission it lacks, and when the calling key is limited to some mailboxes or IP addresses, the new or edited key must be limited to a subset of them. The same `403` `api_key_permissions_exceed_caller` answers otherwise.
* **An OAuth grant** carries only the requested scopes the approving member's role covers, re-checked against the member's current role on every request. See [OAuth scopes](/api/oauth/#scopes). `API_KEYS` is never granted to an OAuth app, and OAuth tokens cannot create, edit, revoke or delete API keys (`403` `oauth_token_not_allowed`).
* **A member restricted to [selected resources](/guides/team-roles/#access-scope)** holds only `view_campaigns`, `view_analytics` and `access_unibox` from their roles, so a key or grant acting for them is limited to the read permissions those cover, and to the read routes and grants their scope allows ([which routes](/api/endpoints/)). A key's own mailbox allowlist still applies on top. Restricted members cannot create keys.

Preset combinations [#preset-combinations]

The three presets are returned by `GET /api-keys/permissions` under `presets` (`read_only`, `agent_draft`, `full_access`). The same response carries `grantable`, the mask the caller may put on a key (its role's reach, or the calling key's own permissions), and `app_scopes`, the mask an OAuth app may request. Existing keys keep exactly the bits they were created with: a key made before a permission existed does not gain it.

Read only (17465375) [#read-only-17465375]

All read permissions combined:

```
READ_EMAILS | READ_CAMPAIGNS | READ_CONTACTS | READ_UNIBOX | READ_ANALYTICS
  | READ_TEMPLATES | READ_CRM | READ_AUDIT_LOGS | READ_WORKSPACE
= 1 | 2 | 4 | 8 | 16 | 32768 | 131072 | 524288 | 16777216
= 17465375
```

Agent: read and draft (84574239) [#agent-read-and-draft-84574239]

Every read permission plus `AI_DRAFT`. This is the key to give a model that reads mail written by people outside the workspace: it can read and have the AI write drafts, charged to credits within the workspace's spend limits, and it holds no permission that writes or sends.

```
READ_ONLY | AI_DRAFT
= 17465375 | 67108864
= 84574239
```

A few published routes act on read permissions, and this preset reaches them: advisor refresh, snooze, dismiss and feedback on `READ_ANALYTICS`, and the reply and compose draft routes and `POST /emails/verify`, which spend credits.

Full access (134217727) [#full-access-134217727]

All 27 permissions:

```
(1 << 27) - 1 = 134217727
```

Agent sign-up key (17793791) [#agent-sign-up-key-17793791]

Not a preset you can pick: it is the key [`POST /agents/register`](/api/for-agents/#register) mints for an AI agent that opened its own workspace. Every read permission plus the writes that set up a workspace, and nothing that sends mail, holds an AI scope (`AI_DRAFT`, `AI_AGENT`, `AI_RESEARCH`), or manages keys, webhooks or integrations.

```
READ_ONLY | WRITE_EMAILS | WRITE_CAMPAIGNS | WRITE_CONTACTS | BULK_CONTACTS
  | WRITE_TEMPLATES | WRITE_CRM
= 17465375 | 32 | 64 | 128 | 512 | 65536 | 262144
= 17793791
```

Some REST routes write with AI under the scopes it holds: like the read presets, the reply and compose draft routes, `POST /templates/analyze` under `WRITE_TEMPLATES`, and under `WRITE_CAMPAIGNS` the writing routes under `/generation` and a brain's `sample-reply` and `organize`. Until a person claims the workspace every one of them answers `403 agent_workspace_unclaimed`, on any instance and whatever its billing, as does every route and tool that runs AI or a paid lookup. So do `POST /emails/verify`, adding or refreshing a web page as brain material, and publishing a form or setting a custom forms domain under `WRITE_CONTACTS`; a campaign tracking domain stays unverified, and contact import previews place columns without a model. After the claim they are charged to the workspace's AI credits and plan like anyone else's. When a person [claims the workspace](/api/for-agents/#how-a-person-takes-over), with a code the agent redeems through `POST /agents/claim` (open to the agent's own key, no scope needed), every key the agent held ends.

Working with bitmasks [#working-with-bitmasks]

Combining [#combining]

```javascript
const READ_EMAILS = 1;
const READ_CAMPAIGNS = 2;
const SEND_CAMPAIGNS = 16384;

const permissions = READ_EMAILS | READ_CAMPAIGNS | SEND_CAMPAIGNS;
// 16387
```

Checking [#checking]

```javascript
function hasPermission(permissions, required) {
  return (permissions & required) === required;
}

hasPermission(16387, 16384); // true: SEND_CAMPAIGNS granted
hasPermission(16387, 64);    // false: WRITE_CAMPAIGNS missing
```

Rejecting unknown bits [#rejecting-unknown-bits]

`POST /api-keys` rejects any request whose `permissions` field has bits outside the known set, so a stale client can't accidentally request a future permission. The current mask of valid bits is `134217727`.

Common permission sets [#common-permission-sets]

Monitoring integration [#monitoring-integration]

For dashboards and reporting:

```javascript
const MONITORING =
  READ_EMAILS |     // 1
  READ_CAMPAIGNS |  // 2
  READ_ANALYTICS;   // 16
// 19
```

CRM sync [#crm-sync]

```javascript
const CRM_SYNC =
  READ_CONTACTS |   // 4
  WRITE_CONTACTS |  // 128
  BULK_CONTACTS |   // 512
  READ_CRM |        // 131072
  WRITE_CRM;        // 262144
// 393860
```

Campaign automation (drafts only, no send) [#campaign-automation-drafts-only-no-send]

```javascript
const CAMPAIGN_DRAFTS =
  READ_CAMPAIGNS |    // 2
  WRITE_CAMPAIGNS |   // 64
  READ_CONTACTS |     // 4
  READ_TEMPLATES |    // 32768
  WRITE_TEMPLATES;    // 65536
// 98374
```

Campaign automation (with send) [#campaign-automation-with-send]

Same as above, plus `SEND_CAMPAIGNS` (16384). Grant only when the integration genuinely needs to start campaigns.

Permission constants [#permission-constants]

<Tabs defaultValue="javascript">
  <TabsList>
    <LangTab lang="javascript" />

    <LangTab lang="python" />

    <LangTab lang="go" />
  </TabsList>

  <Tab value="javascript">
    ```typescript
    export const Permissions = {
      READ_EMAILS: 1,
      READ_CAMPAIGNS: 2,
      READ_CONTACTS: 4,
      READ_UNIBOX: 8,
      READ_ANALYTICS: 16,
      WRITE_EMAILS: 32,
      WRITE_CAMPAIGNS: 64,
      WRITE_CONTACTS: 128,
      WRITE_UNIBOX: 256,
      BULK_CONTACTS: 512,
      BULK_CAMPAIGNS: 1024,
      REALTIME_SUBSCRIBE: 2048,
      WEBHOOKS: 4096,
      API_KEYS: 8192,
      SEND_CAMPAIGNS: 16384,
      READ_TEMPLATES: 32768,
      WRITE_TEMPLATES: 65536,
      READ_CRM: 131072,
      WRITE_CRM: 262144,
      READ_AUDIT_LOGS: 524288,
      INTEGRATIONS: 1048576,
      WARMUP_ROUTING: 2097152,
      AI_AGENT: 4194304,
      AI_RESEARCH: 8388608,
      READ_WORKSPACE: 16777216,
      WRITE_WORKSPACE: 33554432,
      AI_DRAFT: 67108864,
    } as const;
    ```
  </Tab>

  <Tab value="python">
    ```python
    class Permissions:
        READ_EMAILS = 1
        READ_CAMPAIGNS = 2
        READ_CONTACTS = 4
        READ_UNIBOX = 8
        READ_ANALYTICS = 16
        WRITE_EMAILS = 32
        WRITE_CAMPAIGNS = 64
        WRITE_CONTACTS = 128
        WRITE_UNIBOX = 256
        BULK_CONTACTS = 512
        BULK_CAMPAIGNS = 1024
        REALTIME_SUBSCRIBE = 2048
        WEBHOOKS = 4096
        API_KEYS = 8192
        SEND_CAMPAIGNS = 16384
        READ_TEMPLATES = 32768
        WRITE_TEMPLATES = 65536
        READ_CRM = 131072
        WRITE_CRM = 262144
        READ_AUDIT_LOGS = 524288
        INTEGRATIONS = 1048576
        WARMUP_ROUTING = 2097152
        AI_AGENT = 4194304
        AI_RESEARCH = 8388608
        READ_WORKSPACE = 16777216
        WRITE_WORKSPACE = 33554432
        AI_DRAFT = 67108864
    ```
  </Tab>

  <Tab value="go">
    ```go
    const (
        APIPermReadEmails        uint64 = 1 << iota // 1
        APIPermReadCampaigns                        // 2
        APIPermReadContacts                         // 4
        APIPermReadUnibox                           // 8
        APIPermReadAnalytics                        // 16
        APIPermWriteEmails                          // 32
        APIPermWriteCampaigns                       // 64
        APIPermWriteContacts                        // 128
        APIPermWriteUnibox                          // 256
        APIPermBulkContacts                         // 512
        APIPermBulkCampaigns                        // 1024
        APIPermRealtimeSubscribe                    // 2048
        APIPermWebhooks                             // 4096
        APIPermAPIKeys                              // 8192
        APIPermSendCampaigns                        // 16384
        APIPermReadTemplates                        // 32768
        APIPermWriteTemplates                       // 65536
        APIPermReadCRM                              // 131072
        APIPermWriteCRM                             // 262144
        APIPermReadAuditLogs                        // 524288
        APIPermIntegrations                         // 1048576
        APIPermWarmupRouting                        // 2097152
        APIPermAIAgent                              // 4194304
        APIPermAIResearch                           // 8388608
        APIPermReadWorkspace                        // 16777216
        APIPermWriteWorkspace                       // 33554432
        APIPermAIDraft                              // 67108864
    )
    ```
  </Tab>
</Tabs>

See also [#see-also]

* [Authentication](/api/authentication/)
* [Endpoints](/api/endpoints/)
* [Team & roles](/guides/team-roles/)
