Permissions reference
Complete reference for the 26 workspace and 4 agency API permissions available in SenditCold.
SenditCold 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 | Bulk campaign operations |
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 (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 |
CONNECT_EMAILS | 24 | 16777216 | special | Connect mailboxes and create sign-in links (Partnership plan) |
MANAGE_REPORTS | 25 | 33554432 | write | Create, share and schedule client reports |
AGENCY_READ | 26 | 67108864 | agency | View the agency, its staff, clients, overview, inbox and brand |
AGENCY_MANAGE_CLIENTS | 27 | 134217728 | agency | Create, attach, cap and detach client workspaces |
AGENCY_READ_USAGE | 28 | 268435456 | agency | View the shared send pool and AI balance |
AGENCY_TRANSFER_AI | 29 | 536870912 | agency | Move AI balance from the agency into a client workspace |
Bits 26 to 29 exist only on agency API keys. A workspace key or OAuth app never holds them, and POST /api-keys refuses them as unknown bits.
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.
CONNECT_EMAILS is the only scope that lets a key hand SenditCold a mailbox password: it gates the mailbox connect routes, which take SMTP/IMAP credentials, mint Google and Microsoft sign-in links and save the workspace's own OAuth apps. Those routes also refuse a workspace that is not on the Partnership plan with 403 partnership_plan_required. It is left out of the Full access preset, and an OAuth app can never be granted it.
MANAGE_REPORTS covers creating and editing client reports, minting and revoking their public links and setting their email schedules. A report link shows analytics to anyone who holds it, so this is separate from READ_ANALYTICS, which is all that listing, reading and previewing a report takes.
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.
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, mailbox connect, and self-service key management. Grant individually.
Agency
The agency's own endpoints under /v1/agency/:agencyId. Only an agency API key carries these, and it carries them alongside the workspace bits it uses in each client workspace.
| Agency permission | Endpoints |
|---|---|
AGENCY_READ | GET /agency, GET /agency/plans, GET /agency/:agencyId, its staff, clients, overview and brand; with READ_UNIBOX also the cross-client inbox |
AGENCY_MANAGE_CLIENTS | Create, attach, update (send and AI caps) and detach clients |
AGENCY_READ_USAGE | GET /agency/:agencyId/usage and GET /agency/:agencyId/ai-balance |
AGENCY_TRANSFER_AI | POST /agency/:agencyId/clients/:orgId/ai-balance |
An agency key never holds API_KEYS, REALTIME_SUBSCRIBE or CONNECT_EMAILS: it manages no workspace keys, opens no realtime socket and takes no mailbox credentials.
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, CONNECT_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 |
MANAGE_REPORTS | view_analytics and manage_campaigns |
REALTIME_SUBSCRIBE | any member |
An agency key acts for the agency owner or admin who created it, through the membership the agency gives them in each client workspace (Admin), so the same table bounds it there. The agency bits need that person to still be an owner or admin of the agency.
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_KEYSandCONNECT_EMAILSare 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
view_campaigns,view_analyticsandaccess_uniboxfrom any role, andmanage_campaignsandsend_campaignsonly from a role that lets clients act. A key or grant acting for them is limited to the API permissions those cover, to the routes their scope allows, and to their grants (which routes);access_uniboxcovers inbox writes only through a role that lets clients act. A key's own mailbox allowlist still applies on top. Restricted members cannot create keys.
Preset combinations
Both presets are returned by GET /api-keys/permissions for convenience. 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.
Read only (688159)
All read permissions combined:
READ_EMAILS | READ_CAMPAIGNS | READ_CONTACTS | READ_UNIBOX | READ_ANALYTICS
| READ_TEMPLATES | READ_CRM | READ_AUDIT_LOGS
= 1 | 2 | 4 | 8 | 16 | 32768 | 131072 | 524288
= 688159Full access (50331647)
Every workspace permission except CONNECT_EMAILS, which is granted by hand:
((1 << 26) - 1) & ~16777216 = 50331647Agency key presets
GET /agency/:agencyId/api-keys/permissions returns the bits an agency key may hold, grouped as workspace and agency, with two presets:
- Read only (336232479): the workspace read set plus
AGENCY_READandAGENCY_READ_USAGE,688159 | 67108864 | 268435456. - Full access (1056954367): every bit an agency key may hold, which is every workspace bit except
API_KEYS,REALTIME_SUBSCRIBEandCONNECT_EMAILS(50321407) plus the four agency bits (1006632960).
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 33554431. POST /agency/:agencyId/api-keys does the same against 536860671 and answers 400 invalid_agency_key_permissions.
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,
CONNECT_EMAILS: 16777216,
MANAGE_REPORTS: 33554432,
// Agency API keys only
AGENCY_READ: 67108864,
AGENCY_MANAGE_CLIENTS: 134217728,
AGENCY_READ_USAGE: 268435456,
AGENCY_TRANSFER_AI: 536870912,
} 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
CONNECT_EMAILS = 16777216
MANAGE_REPORTS = 33554432
# Agency API keys only
AGENCY_READ = 67108864
AGENCY_MANAGE_CLIENTS = 134217728
AGENCY_READ_USAGE = 268435456
AGENCY_TRANSFER_AI = 536870912const (
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
APIPermConnectEmails // 16777216
APIPermManageReports // 33554432
// Agency API keys only
APIPermAgencyRead // 67108864
APIPermAgencyManageClients // 134217728
APIPermAgencyReadUsage // 268435456
APIPermAgencyTransferAI // 536870912
)