docs
API

Scopes and permissions

All 17 nima scopes, what each unlocks, who can grant it, where it can be granted, and how requests are checked.

Every API key and every OAuth access token carries a list of scopes. A scope is written resource:verb, and it is the only thing that opens an operation to a token. An operation that declares no scope is closed to tokens entirely, however valid the token is. That is why signing in, managing members and minting keys can only be done from a signed-in session.

nima has 17 scopes. Nothing you send can widen a token beyond them.

Scopes are on for every workspace. What differs is where each one can be granted: 8 are offered to MCP clients on the OAuth consent screen, 13 can be put on a key in Settings → API keys, and 4 can only be minted from Slack or not at all. The table below says which.

The 17 nima scopes

The granter column is the workspace permission the person creating the key, or approving the consent screen, must already hold. Owners and admins hold every permission. Members hold a subset, so sources:read, documents:write, integrations:write and, in practice, operations:write are out of their reach.

ScopeUnlocksGranter must holdMember can grantMCP client consentSettings and POST /v1/api-keys
knowledge:readAsk the knowledge graph, search code, blast radius, data modelREAD_CONTENTYesYesYes
signals:readRead signals, beacons, facet counts and the event historyREAD_CONTENTYesYesYes
sources:readThe original document behind a signal, fetched liveREAD_SOURCE_DOCUMENTSNoYesYes
documents:readRead specs, markdown, delivery state and agent operationsREAD_CONTENTYesYesYes
signals:writeFile signalsCREATE_CONTENTYesYesYes
subscriptions:readEvent-type catalog, your watches, scheduling context, your scheduled messagesREAD_CONTENTYesYesYes
subscriptions:writeCreate, change and pause watches; schedule, edit and cancel messagesREAD_CONTENT, UPDATE_CONTENT, USE_AI_GENERATIONYesYesYes
inbox:readYour nima inboxREAD_CONTENTYesYesYes
documents:writeCreate, edit, promote and delete specsCREATE_CONTENT, UPDATE_CONTENT, DELETE_CONTENTNoNoYes
versions:readSpec version historyREAD_CONTENTYesNoYes
versions:writeSnapshot a spec and restore a versionUPDATE_CONTENTYesNoYes
prd:readRead PRD sections, search and criteriaREAD_CONTENTYesNoYes
prd:writeAnswer a PRD's open questionsUPDATE_CONTENTYesNoYes
operations:writeStart, message, steer, park, resume, cancel agent operations; dispatch a specCREATE_CONTENT, UPDATE_CONTENTNo (the Slack command is owner and admin only)NoNo, Slack only
integrations:writeAdministration: hosted-compute settings, writer connections, action policies, project audience, identity linking; workspace-wide watch rules (together with subscriptions:write)MANAGE_INTEGRATIONSNoNoNo, Slack only
actions:approveList, read, approve and discard drafted outbound actionsREAD_CONTENTYesNoNo, not mintable
identities:writeLink your own external accountREAD_CONTENT, UPDATE_CONTENTYesNoNo, not mintable

One built-in first-party client, nima-mcp, may also be granted documents:write, prd:read, prd:write, versions:read and versions:write on the consent screen; a token from it can call those REST routes. No OAuth client can ever be granted a headless scope.

Four scopes are headless scopes: operations:write, integrations:write, actions:approve and identities:write. The MCP transport accepts them, but nima never adds them to OAuth consent or to an existing key.

How to get a key with a headless scope

An owner or admin runs /nima mcp-key operations or /nima mcp-key integrations in Slack (also accepted as an @nima mention or DM), then confirms on the card or tells @nima confirm <code> in the channel within five minutes. Revocation, the key cap and other details are in Authentication.

Slack commandKey carriesLifetime
/nima mcp-key operationsoperations:write, documents:read, knowledge:read30 days
/nima mcp-key integrationsintegrations:write only30 days

actions:approve and identities:write are minted by nothing: not the API key route, not the consent screen, not Slack. An API key or OAuth client cannot reach the tools behind them today. Approvals happen on the card in Slack. A signed-in browser session skips scope checks, so it can call those routes directly.

Which scopes the API key form accepts

POST /v1/api-keys validates the scopes array against 13 values: every scope in the table except the four headless ones. Sending operations:write there is a 400 with "error": "Bad Request", not a silent drop.

Routes and tools unlocked by each scope

Paths are relative to https://api.getnima.io. Signals, beacons, knowledge, code, operations and the newer resources sit under /v1. Specs (/documents) and PRDs (/prd) sit at the root with no version segment. There is no other version to ask for. The one exception is spec dispatch, POST /v1/documents/{id}/dispatch, which belongs to agent operations and carries /v1.

Each MCP tool is documented on exactly one MCP tool page; the last column only names it.

