The three permission systems
CognitionDesk has three permission systems, and they don't substitute for each other. Mixing them up is the most common support question — this page exists so a 403 sends you to the right one.
Giving an API key inventory:write does not give the ASSISTANT any new tool. Raising a contact to staff does not grant the assistant a capability it didn't already have. Each system answers a different question.
The ASSISTANT
Agent capabilities (grantedScopes)
What the assistant may do inside a chat — its owner grants a set of scopes, and a tool only appears if its scope is in that set.
The PROGRAM
API key scopes
What a program from outside (your code, an MCP client, the dashboard) may call on /platform-api. Your 403 is almost always this one.
The PERSON
Audience rungs
What each person talking to the assistant gets to see: guest, contact, staff, owner. Below the rung, a tool doesn't exist for that person this turn.
Side by side, by domain
Generated from the live catalog — a domain that only exists on one side is real, not a gap in this table.
| Domain | Agent capability | API key scope | In the default key |
|---|---|---|---|
| Appointments sensitive | read, write | read, write | — |
| Agent Workspace (Notes & Files) | read, write | — | — |
| API Keys sensitive | read, write | manage | — |
| Assistant files sensitive | — | read, write | — |
| Assistant setup wizard | — | read, write | yes |
| Assistants (Agent Builder) | read, write | read, write | yes |
| Audience (quién le habla al agente) sensitive | read, write | read, write | — |
| Email sensitive | read, write, send | read, write | — |
| Channels (widgets web) sensitive | read, write | read, write | — |
| Receivables (Cobros) sensitive | read, write | read, write | — |
| Usage & Plans | read | read, write | yes |
| Contacts (CRM) | read, write | — | — |
| Handoff (especialistas para derivar) sensitive | read, write | read, write | — |
| Escalate to the Workspace | write | — | — |
| Custom Tools sensitive | read, write | read, write | — |
| Image Generation & Assets sensitive | read, write | write | — |
| Inventory & Catalog | read, write | read, write | yes |
| Knowledge Base | read, write | — | — |
| Work loops | — | read, write | — |
| User memory sensitive | — | read, write | — |
| Orders & Checkout | read, write | — | — |
| Ask a human sensitive | — | read, write | — |
| Chat (probar agentes) sensitive | run | run | — |
| Projects | — | read | — |
| Public Storefront | read, write | — | — |
| Skills | read, write | — | — |
| Squads (multi-agente) | read, write | — | — |
| Tasks | read, write | read, write | — |
| Schedules & Reports | read, write | read, write | yes |
| Video (Kondor) sensitive | — | read, write, analyze | — |
| WhatsApp Messaging | read, write, send | — | — |
| Workflows | read, write | read, write, run | yes |
Full detail on granting and the owner-bounded model: Capabilities & Grants · API Keys
What a new API key can do by default
A key created without explicit scopes gets exactly this set — DEFAULT_API_KEY_SCOPES in the backend. Nothing else, until you grant it.
assistants:*workflows:*wizard:*usage:readinventory:readinventory:writeschedules:readScopes marked sensitive (chat:run, images:write, tools:*, audience:*, handoff:*, email:*, receivables:*, channels:*, apikeys:manage…) never travel by default — they cost money, leave the platform, or decide who your assistant answers. Granting them is an act, not a default.
The widget's embed key is a different, smaller set
The key that ships inside a public page's HTML (web-widget) is not a secret — anyone who views source can read it. It carries only this, on purpose:
assistants:readUntil 2026-08-29 the embed key got the same DEFAULT_API_KEY_SCOPES as a server key, so a key published in a page's source could rewrite assistants and write inventory for the whole account. If you're on an old widget build, upgrade — this is not a config flag, it's a code fix.
Audience rungs: who's on the other end of the chat
Rungs compare with >=: a rule written once for staff keeps working when a new role is added. A role like "nutritionist" or "cashier" is data on the assistant — it maps to one of these four rungs.
| Rung | Means |
|---|---|
| guest | someone we haven't identified |
| contact | a known contact, unverified |
| staff | someone from the team, verified |
| owner | the account owner |
On a self-authenticating channel (WhatsApp, Telegram, Facebook, Instagram) a contact's declared role is trusted directly. On the web widget nothing authenticates itself — even a contact saved as owner starts every conversation at contact and has to redeem a PIN to reach staff or owner that turn.
Your 403 is one of these three
The error names which system stopped you. Only one of the three gets fixed by editing the assistant: the scope one goes on the API key, the audience one on the contact.
"missing scope" / "forbidden" on /platform-apiYour API KEY lacks that scope. Check the table above, then grant it on the key (dashboard, or update_api_key if your key can manage keys) — never on the assistant.
a tool the assistant should have just isn't offered in chatThe ASSISTANT doesn't have that capability granted. grantedScopes lives on the assistant's config, bounded by its owner's role and plan — grant the scope there, not on any API key.
the assistant says it can't help with that, to a specific personThat PERSON's audience rung is below what the tool requires. A guest asking a staff-only question isn't a bug — check their rung, or have them redeem a PIN.