Skip to main content
Every Personal Access Token (PAT) carries a set of scopes — the list of actions that token can perform against the public API. You pick scopes at creation time, and they’re immutable afterward — to change them, mint a new token and revoke the old one.

How they’re validated

On every request, the auth guard loads the token and puts its scopes on the request context. Each endpoint declares the scope it needs with @RequireScopes('messages:send'). If the scope is missing, the response is 403 Forbidden with code: INSUFFICIENT_SCOPE.
Unlike auth errors (which are intentionally generic), authorization errors spell out which scope is missing — because you already authenticated successfully; you just lack permission.

Catalog

The catalog lives in the api_scopes MongoDB collection. The portal pulls it via GET /api/v1/api-scopes and builds the create-token modal dynamically, so adding a new scope doesn’t require a frontend deploy.
The checkboxes in the Create API Key modal load live from the backend. A missing scope means it’s flagged is_active: false in the database.

The MCP server uses the same scopes

The MCP server does not have a scope system of its own. Each of its tools requires exactly the scope its REST counterpart does — keebai_crm_ticket_create needs crm:tickets:write, keebai_contacts_list needs contacts:read, and so on. The tool catalogue lists the mapping. One difference matters when you hand a token to an agent: a missing scope is not a 403. Every tool stays visible in tools/list regardless of your scopes, and the call comes back as a tool error carrying INSUFFICIENT_SCOPE. An agent will happily try something it cannot do and discover the limit afterwards, so pick the scope set deliberately rather than relying on the agent not to reach for a tool.

Permission groups

When an app connects to the MCP server over OAuth — Claude, ChatGPT, Cursor — the user does not tick scopes one by one. The consent screen shows six groups. The token ends up with the scopes the app asked for that belong to a group the user approved and that the user’s role allows. Each scope’s group is stored with it in the api_scopes catalog, as consent_group. It is not derived from the scope’s name: contacts: and loyalty: sit with crm: in Customers, restaurants:menu:read sits with the catalog reads in Products and sales, and ecommerce: splits by action — reads go to Products and sales, anything that writes or issues goes to Sell and invoice — so a user can let an app look at sales without letting it create them. cli:device is in no group. It is an internal marker that Keebai puts on tokens issued to the CLI, not a permission an app can ask for. Inactive scopes are in no group either. Rules that apply on the consent screen:
  • Your role limits the groups. A group is available only if your Keebai role has the permission behind at least one of the scopes the app asked for in it. Inside an approved group, scopes your role does not allow are left out. The company owner and superusers can approve every group.
  • One company per connection. The user picks the company before approving. To use another company, connect again.
  • Reconnecting replaces. A second approval from the same user, for the same app and company, replaces the scopes of the first one.
  • Disconnecting stops the connection within 15 minutes. Under Developers › Connected apps in the portal, the company owner and superusers see every connection in the company and everyone else sees their own. Disconnecting revokes the refresh token at once; the app’s current access token is not revoked and expires on its own, at most 15 minutes later.
  • Losing access ends the connection. If the user is deactivated or loses access to the company, the app’s next refresh fails and it has to sign in again. A change of role alone does not change the scopes already granted; disconnect and connect again to apply it.
Personal Access Tokens do not use groups. You still pick single scopes when you create a token.

Scopes are independent from role permissions

Scopes are independent from the role permissions of the user who creates the token. The backend validates what scopes you’re allowed to assign at creation time (each scope must exist and be active in api_scopes, and a few are tied to role permissions — see below), but once minted, the token uses its assigned scopes regardless of later changes to your role. That means:
  1. To create a token you need developer.manage_tokens on your role. Without it you don’t even see the API Keys screen.
  2. Once created, a token’s scopes are fixed. If your role is later trimmed, the token keeps working with its original scopes. To revoke access, revoke the key explicitly.
  3. To use new scopes, mint a new token. You can’t add scopes to an existing key.
Two ecommerce scopes require the permission in your role. Minting a token with ecommerce:stock:write requires the permissions to adjust and transfer stock in your Keebai role, and ecommerce:invoicing:issue requires the permission to issue DTE. The company owner and superusers are exempt. On every call the API passes the ecommerce service the permissions that match the token’s scopes, so what the token can do is what the role allowed when it was minted.
Because scopes survive role changes, audit the API Keys table periodically and revoke tokens belonging to people who shouldn’t have access anymore. The Last used column helps find forgotten keys.

Least privilege by example

Pick the smallest scope set that does the job. Splitting messages:send and messages:bulk exists exactly for this — bulk is higher-risk (Meta cost, sender reputation, abuse potential), so isolate it in a key the marketing tool owns, not the transactional backend.

Transactional backend (1:1 notifications)