Scopes for reading and searching

ScopeOperationMCP tool
knowledge:readGET /v1/knowledge-graph/askask_knowledge_graph
knowledge:readGET /v1/code-intel/searchsearch_code
knowledge:readGET /v1/code-intel/blast-radiusblast_radius
knowledge:readGET /v1/code-intel/data-modelget_data_model
signals:readGET /v1/signalssearch_signals
signals:readGET /v1/signals/facetsnone
signals:readGET /v1/signals/{id}get_signal
signals:readGET /v1/beaconslist_beacons
signals:readGET /v1/beacons/facetsnone
signals:readGET /v1/beacons/{id}get_beacon
signals:readGET /v1/eventsget_history
sources:readGET /v1/signals/{id}/sourceget_signal_source
documents:readGET /documentslist_specs
documents:readGET /documents/{id}none
documents:readGET /documents/{id}/markdownget_document_markdown
documents:readGET /documents/{id}/deliveryget_spec_delivery
versions:readGET /documents/{id}/versionsnone
versions:readGET /documents/{id}/versions/{versionId}none
prd:readGET /prd/{documentId}/sections/{heading}none
prd:readGET /prd/{documentId}/searchnone
prd:readGET /prd/{documentId}/criterianone

Scopes for writing signals and specs

ScopeOperationMCP tool
signals:writePOST /v1/signalscreate_signal
documents:writePOST /documentsnone
documents:writePUT /documents/{id}/markdownnone
documents:writePATCH /documents/{id}/spec-metadatanone
documents:writePATCH /documents/{id}/epic-metadatanone
documents:writePOST /documents/{id}/promotenone
documents:writeDELETE /documents/{id}none
versions:writePOST /documents/{id}/snapshotnone
versions:writePOST /documents/{id}/restore/{versionId}none
prd:writePOST /prd/{documentId}/questionsnone

Scopes for watches, inbox and scheduled messages

ScopeOperationMCP tool
subscriptions:readGET /v1/triggers/event-typeslist_event_types
subscriptions:readGET /v1/watcheslist_watches
subscriptions:readGET /v1/scheduled-messages/contextget_scheduling_context
subscriptions:readGET /v1/scheduled-messageslist_scheduled_messages
subscriptions:writePOST /v1/watchescreate_subscription
subscriptions:writePATCH /v1/watches/{id}update_subscription
subscriptions:writeDELETE /v1/watches/{id}unwatch
subscriptions:writePOST /v1/scheduled-messagesschedule_message
subscriptions:writePATCH /v1/scheduled-messages/{id}update_scheduled_message
subscriptions:writeDELETE /v1/scheduled-messages/{id}cancel_scheduled_message
inbox:readGET /v1/inboxget_inbox

A workspace rule (scope: "org" on POST /v1/watches) needs integrations:write on the same key or token as well as subscriptions:write, plus the integrations-management permission. No key today carries both, so workspace rules can be created only from a signed-in owner or admin session. The workspace must also have watches and outbound dispatch turned on, or the call answers 409; see Watches.

Scopes for agent operations

Reads of operations use documents:read. Controls use operations:write.

ScopeOperationMCP tool
documents:readGET /v1/operationslist_operations
documents:readGET /v1/operations/{id}get_operation
documents:readGET /v1/operations/{id}/resultget_operation_result
documents:readGET /v1/operations/{id}/briefget_operation_brief
documents:readGET /v1/operations/{id}/eventsnone
documents:readPOST /v1/operations/{id}/inspectinspect_operation
operations:writePOST /v1/operationsstart_operation
operations:writePOST /v1/operations/{id}/claimclaim_operation_handoff
operations:writePOST /v1/operations/{id}/resultreport_operation_result
operations:writePOST /v1/operations/{id}/messagemessage_operation
operations:writePOST /v1/operations/{id}/steernone
operations:writePOST /v1/operations/{id}/parknone
operations:writePOST /v1/operations/{id}/resumenone
operations:writePOST /v1/operations/{id}/cancelcancel_operation
operations:writePOST /v1/operations/{id}/raise-limitraise_operation_limit
operations:writePOST /v1/documents/{id}/dispatchnone

Three more tools exist only for a hosted VM run and are never shown to a user token: get_run_brief (GET /v1/operations/{id}/run-brief, documents:read), report_or_ask_pm (POST /v1/agent-runs/{id}/report, signals:write) and wait_for_checks (POST /v1/agent-runs/{id}/wait-for-checks, signals:write).

Scopes for administration and outbound dispatch

