Documentation
Scope and permissions
The three layers that decide what a credential can do — and why you got a 403.
A credential does not grant access: it narrows it. It always acts on behalf of the person who created it, with their permissions and their license, and scopes only shrink that reach. They never widen it.
lo que el USUARIO puede hacer hoy
∩
los SCOPES del access token
∩
los RECURSOS que la credencial alcanza
─────────────────────────────────────
= lo que este request puede hacerThe three layers are evaluated on every request, against live state. There is no frozen snapshot from the day it was created.
1. The user's permissions
It is the ceiling. If skills.update.own is removed from the user's role, the skills:write scope on all their credentials stops working on the next request, without touching any credential. There is no need to hunt them down and revoke them one by one.
The same works upward: if they gain a new permission, credentials that already had that scope granted start being able to use it. That is why GET /me can return different scopes from one day to the next with the same token.
2. The token's scopes
Each operation declares the scope it requires. If one is missing the answer is 403 with WWW-Authenticate: Bearer error="insufficient_scope", scope="…", naming exactly which one.
| Scope | What it enables | Backing permissions |
|---|---|---|
chat:read | List and read chats, messages and the chat resource catalog. | sidebar.chat |
chat:write | Create chats, send turns, answer interactions, and rename or delete chats. Consumes the niucredits pool of the user's license. | sidebar.chat |
areas:read | List and read the areas visible to the user. | areas.read.all, areas.read.area |
skills:read | List and read skills. | skills.read.all, skills.read.area, skills.read.own |
skills:write | Create, edit, enable/disable and delete skills. | skills.create, skills.create.public, skills.create.shared, skills.update.all, skills.update.own, skills.delete.all, skills.delete.own |
rules:read | List and read the organization's rules. | rules.read |
rules:write | Create, edit, enable/disable and delete rules. | rules.create.company, rules.create.area, rules.update, rules.delete |
permissions:read | Query roles, the permission catalog and the user's effective permissions. | roles.read |
libraries:read | List and read libraries and their files. | workspaces.read.all, workspaces.read.area, workspaces.read.own |
libraries:write | Create and edit libraries. Does not include uploading or deleting files. | workspaces.create, workspaces.update.all, workspaces.update.area, workspaces.update.own |
connectors:use | Query the connector catalog and let the chat run their tools. It does not authorize new connectors: it uses the ones the user already connected. | mcps.read.all, mcps.read.area |
profile:read | Read the profile of the user the credential acts on behalf of. | — (operates on the user's own profile) |
profile:write | Update profile data of the user the credential acts on behalf of. | — (operates on the user's own profile) |
users:read | List the company's users (id, name, email, status and areas). Needed to build shared_user_ids when sharing a skill. | users.read.all, users.read.area |
analytics:read | Query usage and token consumption metrics. | metrics.read.all, metrics.read.area |
Note
One of the listed permissions is enough for the scope to be grantable. skills:write covers create, edit and delete; a role that can only create still needs the scope. The fine-grained permission is checked again inside the operation — the scope is the first layer, never the only one.
3. The resources
A credential can additionally be limited to specific libraries, skills, areas or connectors. See Credentials for how it is configured.
A resource outside the scope answers `404`, not `403`, when the client did not name it explicitly. A 403 would confirm the resource exists, and with sequential ids that is enough to inventory someone else's.
The exception is when the client did name it: an area_id in a rule, a workspace_id in a skill. There the answer is 403 api_v1_resource_not_allowed, because pretending that something the client just asked for does not exist would be confusing and protects nothing.
Chat companion scopes
chat:write authorizes conversing, not reading whatever gets attached. Attaching a resource to a turn additionally requires that resource's read scope:
| Attachment | Additional scope |
|---|---|
attachments.workspace_ids | libraries:read |
attachments.file_ids | libraries:read |
attachments.skill_ids | skills:read |
attachments.mcp_server_ids | connectors:use |
Heads up
Without this rule, chat:write alone would be enough to make the model read a library the credential cannot open: the turn would be the leak. The check happens before the idempotency reservation, so a 403 here does not burn your key.
Which tools the model sees
In an API turn, the model receives a narrowed set of internal tools, decided by the same scopes:
| Tool | Requires |
|---|---|
list_workspaces, list_documents, search_documents | libraries:read |
ask_user_choice | — (produces an interaction) |
submit_plan, update_plan | — (report progress) |
search_tools | — |
Note
It is an allow-list, not a deny-list: a new platform capability does not enter an API turn on its own. Tools that re-enter NiuCore with the user's session are out of v1.0, because in a delegated turn that session does not exist.
Connectors (Gmail, Drive, Calendar…) do work, with connectors:use, but only the ones the user already connected from the application. The API cannot authorize a new connector: that needs a browser and a person.
Diagnosing a 403
- Call
GET /me.scopessays what you are actually operating with, andpermissionssays why. - If the scope is on the credential but not in
scopes, the user is missing the permission behind it. - If the scope is there and the
403persists, it is the resource layer: the credential is narrowed and that resource fell outside. - In a turn, check
data.required_scopes: it names the companion scope missing for the attachment you sent.