> For the complete documentation index, see [llms.txt](https://codemash.gitbook.io/codemash/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://codemash.gitbook.io/codemash/ai/developers/read-only-keys.md).

# Read-only keys

Give an AI agent a key that can only read — the Viewer and Auditor roles, what the agent still sees, and how to make a key for CI.

You want an agent to look at a project — answer "how many orders were paid this week?", read logs, check settings — without any chance that it changes something. In Norbix that is not a special kind of key: it is an AI service user that holds only **read roles**.

## How Norbix models it

Every AI tool, CLI session and `nbsu_` key acts as an **AI service user** that holds Membership roles, like a team member. The permission engine checks each call against those roles — for the MCP server (MCP = Model Context Protocol, the standard way AI tools call outside services), the CLI, the SDK and REST alike.

| You want                           | Give this role                   | It can                                                              |
| ---------------------------------- | -------------------------------- | ------------------------------------------------------------------- |
| Read one project                   | **Viewer** (project role)        | read that project; nothing else                                     |
| Read every project of the account  | **Auditor** (account role)       | read account info, the audit log and every project; changes nothing |
| Read and change data, not settings | **Data Editor** (project role)   | read, and change records                                            |
| Full control of one project        | **Administrator** (project role) | everything in that project                                          |

You pick the roles when the tool signs in (the OAuth consent screen, or the **/device** screen of `norbix login`), or in the body when you create a key through the API. You can only give roles you hold yourself.

What a read-only key sees and does over MCP — run with a **Viewer** key on one project:

| Call                                    | Answer                                                                                                                                   |
| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `tools/list`                            | all 418 tools — the list does not depend on the roles                                                                                    |
| `get_database_taxonomies` (read)        | `{"ok":true,"data":{"list":{"items":[…]}}}`                                                                                              |
| `save_database_taxonomy` (change)       | `{"ok":false,"errors":[{"message":"Caller is missing required permission 'database:create on …:database:taxonomy:all'."}],"env":"TEST"}` |
| `disable_database` with `confirm: true` | refused the same way: *missing required permission 'database:manage …'*                                                                  |

So the key **cannot change anything**, but it **lists** change tools too. To keep them out of the agent's context, load only read toolsets, for example `…/v3/account/mcp?toolsets=ai:project-inspect` (146 read tools).

{% hint style="info" %}
Until 2026-10-06 a key had a *scope* (`reach`, `rights: read | admin`, `envs`), and a read key saw only the read tools. Scopes are gone: old service users keep working with the matching role (project + read → **Viewer**, project + admin → **Administrator**, account + read → **Auditor**, account + admin → **Account Administrator**).
{% endhint %}

### Which tools only read

Each tool carries `readOnlyHint: true` when it only reads — 157 of 418. The rule comes from the endpoint: a tool reads when every route of its endpoint is `GET`, or when it is a `POST` that is declared read-only. A tool that asks for confirmation is never a read. The seven reads sent as `POST`:

| Tool                                    | Why it is a read                                                                                       |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `aggregate_records`                     | runs your aggregation pipeline; `$out`, `$merge` and server-side JavaScript are refused before it runs |
| `execute_records_aggregate`             | runs a saved pipeline under the same guard                                                             |
| `test_database_aggregate`               | a dry run of a pipeline                                                                                |
| `check_email_integration_domain_health` | looks up SPF / DMARC / DKIM in DNS; writes nothing                                                     |
| `validate_schema`                       | checks a schema; saves nothing                                                                         |
| `run_diagnostic_pack`                   | read-only diagnostic packs (adds one audit line to the support case)                                   |
| `run_diagnostic_health_check`           | one HTTP `GET` of a health endpoint                                                                    |

Every `test_*_integration` tool counts as a change: it contacts the provider and may send a real test message. The full list per module is on [What agents can do](/codemash/ai/developers/what-agents-can-do.md).

## Create a key for CI

A key is for a run without a person (CI, a script, a server). For your own IDE use OAuth or `norbix login` instead — nothing to store.

Cloud has no screen that creates a key: **Account → AI service users** lists and removes service users only. Create one through the Hub API, signed in as yourself:

```sh
# 1. the role id: GET the project's roles, take "Viewer"
curl -s "https://hub.norbix.ai/v3/membership/roles?projectId=pr_3dMEdKCg5DyxGuA4CtzS1F" \
  -H "Authorization: Bearer <your bearerToken>"
# → {"roles":[…,{"viewId":"pr_3dMEdKCg5DyxGuA4CtzS1F_nr_1glnjkoPimR5r1stRHIRZp","name":"Viewer",…},…]}

# 2. the service user with that role, and its first key
curl -s -X POST https://hub.norbix.ai/v3/account/ai/service-users \
  -H "Authorization: Bearer <your bearerToken>" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Report agent (read only)",
       "projectRoles":[{"projectId":"pr_3dMEdKCg5DyxGuA4CtzS1F",
                        "roleIds":["pr_3dMEdKCg5DyxGuA4CtzS1F_nr_1glnjkoPimR5r1stRHIRZp"]}]}'
```

```json
{
  "id": "aisu_5d6thycwjVKVNfQl1tuqRb",
  "keyId": "aisk_6j6Cb1GqGSY9omxKlkCo0K",
  "key": "nbsu_…",
  "responseStatus": { "isSuccess": true, "errors": [] }
}
```

The key is shown **once**; Norbix keeps only a hash. Put it in your secret store and pass it as `NORBIX_API_KEY`. For an account-wide reader send `"accountRoleIds": ["acc_<account>_nr_<template>"]` (the Auditor role) instead of `projectRoles`. A service user without any role is refused (`CM-ERRORS-AI-SU-016`); a role you do not hold is refused (`CM-ERRORS-AI-SU-018`).

Rotate or revoke keys with `POST /v3/account/ai/service-users/{id}/keys` (with `revokeKeyId` to rotate) and the revoke and delete routes under the same path. Removing the service user in Cloud stops all its keys at once.

## Good to know

* A read key still needs the role to cover the thing it reads: **Viewer** cannot read account-level data such as `get_account_profile` (*missing required permission 'account:read …'*).
* `norbix whoami` with a Viewer key prints `verified: false` — the check reads the project environments, which Viewer may not read. Other reads work.
* Every call, read or not, is in [Logs](/codemash/cloud/logs.md).

## Where you use it

* [MCP server](/codemash/ai/developers/mcp-server.md) — sign-in and scopes
* [Connect your IDE](/codemash/ai/developers/connect-your-ide.md) — the `-ci` blocks that read `NORBIX_API_KEY`
* [The CLI for agents](/codemash/ai/developers/cli-for-agents.md) — `NORBIX_API_KEY` in CI
* [Keys and environments](/codemash/get-started/keys-and-environments.md)
* [Permissions](/codemash/cloud/concepts/permissions.md) — roles and policies