ScopeOperationMCP tool
integrations:writeGET /v1/agent-computeget_agent_compute_settings
integrations:writePATCH /v1/agent-computeupdate_agent_compute_settings
integrations:writeGET /v1/github-writer/app/installget_github_writer_install_url
integrations:writeGET /v1/github-writer/app/statusget_github_publishing_status
integrations:writePATCH /v1/github-writer/publishingset_github_publishing
integrations:writeDELETE /v1/github-writer/app/installationdisconnect_github_writer
integrations:writePOST /v1/integrations/jira/writer/mcpconnect_jira_writer
integrations:writePOST /v1/integrations/jira/writer, GET /v1/integrations/jira/writernone
integrations:writePOST /v1/integrations/linear/writerconnect_linear_writer
integrations:writeGET /v1/integrations/linear/writernone
integrations:writePATCH /v1/dispatch/policiesset_action_policy
integrations:writeGET /v1/dispatch/policiesnone
integrations:writePOST /v1/dispatch/project-audienceassert_project_audience
integrations:writeGET /v1/identities/unlinkedlist_unlinked_identities
integrations:write or identities:writePOST /v1/identities/linklink_external_identity
identities:writePOST /v1/identities/link-melink_my_identity
actions:approveGET /v1/dispatch/actions (alias /v1/actions)list_pending_actions
actions:approveGET /v1/dispatch/actions/{id} (alias /v1/actions/{id})get_action
actions:approvePOST /v1/actions/{id}/approveapprove_action
actions:approvePOST /v1/actions/{id}/discarddiscard_action

Operations that accept more than one scope

GET /oauth/userinfo accepts any of the 17 scopes, so it works with every token. It returns sub, email, isOnboarded, organizationId and orgRole, and no scope list. POST /v1/identities/link accepts integrations:write or identities:write. The MCP transport at POST /mcp accepts the 12 scopes described in Scopes on the MCP server. GET /mcp and DELETE /mcp accept the 8 consent scopes (they answer 405, since the server keeps no stream or session).

Some routes declare a plan feature as well, for example specs under documents, PRDs under prd_generation and code search under code_intelligence. A workspace whose plan lacks the feature can get 403 INSUFFICIENT_PERMISSIONS with details.feature naming it. Handle that response; do not assume a scope alone is enough.

How nima checks scopes

nima checks a token's scopes against the operation on every request, in four steps.

Closed by default. An operation that declares no scope rejects every key and every OAuth token with 403 and the message This endpoint does not accept OAuth access tokens, before the handler runs. A valid key is not enough.

One matching scope is enough. An operation passes a token that holds at least one of the scopes it lists, not all of them. Most operations list exactly one. POST /v1/identities/link (2), POST /mcp (12), GET/DELETE /mcp (8) and GET /oauth/userinfo (all 17) list more.

Scopes only narrow. On every request nima reads the token owner's current role in the workspace the token belongs to and intersects those permissions with the permissions the token's scopes imply. A token can never do more than its owner can do right now. Demote the owner to member and their sources:read key stops fetching sources on the next call. Remove them from the workspace and every token they minted resolves to no role: permission-checked routes answer 403, and GET /oauth/userinfo returns orgRole: null. Access stops without a revocation step, but the keys stay listed and keep authenticating until they are revoked or expire. Tokens never carry super-user rights.

Two different 403 messages. A token missing the scope is refused with OAuth token scopes do not cover this endpoint and details.requiredScopes. A token that holds the scope while its owner has lost the permission is refused with Missing required permissions (AND): … (or (OR) on a route that accepts any one permission), naming the permissions in details.requiredPermissions and the mode in details.mode.

Granting scopes: the silent drop

A scope is grantable only if the person granting it holds all the permissions in the granter column. Scopes they cannot back are dropped from the request and the key is created anyway.

Asking for a scope you cannot grant is not an error. A member who ticks sources:read in Settings → API keys gets a working key back with sources:read quietly missing, and the first 403 arrives later, in whatever script was going to use it. Read the scopes array on the created key, not the boxes you ticked.

If every requested scope is ungrantable, key creation fails instead with 400 and "None of the requested scopes can be granted by this user", carrying the scopes you asked for in details.requestedScopes.

OAuth consent works the same way and adds one filter in front. A client can only be granted the scopes it registered with. Scopes outside that set are dropped from the request, and only when none are left is the request refused with 400 and "None of the requested scopes are allowed for this client". If the approver's role can back none of what remains, approval fails with 400 and "User lacks permissions to grant the requested scopes". The authorization code is issued for whatever passes both filters.

No route reports a key's or token's scopes back when you present it. What a key carries is in the response you got when you created it and in the key list in Settings → API keys. What an OAuth token carries is in the scope field of the token response.

Scopes only owners and admins can grant

