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 hacer

The 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.

ScopeWhat it enablesBacking permissions
chat:readList and read chats, messages and the chat resource catalog.sidebar.chat
chat:writeCreate chats, send turns, answer interactions, and rename or delete chats. Consumes the niucredits pool of the user's license.sidebar.chat
areas:readList and read the areas visible to the user.areas.read.all, areas.read.area
skills:readList and read skills.skills.read.all, skills.read.area, skills.read.own
skills:writeCreate, 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:readList and read the organization's rules.rules.read
rules:writeCreate, edit, enable/disable and delete rules.rules.create.company, rules.create.area, rules.update, rules.delete
permissions:readQuery roles, the permission catalog and the user's effective permissions.roles.read
libraries:readList and read libraries and their files.workspaces.read.all, workspaces.read.area, workspaces.read.own
libraries:writeCreate and edit libraries. Does not include uploading or deleting files.workspaces.create, workspaces.update.all, workspaces.update.area, workspaces.update.own
connectors:useQuery 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:readRead the profile of the user the credential acts on behalf of.— (operates on the user's own profile)
profile:writeUpdate profile data of the user the credential acts on behalf of.— (operates on the user's own profile)
users:readList 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:readQuery 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:

AttachmentAdditional scope
attachments.workspace_idslibraries:read
attachments.file_idslibraries:read
attachments.skill_idsskills:read
attachments.mcp_server_idsconnectors: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:

ToolRequires
list_workspaces, list_documents, search_documentslibraries: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

  1. Call GET /me. scopes says what you are actually operating with, and permissions says why.
  2. If the scope is on the credential but not in scopes, the user is missing the permission behind it.
  3. If the scope is there and the 403 persists, it is the resource layer: the credential is narrowed and that resource fell outside.
  4. In a turn, check data.required_scopes: it names the companion scope missing for the attachment you sent.