Only needs to send single messages.messages:send.

Marketing tool / outbound CRM

Needs to launch campaigns and check status.messages:bulk, templates:read.

Template viewer

Read-only browser of the approved catalog.templates:read.

Template provisioning

Creates and maintains the company’s templates without using the portal.templates:read, templates:create, templates:update.

Full-stack integration (internal)

The whole combo, when it’s under your team’s direct control.messages:send, messages:bulk, templates:read, templates:create, templates:update.

CLI-driven provisioning

Automated onboarding: connects channels, registers webhooks, verifies.channels:read, channels:connect, webhooks:read, webhooks:manage.

Webhook-based integration

Your backend receives realtime events and inspects delivery history.webhooks:read, webhooks:manage, channels:read.

Knowledge-base sync

Your CMS or docs pipeline publishes documents and assigns them to agents automatically.knowledge:read, knowledge:write, knowledge:delete, knowledge:assign, agents:read.

External RAG / search

An external app queries your knowledge base as retrieval for its own LLM.knowledge:read, knowledge:query.

Scheduling — read-only catalog and availability

Your public website or client app shows branches, services, professionals, and available times — no booking.scheduling:branches:read, scheduling:services:read, scheduling:professionals:read, scheduling:availability:read.

Scheduling — full booking flow

Your integration creates, confirms, and cancels appointments end-to-end.scheduling:branches:read, scheduling:services:read, scheduling:professionals:read, scheduling:availability:read, scheduling:appointments:read, scheduling:appointments:create, scheduling:appointments:confirm, scheduling:appointments:cancel.

CRM — reporting and dashboards

A BI tool or dashboard reads the pipeline without ever writing to it.crm:customers:read, crm:tickets:read, crm:tasks:read, crm:notes:read.

CRM — two-way sync, no deletes

Your external CRM or ERP keeps records in step with Keebai. Deletes stay out so a sync bug cannot erase history.crm:customers:read, crm:customers:write, crm:tickets:read, crm:tickets:write, crm:tasks:read, crm:tasks:write, crm:notes:read, crm:notes:write.

CRM — activity logger

A dialer or email tool writes call and email records onto the right ticket, and nothing else.crm:tickets:read, crm:notes:write.

Storefront pricing widget

Your website shows live prices and stock. It never touches customer data.ecommerce:products:read.

Abandoned-cart recovery

Finds pending carts and messages the customer with the recovery link.ecommerce:carts:read, contacts:read, messages:send.

Sales reporting

A BI job pulls closed sales nightly. Read-only, no catalog, no carts.ecommerce:sales:read.

Point of sale

Your POS looks up products by barcode, records sales with their boleta or factura, and keeps stock in step.ecommerce:products:read, ecommerce:stock:read, ecommerce:sales:write, ecommerce:invoicing:read, ecommerce:invoicing:issue.

Warehouse and stock sync

Your ERP or warehouse system adjusts and transfers stock and audits the movements. It never creates sales or touches invoicing.ecommerce:products:read, ecommerce:stock:read, ecommerce:stock:write.

Email campaigns

Your marketing tool sends emails to a list from the company sender and tracks opens.messages:bulk, templates:read, channels:read.

Loyalty terminal

Your point of sale enrolls customers, accrues on purchase, and hands out the wallet card.loyalty:read, loyalty:write.

Loyalty balance display

A kiosk or a customer-facing screen shows the balance and nothing more. Cannot move points or push notifications.loyalty:read.

Agent provisioning

Your onboarding flow creates an agent per client and writes its prompt blocks from a template you keep in git.agents:read, agents:write.

Prompt auditor

A reviewer reads every agent’s blocks and their history to check what the bots are told to say. Cannot change a word, and webhook secrets stay masked.agents:read.

Contact sync

Your CRM or ERP keeps the contact directory in step with Keebai. Deletes stay out so a sync bug cannot erase people.contacts:read, contacts:write.

AI agent over MCP

An agent that answers with your knowledge base, replies on WhatsApp, and logs what it did — without being able to destroy anything.knowledge:query, contacts:read, messages:send, crm:tickets:read, crm:tickets:write, crm:notes:write.
CRM reads are company-wide. A crm:*:read scope returns records from every project in your company, not just the project the token was minted for. Writes stay scoped to the token’s project. See the CRM overview before handing a CRM token to a third party.

Change a token’s scopes

You can’t edit scopes on an existing token. The pattern is:
1

Mint a new token with the right scopes

From API Keys, click Create API Key and pick the new scopes.
2

Update your integration

Swap the old token for the new one in your secret manager.
3

Revoke the old

Once the integration is happily on the new token, revoke the old one.
This pattern gives you zero-downtime rotation.