Reading from your brain
Two ways to read. Ask for exact things and get bytes back, or hand the server a task and let a reader agent assemble what you need. Both answer only at your key's tier, and neither will tell you what it is hiding.
The read endpoints
Six endpoints that do exactly what they say and nothing clever. Every one of them resolves your tier, serves from that tier's snapshot, and logs what it handed over.
| Endpoint | What you get |
|---|---|
get-index | The index of everything visible to you, generated per tier. |
list-notes | Entities, filterable by kind, topic, project and status. Defaults to current, so superseded notes stay out of your way. |
get-note | The full markdown of one entity. |
get-lens | One lens: a named retrieval scope. |
get-identity | Your identity files. The ones that let an agent write as you rather than about you. |
get-raw | An archived source. A raw/ file inherits the visibility of the notes that link to it. |
There is also ping, which is the fastest way to find out what tier your key actually has:
POST /api/v1/ping
Authorization: Bearer <your-key>
{"pong": true, "service": "my-brain-web-app", "tier": "agents-only"}
Same brain, two keys, one question. An agents-only key
lists 7 entities. A public key lists 2. Nothing in the
agent decides that.
What a tier boundary feels like from below
The interesting question is not whether a low tier is refused. It is whether being refused tells you something.
Asking a public key for a note that really exists one
tier up, and for a note that does not exist at all:
# exists, one tier above this key
{"error": {"code": "input_validation_error",
"message": "unknown entity: take-2026-08-docs-from-real-sessions"}}
# does not exist anywhere
{"error": {"code": "input_validation_error",
"message": "unknown entity: take-2026-08-this-does-not-exist"}}
Same status, same wording. There is no "exists but you may not have it", because that sentence is itself a disclosure. If you can probe for which ids come back differently, you can map a brain you cannot read.
This works because each tier is a materialized snapshot, built when your brain is indexed. The read layer is not filtering a full corpus per request and hoping the filter is right. It is reading a smaller corpus that never contained the note.
assemble-context: hand it a task, not a query
The endpoints above are for when you know what you want. This one is for when you know what you are about to do.
You send a task. A reader agent goes into your brain at your tier, picks what matters, and returns a context pack with the verbatim quotes preserved. It is not a search: "write a reply to this comment about testing" is a better input than "testing".
POST /api/v1/assemble-context
{"task": "Write a short paragraph in the owner's voice about why
documentation should be written from real sessions."}
What comes back:
{"tier": "agents-only",
"entity_ids_used": ["take-2026-08-docs-from-real-sessions"],
"gaps": ["identity/core.md, identity/voice.md, and identity/beliefs.md
are all unfilled template placeholders ..."],
"tokens": {"input": 8, "output": 1648},
"model": "claude-haiku-4-5-20251001, claude-sonnet-5",
"duration_ms": 23635}
Two fields there earn their place. entity_ids_used means
you can audit any answer back to the notes behind it.
gaps is the agent telling you what it wanted and did not
find. On that call it reported the identity files were still empty
scaffolding, which is a real gap and a useful nudge.
You can pass a lens name to narrow the scope to a
subject you have already defined.
This one is slow, and the docs say so. The endpoint documents 5 to 30 seconds. On a tiny brain, two calls took 18.7 and 24.6 seconds. It runs a real agent, so budget for it, do not put it in a tight loop, and do not retry it automatically. It is never retried for you.
Pointing Claude Code at it
Mint a key in the ops UI under API keys, then drop an
.mcp.json next to your project:
{
"mcpServers": {
"brain": {
"type": "http",
"url": "https://your-domain/mcp/",
"headers": { "Authorization": "Bearer <your-key>" }
}
}
}
That is the whole setup, and it is the same shape for any MCP client
that speaks streamable HTTP: a URL ending in /mcp/ and a
bearer token. Every read endpoint on this page shows up as a tool, and
so does propose-feed if your key is
agents-only or above.
One thing worth knowing if you run agents that already have other MCP
servers configured: a nested run can inherit them. When it matters
that an agent reads this brain and nothing else, pass
--strict-mcp-config. It is easy to miss, because a second
brain connector sitting alongside the one you meant to test answers
perfectly well.
Consumer keys
Mint one key per agent or per project, not one key you paste everywhere. Each carries a name, a tier, and a rate limit per minute.
- The key is shown once. Copy it then. If you lose it, rotate, which mints a replacement and kills the old one immediately.
-
A key with no consumer profile reads as
public. The failure direction is least privilege: a misconfigured key sees less, never more. -
agents-onlyis the right default for your own tools. It reads your notes and your identity files and never your private ones. Keepprivatefor something you run and watch. - Revoking is immediate. Every call using that key fails from the next request.
Every read is logged with the entity ids it served, so who read what, and when is a question your instance can answer.