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.
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 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 | 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 and /ai/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 and /ai/tools; every other tool there checks its own scope. It also gates the 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 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 that adds its leads to a campaign which is already sending takes it too. Over the agent tools, 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 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, 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 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, 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 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
Read
Read-only data access. Safe for monitoring, reporting, BI sync.
Write
Resource mutation, but no campaign starts. A key with the full write set can edit drafts but cannot turn on a campaign.
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
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
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 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
403api_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
403api_key_permissions_exceed_calleranswers 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_KEYSis never granted to an OAuth app, and OAuth tokens cannot create, edit, revoke or delete API keys (403oauth_token_not_allowed). - A member restricted to selected resources holds only
view_campaigns,view_analyticsandaccess_uniboxfrom 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). A key's own mailbox allowlist still applies on top. Restricted members cannot create keys.
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)
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
= 17465375Agent: 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
= 84574239A 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)
All 27 permissions:
(1 << 27) - 1 = 134217727Agent sign-up key (17793791)
Not a preset you can pick: it is the key POST /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
= 17793791Some 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, 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
Combining
const READ_EMAILS = 1;
const READ_CAMPAIGNS = 2;
const SEND_CAMPAIGNS = 16384;
const permissions = READ_EMAILS | READ_CAMPAIGNS | SEND_CAMPAIGNS;
// 16387Checking
function hasPermission(permissions, required) {
return (permissions & required) === required;
}
hasPermission(16387, 16384); // true: SEND_CAMPAIGNS granted
hasPermission(16387, 64); // false: WRITE_CAMPAIGNS missingRejecting 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
Monitoring integration
For dashboards and reporting:
const MONITORING =
READ_EMAILS | // 1
READ_CAMPAIGNS | // 2
READ_ANALYTICS; // 16
// 19CRM sync
const CRM_SYNC =
READ_CONTACTS | // 4
WRITE_CONTACTS | // 128
BULK_CONTACTS | // 512
READ_CRM | // 131072
WRITE_CRM; // 262144
// 393860Campaign automation (drafts only, no send)
const CAMPAIGN_DRAFTS =
READ_CAMPAIGNS | // 2
WRITE_CAMPAIGNS | // 64
READ_CONTACTS | // 4
READ_TEMPLATES | // 32768
WRITE_TEMPLATES; // 65536
// 98374Campaign automation (with send)
Same as above, plus SEND_CAMPAIGNS (16384). Grant only when the integration genuinely needs to start campaigns.
Permission constants
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;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 = 67108864const (
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
)See also
OAuth
Let third-party apps act for an AY Inbox workspace with OAuth 2.1: the authorization-code flow with PKCE, scopes, consent, tokens and client registration.
Endpoint scope map
Every AY Inbox API endpoint with the auth types it accepts and the permission it requires, so you know exactly what an API key or OAuth token can reach.