Four scopes are out of a member's reach, for different reasons.

  • documents:write needs create, update and delete on content. A member holds create and update but not delete.
  • sources:read needs READ_SOURCE_DOCUMENTS. Fetching the document behind a signal replays the request against the source system with the workspace's own integration credential, which reaches further than any one member does.
  • integrations:write needs MANAGE_INTEGRATIONS, and it is also minted only through the Slack command above.
  • operations:write is backed by permissions a member holds, but the only way to mint it is /nima mcp-key operations in Slack, which accepts owners and admins only.

Scopes on the MCP server

The remote MCP server at https://mcp.getnima.io/mcp offers 8 scopes on its consent screen, and its transport accepts 12.

Consent screen scopes (8). In display order: knowledge:read, signals:read, sources:read, documents:read, signals:write, subscriptions:read, subscriptions:write, inbox:read. Every client that registers dynamically is registered with exactly this set, so a registered MCP client can be granted only these; the headless scopes are never grantable through consent. The screen groups them as read permissions ("Search what nima knows", "Read signals and beacons", "Read the sources behind signals", "Read specs and documents", "See what you are watching", "Read your inbox") and write permissions ("Manage your subscriptions", "File signals for you").

Transport scopes (12). POST /mcp accepts the 8 consent scopes plus the 4 headless scopes. A token holding any one of the 12 opens the endpoint, and each tool then re-checks its own scope.

Visibility rule for tools/list. A signed-in browser session sees every tool. A key or OAuth token sees every tool behind a consent scope, even if the token does not hold that scope, so a stale token learns the tool exists and the failed call triggers re-consent. Tools behind a headless scope appear only when the token holds that scope. VM-only tools never appear. A default OAuth connection therefore lists the 30 consent-scope tools, including the five that read agent operations, and none of the tools that start or control operations, administer the workspace, approve actions or link identities.

The WWW-Authenticate challenge. A 401 carries Bearer error="invalid_token" with the resource metadata URL and the 8 consent scopes. A 403 from a tool behind a consent scope becomes an outer 403 with error="insufficient_scope" and a scope list made of the tool's scope followed by the scopes the token already holds, so re-consent keeps what the client had. That is the client's cue to re-consent:

WWW-Authenticate: Bearer error="insufficient_scope", scope="sources:read knowledge:read signals:read"

A 403 from a tool behind a headless scope is not an outer error. It comes back as a tool result with isError set and this text: "This tool requires an explicit headless grant. Ask a workspace admin to use /nima mcp-key operations or /nima mcp-key integrations in Slack, then connect using that key. Existing OAuth consent does not grant administration." An OAuth client can never administer integrations or compute.

Connect steps live on Connect to nima's MCP server; each tool is documented on its MCP tool page, starting from knowledge tools.

Scope errors

StatuserrormessageWhen
401TOKEN_INVALIDInvalid or expired access tokenThe key or token is unknown, revoked or expired.
403INSUFFICIENT_PERMISSIONSThis endpoint does not accept OAuth access tokensThe operation declares no scope. No key reaches it.
403INSUFFICIENT_PERMISSIONSOAuth token scopes do not cover this endpointThe token holds none of the scopes the operation lists. details.requiredScopes says which.
403INSUFFICIENT_PERMISSIONSMissing required permissions (AND): … (or (OR) on a route that accepts any one permission)The scope is there, the owner's current role is not. details.requiredPermissions names the permissions and details.mode gives the mode.
403INSUFFICIENT_PERMISSIONSAPI key not allowed from this addressThe key is restricted to CIDR ranges and the call came from outside them.
403INSUFFICIENT_PERMISSIONSFeature "<feature>" is not available on your current planThe route is declared under a plan feature the workspace plan lacks. details.feature and details.plan name them.
400VALIDATION_ERRORNone of the requested scopes can be granted by this userEvery scope asked for at key creation was ungrantable.

A scope string that is not one of the accepted values is rejected by generic input validation, which answers 400 with "error": "Bad Request" and "message": "Bad Request Exception", without saying which field was wrong. Compare your scope strings against the table above, not against that message.

To see a scope failure, call a route with a key that lacks the scope:

curl -i -H "Authorization: Bearer $NIMA_API_KEY" \
  https://api.getnima.io/v1/signals/3f7a9c21-5b48-4d0e-9a11-6c2f8be41d73/source

A key minted without sources:read gets:

{
  "statusCode": 403,
  "error": "INSUFFICIENT_PERMISSIONS",
  "message": "OAuth token scopes do not cover this endpoint",
  "details": { "requiredScopes": ["sources:read"] },
  "timestamp": "2026-09-22T10:31:07.512Z",
  "path": "/v1/signals/3f7a9c21-5b48-4d0e-9a11-6c2f8be41d73/source"
}

To check that a token is alive without needing a particular scope, call GET /oauth/userinfo. A null orgRole means the owner is no longer a member.