Get Your API Keys
You need one key per server, and they come from different places. This is not an oversight β the two credentials answer to different authorities.
| Key | Where it comes from |
|---|---|
| Companion Hub | Hub web UI β Settings β Security, or cihub api-key create on the appliance host |
| Companion Memory | Companion Memory web UI β Settings β API Keys |
Hub gives you both a UI and a CLI because its key is an operator credential β useful to mint from a script on a headless box. Memoryβs key is a per-user credential tied to your signed-in session, so the UI is the only route; there is deliberately no memory-side CLI.
Both keys are shown once, at creation. Neither can be retrieved afterwards β if you lose one, mint a replacement and delete the old one.
The Companion Hub key
From the web UI
Open the Hub dashboard and go to Settings β Security. The API keys card sits below two-factor authentication; Create key mints one, and its raw value is shown only at creation.
Each row shows the keyβs prefix and carries its scopes and capability as chips. Change access adjusts the capability β scopes are fixed at creation and render as read-only badges. Revoke deletes the key.
From the CLI
Run this on the appliance host, not on the machine running your agent:
cihub api-key create --name "my-laptop-openclaw"The full form, if you want to be explicit:
cihub api-key create \
--name "my-laptop-openclaw" \
--scopes mcp \
--capability read| Flag | Values | Default |
|---|---|---|
--name | any label | required |
--scopes | mcp | mcp |
--capability | read, write, full | write |
List what exists β this prints IDs, names, scopes, capability and a key prefix, but never the key itself:
cihub api-key listHubβs MCP server is enabled by default. If a key connects but the endpoint
refuses, check cihub mcp config β someone may have set MCP_ENABLED=false.
The Companion Memory key
Open Settings
Sign in to Companion Memory and go to Settings β API Keys. The create control is the + button
in the top right of the panel, not a labelled button in the empty state.


Name, expiration, access and capability are all fields of the same dialog β you fill them in before the key exists, not after.
Name it
Give it a name that identifies the client you are connecting, so you can revoke precisely later.
Tick the areas it may reach
The scope checkboxes are grouped under Access. For MCP you must select both MCP and
Intents. See the scope trap below β this is the single most
common setup mistake.


The dialog warns about both traps itself: the MCP checkbox reads βpair with Intents to see any
toolsβ, and the note under the group confirms the key βis limited to the selected areas and is
refused everywhere else, including bulk ingest and GraphQL.β
Add Memory if the key will be used for anything beyond MCP β including
first-class memory: the passive-capture provider
talks REST, so a key without Memory is refused on every call it makes, silently. Ticking all three
(Memory, Intents, MCP) yields one key that covers both the MCP setup and the provider.
Get this right the first time. Scopes cannot be changed after creation β the edit dialog offers only the name and the capability. A key with the wrong access has to be replaced.
Choose a capability
Under What this key can do, the dialog labels these Read-only, Read & write, and
Read, write & delete β the same three levels this page calls read, write, and full. See
Choosing a capability.
The dialog starts on Read & write, as in the screenshot above. For a client whose job is recall, change it to Read-only β that is a deliberate step, not the default. The exception is a key for first-class memory: passive capture writes, so that key must stay on Read & write β on a Read-only key, recall works and every capture is silently refused, which looks exactly like a healthy setup that never remembers.
Create it, and copy the key
The key is shown once, on submit, under βYour new API key has been createdβ β and the panel tells you plainly that βthe key value wonβt be shown again.β Store it in your password manager or export it as an environment variable now.


Once you dismiss that banner the key row keeps only a short prefix β redacted in the screenshots above β alongside its scope chips and its capability. Enough to identify the key, never enough to use it.
Scopes: which surface the key opens
Scopes say where a key may go. They are not the same thing as capability, which says what it may do once it gets there.
| Scope | Opens |
|---|---|
MCP | The Model Context Protocol endpoint at /api/mcp |
Intents | The agent intent catalog β and the tools an MCP client can see |
Memory | The memory REST API (/api/memory/*) β recall, context, write, turn |
The scope trap: MCP without Intents
A key with MCP but not Intents authenticates successfully, opens a session, and then advertises
zero tools. Your client will report a connected server with nothing on it, which looks exactly
like a broken server and is not one.
Measured against a live instance: MCP alone advertises no tools at all β tools/list answers
-32601 Method not found, because nothing was ever registered. Adding Intents fixes it. See
the full table, which also shows how
capability changes the count.
Different jobs need different scopes. If you are wiring MCP and want your agent to reach the
memory REST API, you need MCP + Intents + Memory. A key minted for MCP alone will connect over
MCP and return 403 on every /api/memory/* request.
Scopes are not additive permissions β enforcement is deny-by-default. Only
those three surfaces declare a scope at all, and a key with any box ticked is
refused everywhere no scope is declared: GraphQL, bulk ingest, files, and the
graph routes. So MCP + Intents + Memory is not βthe maximal keyβ β it is
narrower than leaving every box unticked. An unscoped key is the only one that
reaches those other surfaces, and it is full-access by construction.
Choosing a capability
Capability says what a key may do on the surfaces its scopes opened. It is a separate axis, and every key has one.
| Capability | Reaches | Use it when |
|---|---|---|
read | Read-only tools. Nothing it calls changes state. | The agentβs job is recall, search, and inspection |
write | Plus mutating tools β install, start, stop, update, record. Default. | The agent should manage apps or record memories |
full | Plus destructive tools β uninstall, reset, delete, bulk actions. | You specifically want the agent to be able to remove things |
Prefer read unless you want the agent changing things. A key that only needs to recall memories
does not need permission to uninstall your apps, and a key pasted into a third-party client is a key
you no longer fully control.
Capability also filters what your agent sees. A read keyβs tools/list returns only the tools it
could actually call, so the agent never wastes a turn on a tool that was always going to be refused.
read with no scopes selected is not read-only
Capability is enforced on the routes that declare a scope. A key with no scopes selected is not scope-limited at all β it also reaches GraphQL and bulk ingest, where nothing checks capability.
This one combination means less than it says. If you want read-only to hold, select the areas the key needs rather than leaving them all unchecked.
The two app-proxy tools are gated differently
hub_call_app_api proxies an HTTP call to an app, so what it does depends on the arguments. It
appears in every capabilityβs tool list and the decision is made per call from the method.
The dividing line is narrower than it looks: only GET, HEAD and OPTIONS count as reading.
Everything else β POST, PUT, PATCH, DELETE β is treated as destructive and needs full.
So a write key can see this tool and use it to read, but is refused for every mutating method, the
same as a read key. An unrecognised or missing method counts as mutating.
If your agent needs to drive an appβs HTTP API beyond reads, full is the only capability that
works.
hub_call_app_tool is gated the other way. A bridged toolβs effect is opaque to the Hub, so it is
flagged destructive outright rather than judged per call β only a full key sees it. If your
agent needs to call tools on an appβs bridged MCP server, read and write will not do.
Changing a key later
You do not need to replace a key to change what it can do β but where you do it differs by server.
Companion Memory
In Settings β API Keys, edit the key and change its capability. Raising a keyβs authority asks for confirmation; lowering it does not, so tightening a key is never harder than loosening one.
Revoking
Revoke a memory key from Settings β API Keys with the delete icon on its row, and a Hub key from Settings β Security with Revoke. Because keys are per-client if you minted them that way, revoking one leaves your other clients working.