Organizations
Team-wide AI memory that's unified, scalable, secure, and safe, with roles, tags, RBAC retrieval, audit trails, and human-in-the-loop review.
Organizations
Base URL: https://api.memoryrouter.ai
Organizations turn MemoryRouter from a per-user memory vault into a team-wide memory fabric. Everyone on the team gets their own private memory, and the knowledge that matters to the whole team is shared in real time, governed by roles, filtered by permissions, recorded in an audit trail, and protected by human approval when the AI isn't sure.
Executive summary
A normal MemoryRouter key gives one person an AI that remembers them. That's powerful for an individual, but teams have a different problem: knowledge is trapped in individual heads (and individual AI sessions). When Cindy learns something about a client, Dave's AI has no idea until Cindy tells him. Multiply that across a 50-person company and you get an organization where the AI is smart for each person but dumb for the team.
Organizations fix that. With one org you get:
- Unified team memory. When Cindy's AI notes a client status, Dave's AI knows it five minutes later, without Cindy and Dave ever talking. Shared knowledge propagates in real time.
- Private memory stays private. Cindy's personal working memory ("her session") auto-injects for her and never leaks into Dave's. We share the team-relevant facts, not the personal ones.
- Per-employee identity, baked into the key. Each team member gets their own
mk_org_key. Identity is resolved 100% from the key: there's no spoofable header, no shared login. One org → many keys, one per person. - Role-based access control (RBAC). A role is a set of tags a person is allowed to see. Sensitive memories simply never enter the context of someone whose role doesn't permit them. The boundary is enforced at the data layer, not by a prompt.
- Audit trails. Every create / update / delete / retag of a shared memory is recorded with who did it and when. You can answer "who has seen what?" and "who changed this?" at any time.
- Human approval when the AI isn't sure. A small, fast model classifies what to remember and how to tag it. When its confidence is low, the memory lands in a review queue instead of being silently trusted, and a human approves, edits, or dismisses it. The AI doesn't get to quietly make a mistake.
The result: one shared brain for your company that's scalable (it's just keys and tags), secure (RBAC at the data boundary), and safe (audit + human review).
Why this is hard, and why MemoryRouter makes it easy
Sharing memory across a team naively creates three problems. Organizations solve each one structurally:
| Problem | Naive approach | MemoryRouter |
|---|---|---|
| Sessions bleed together | One shared vault → everyone's personal context pollutes everyone else's | Dual-vault retrieval. Each user's private vault auto-injects for them; a shared org vault is merged in for team facts. Personal stays personal. |
| Everyone sees everything | Shared vault = no privacy; the intern sees board-level memos | RBAC tag filtering. A role grants a set of tags. Retrieval drops any shared memory whose tags the requester's roles don't allow. Untagged = public; unknown users fail closed. |
| The AI mis-files sensitive data | Trust a model to tag correctly, every time, silently | Three-pass write trail + low-confidence review queue. Extraction and tagging are logged and auditable, and anything the classifier is unsure about waits for a human. |
You define the policy once (roles, tags, what-to-remember rules). MemoryRouter enforces it on every read and write, automatically, for every member of the org.
Core concepts
Organization (org): The top-level container. Owns roles, tags, users, keys, instructions, and a shared-memory bank. You can own many orgs.
Member key (mk_org_...): A per-employee API key minted under an org. The key carries both the org association and the team_member_id. Members authenticate with this key; their identity (and therefore their RBAC scope) resolves directly from it.
Role: A named set of allowed tags (allowed_tags). A "*" wildcard means "all tags, including future ones," a super-role. A user holds many roles; their effective scope is the union.
Tag: A classification rule: a label plus the question the classifier asks of each memory ("Is this about pricing?"), with optional examples (positive) and negatives. Tags are how shared memories get filtered by RBAC.
User: A person in the org, identified by email, assigned one or more roles. (team_member_id is the same identity used by keys; it can be an email or any string.)
Instructions: The org's "what to remember" policy: a base default, plus org-specific custom guidance, plus anti_rules (never remember these) and exec_rules (remember only short, high-level versions).
Shared memory: A team-visible fact in the org bank. Created either by the write-worker (extracted from chat turns) or seeded by an admin. Carries tags, confidence, author, and a reviewed flag.
Authentication & the two credential tiers
There are two distinct credential tiers, and keeping them separate is the entire security model:
1. Admin tier: manages the org (top-level only)
Every endpoint under /v1/orgs is gated by an admin credential. This is your account-level / top-level credential, not an mk_org key. Accepted forms:
Authorization: Bearer <ADMIN_SECRET>orAuthorization: Bearer mk_admin...- Dashboard credentials
X-Dashboard-Key/X-Admin-Key(used by the app's server-side proxy)
An
mk_orgmember key cannot mint, list, reveal, or revoke keys, nor touch any org-admin route. It returns401. The/v1/orgsrouter is mounted ahead of the member-key auth path, so a member key is never even evaluated for these routes. Org members cannot take control of the org. Management is top-level only.
The acting admin's email is resolved server-side and forwarded as X-Admin-Email for solo ownership (you only see and manage orgs you own) and for audit attribution (the audit trail records who did what).
2. Member tier: uses the org (per-employee)
Each team member calls the inference API (/v1/chat/completions, etc.) with their own mk_org_ key. That key:
- Associates the request with the org (shared vault).
- Resolves the member's identity (
team_member_id), which drives their RBAC tag scope. - Auto-injects their private memory and merges in permitted shared memory.
Members never get an admin credential, so they can read/write memory under policy but can never change the policy. Per-employee keys live in the Memory Keys surface, one per person.
Quickstart: stand up an org in 8 calls
This walkthrough creates an org, defines a role and a tag, adds a user, assigns the role, mints the member's key, and seeds a shared memory. Every call uses the admin credential.
ADMIN="Authorization: Bearer $ADMIN_SECRET"
BASE="https://api.memoryrouter.ai"
# 1) Create the org (returns org_id + the org key ONCE)
curl -s -X POST $BASE/v1/orgs -H "$ADMIN" -H 'Content-Type: application/json' \
-d '{"name":"Helios Robotics","extract_model":"openai/gpt-5.5","classify_model":"openai/gpt-5.4-mini"}'
# → { "org_id": "ORG", "name": "Helios Robotics", "org_key": "mk_org_...", ... }
# 2) Create a tag (the classifier question + examples)
curl -s -X POST $BASE/v1/orgs/ORG/tags -H "$ADMIN" -H 'Content-Type: application/json' \
-d '{"label":"pricing","question":"Is this about deal pricing, discounts, or contract value?",
"examples":["We offered Acme 20% off the annual plan"],
"negatives":["The product roadmap for Q3"]}'
# → { "tag_id": "TAG", "label": "pricing", ... }
# 3) Create a role that may see the pricing tag
curl -s -X POST $BASE/v1/orgs/ORG/roles -H "$ADMIN" -H 'Content-Type: application/json' \
-d '{"name":"Sales","allowed_tags":["pricing"]}'
# → { "role_id": "ROLE", "name": "Sales", "allowed_tags": ["pricing"], ... }
# 4) Add a user, assigned to that role
curl -s -X POST $BASE/v1/orgs/ORG/users -H "$ADMIN" -H 'Content-Type: application/json' \
-d '{"email":"cindy@helios.example","first_name":"Cindy","role_ids":["ROLE"]}'
# → { "user_id": "USER", "email": "cindy@helios.example", "role_ids": ["ROLE"], ... }
# 5) Mint Cindy's per-employee member key (her inference credential)
curl -s -X POST $BASE/v1/orgs/ORG/keys -H "$ADMIN" -H 'Content-Type: application/json' \
-d '{"team_member_id":"cindy@helios.example"}'
# → { "key_id": "mk_org_...", "team_member_id": "cindy@helios.example", ... } (raw key returned ONCE)
# 6) Seed a shared memory (admin-asserted, full confidence → skips review)
curl -s -X POST $BASE/v1/orgs/ORG/memories -H "$ADMIN" -H 'Content-Type: application/json' \
-d '{"text":"Acme signed a 2-year contract at $48k/yr","tags":["pricing"]}'
# 7) Cindy now calls inference with HER key: shared pricing memory is in context
curl -s -X POST $BASE/v1/chat/completions \
-H "Authorization: Bearer mk_org_CINDYS_KEY" -H 'Content-Type: application/json' \
-d '{"model":"openai/gpt-5.5","messages":[{"role":"user","content":"What did Acme sign?"}]}'
# 8) Review anything the classifier was unsure about
curl -s $BASE/v1/orgs/ORG/memories/review -H "$ADMIN"API reference
All routes are under /v1/orgs and require the admin credential (see Authentication). Replace :orgId, :roleId, :userId, :tagId, :keyId, :memId with real ids. Standard responses are JSON; creates return 201.
Organizations
POST /v1/orgs: create an org
Creates the org row and mints the org key (returned once). The owner is the authenticated admin (server-resolved; never client-supplied).
// Request
{
"name": "Helios Robotics",
"processor_provider": "openai",
"extract_model": "openai/gpt-5.5", // large model: pulls facts
"classify_model": "openai/gpt-5.4-mini" // small model: tags facts
}
// Response 201
{
"org_id": "uuid",
"name": "Helios Robotics",
"org_key": "mk_org_...", // shown ONCE, never re-displayed
"processor_provider": "openai",
"extract_model": "openai/gpt-5.5",
"classify_model": "openai/gpt-5.4-mini",
"owner_email": "you@company.com",
"created_at": 1730000000000
}GET /v1/orgs: list your orgs
Returns only the orgs owned by the requesting admin (solo ownership, scoped by X-Admin-Email). Never returns the org key hash.
GET /v1/orgs/:orgId: get one org
Returns the org's settings (name, provider, extract/classify models, timestamps).
PATCH /v1/orgs/:orgId: update org settings
Update name, processor_provider, extract_model, classify_model, etc. Only fields present in the body are changed.
DELETE /v1/orgs/:orgId: delete an org
Removes the org and its associated policy rows.
Member keys (the "Memory Keys" surface)
One org → many keys, one per team member. Each key is stamped with org_id + team_member_id; identity resolves from the key (no headers).
POST /v1/orgs/:orgId/keys: mint a member key
// Request
{ "team_member_id": "cindy@helios.example" } // email or any string
// Response 201
{
"key_id": "mk_org_...", // raw key: returned ONCE
"memory_key": "mk_org_...", // alias for clients
"org_id": "uuid",
"team_member_id": "cindy@helios.example",
"created_at": 1730000000000
}This single call provisions everything: the KV auth record, the org_keys row, the user upsert, and the Memory Keys mirror.
GET /v1/orgs/:orgId/keys?q=: list member keys (masked)
Returns all keys for the org, masked (prefix + last 4). ?q= filters by either key id or team_member_id (case-insensitive substring), so you can search a person's keys by their email or by a key fragment.
{ "keys": [
{ "key_id": "mk_org_...", "masked_key": "mk_org_••••1a2b",
"team_member_id": "cindy@helios.example",
"active": true, "revoked_at": null, "last_used_at": 1730000000000,
"created_at": 1730000000000 }
]}GET /v1/orgs/:orgId/keys/:keyId: reveal a raw key (admin-gated)
Returns the full raw key for an existing member key.
DELETE /v1/orgs/:orgId/keys/:keyId: revoke a member key
Revokes the key so it can no longer authenticate.
Roles
A role is a set of allowed tags. "*" = wildcard (all tags, incl. future) = super-role. Users hold many roles; effective scope is the union.
POST /v1/orgs/:orgId/roles: create a role
// Request
{ "name": "Sales", "allowed_tags": ["pricing", "client-status"] }
// Response 201
{ "role_id": "uuid", "org_id": "uuid", "name": "Sales",
"allowed_tags": ["pricing", "client-status"], "created_at": 1730000000000 }A super-role: { "name": "Executive", "allowed_tags": ["*"] }.
GET /v1/orgs/:orgId/roles: list roles
PATCH /v1/orgs/:orgId/roles/:roleId: update name and/or allowed_tags
{ "name": "Sales Team", "allowed_tags": ["pricing", "client-status", "renewals"] }DELETE /v1/orgs/:orgId/roles/:roleId: delete a role
Also removes any user→role assignments referencing it.
Users
POST /v1/orgs/:orgId/users: add a user
// Request
{ "email": "cindy@helios.example", "first_name": "Cindy", "last_name": "Lee",
"role_ids": ["ROLE_SALES", "ROLE_SUPPORT"] }
// Response 201
{ "user_id": "uuid", "email": "cindy@helios.example",
"first_name": "Cindy", "last_name": "Lee",
"role_ids": ["ROLE_SALES", "ROLE_SUPPORT"], "created_at": 1730000000000 }GET /v1/orgs/:orgId/users: list users with their roles
{ "users": [
{ "user_id": "uuid", "email": "cindy@helios.example",
"first_name": "Cindy", "last_name": "Lee",
"has_memory_key": true, "role_ids": ["ROLE_SALES"],
"created_at": 1730000000000 }
]}PATCH /v1/orgs/:orgId/users/:userId: update user / reassign roles
Independently settable: email, first_name, last_name, and role_ids. Passing role_ids replaces all of the user's role assignments.
{ "role_ids": ["ROLE_SALES", "ROLE_EXEC"] } // replace assignmentsDELETE /v1/orgs/:orgId/users/:userId: remove a user
Full cleanup: removes the membership, the member's keys, the Memory Keys mirror rows, and the KV auth records. No residue.
POST /v1/orgs/:orgId/role-assignments: assign roles by member id or key
Assign roles using either team_member_id or key_id (the key resolves to its member). The user row is auto-created if missing, then role assignments are replaced.
// Request (one of the two identifiers + roles)
{ "team_member_id": "cindy@helios.example", "role_ids": ["ROLE_SALES"] }
// or
{ "key_id": "mk_org_...", "role_ids": ["ROLE_SALES"] }
// Response
{ "user_id": "uuid", "team_member_id": "cindy@helios.example",
"role_ids": ["ROLE_SALES"] }POST /v1/orgs/:orgId/users/:userId/memory-key: mint/rotate a user's private mk_ key
Mints a standard mk_ key for the user's own private vault (separate from the shared org vault). Raw key returned once.
Tags (classification rules)
A tag teaches the small classifier what to label. examples are positive samples ("content that should get this tag"); negatives are counter-examples.
POST /v1/orgs/:orgId/tags: create a tag
// Request
{ "label": "pricing",
"question": "Is this about deal pricing, discounts, or contract value?",
"examples": ["We gave Acme 20% off the annual plan"],
"negatives": ["The Q3 product roadmap"] }
// Response 201
{ "tag_id": "uuid", "label": "pricing", "question": "...",
"examples": ["..."], "negatives": ["..."], "created_at": 1730000000000 }GET /v1/orgs/:orgId/tags: list tags
PATCH /v1/orgs/:orgId/tags/:tagId: update label, question, examples, or negatives
DELETE /v1/orgs/:orgId/tags/:tagId: delete a tag
Instructions (what to remember)
The org's retention policy. You see the base default and add specifics on top of it. anti_rules and exec_rules are the two extra retention modes.
PUT /v1/orgs/:orgId/instructions: upsert the policy
// Request: only fields present are written; omitted fields are preserved
{
"default_on": true, // keep the base retention behavior
"custom": "Always remember client commitments and deadlines.",
"anti_rules": [ // NEVER remember these
{ "match": "personal health details", "note": "privacy" }
],
"exec_rules": [ // remember only short, high-level versions
{ "match": "board-level financials", "note": "summary only" }
]
}default_on: keep the platform base retention (you can't edit the base, only toggle/extend it).custom: org-specific guidance added to the base.anti_rules: anti-memory, where matches here are never stored.exec_rules: executive-memory, where matches are stored only as short, high-level notes (ideal since a small model handles classification).
GET /v1/orgs/:orgId/instructions: read the policy
{ "org_id": "uuid", "default_on": true, "custom": "...",
"anti_rules": [ { "id": "uuid", "match": "...", "note": "..." } ],
"exec_rules": [ { "id": "uuid", "match": "...", "note": "..." } ],
"updated_at": 1730000000000 }Shared memories (view / seed / edit / audit)
The team-visible memory bank. Rows arrive two ways: extracted by the write-worker from chat turns, or seeded by an admin.
POST /v1/orgs/:orgId/memories: seed shared memories
Bootstrap the bank by hand. Each item is embedded with the same default model the write-worker uses, so seeds are retrievable alongside extracted memories. Admin-asserted seeds default to confidence = 1.0, so they skip the review queue.
// Single
{ "text": "Acme signed a 2-year contract at $48k/yr", "tags": ["pricing"] }
// Bulk
{ "items": [
{ "text": "Acme renewal due 2026-09", "tags": ["pricing","renewals"] },
{ "text": "Acme primary contact is Jane Doe", "tags": ["client-status"] }
] }Per-item embedding failures collect into errors[] (partial success) rather than failing the whole batch.
GET /v1/orgs/:orgId/memories: list / search shared memories
Returns shared memories with parsed tags and a computed sensitivity band (derived from which roles can see them).
PATCH /v1/orgs/:orgId/memories/:memId: edit a memory
Edit text, tags, or mark reviewed. Bulk-select + edit/delete is built on this in the dashboard.
DELETE /v1/orgs/:orgId/memories/:memId: delete a memory
Security model: RBAC at the data boundary
The retrieval path enforces permissions before memory ever reaches the model:
- The member's
mk_org_key resolves their identity → their roles → the union of allowed tags (resolveAllowedTags). A role with"*"grants everything. - When building context, MemoryRouter merges the member's private vault with the org's shared vault.
- Every shared memory is filtered: if its tags aren't permitted by the requester's roles, it's dropped and never enters the prompt.
- Untagged memory is public. Unknown users fail closed (untagged-only), so a misconfigured or unrecognized identity can't see sensitive tagged data.
This is enforced in code at the merge point, not via prompt instructions, so it can't be jailbroken by clever user input. Sensitive information is structurally unreachable for roles that don't hold the tag.
Safety model: audit trails + human approval
Audit: who touched what, who's seen what
Every create / update / delete / retag of a shared memory is recorded with the acting admin (X-Admin-Email) and a timestamp.
GET /v1/orgs/:orgId/memories/:memId/audit: full history for one memory.GET /v1/orgs/:orgId/audit?actor=&action=&since=&limit=: org-wide activity feed, newest first. Filter byactor, byaction(create|update|delete|retag), and bysince(timestamp).limitdefaults to 50 (max 500).
// GET /v1/orgs/:orgId/audit
{ "org_id": "uuid", "count": 2, "events": [
{ "action": "update", "actor": "you@company.com", "mem_id": "uuid",
"created_at": 1730000000000 },
{ "action": "create", "actor": "cindy@helios.example", "mem_id": "uuid",
"created_at": 1729999990000 }
]}Human approval: the AI doesn't get to silently guess
Tagging is done by a small, fast model. When its confidence is low, the memory is not trusted silently; it waits in a review queue for a human.
GET /v1/orgs/:orgId/memories/review?threshold=0.6&limit=50
Returns unreviewed shared memories whose tagging confidence is below threshold (default 0.6). Each row includes the memory, its tentative tags, confidence, computed sensitivity, and a pass link to the three-pass write trail (EXTRACT → TAG → RECONCILE) so the reviewer can see why it was tagged that way.
{ "org_id": "uuid", "threshold": 0.6, "count": 1, "review": [
{ "mem_id": "uuid", "text": "Maybe Acme wants a discount?",
"tags": ["pricing"], "confidence": 0.41, "author": "cindy@helios.example",
"acquired_at": 1730000000000,
"pass": { "pass1": "...EXTRACT...", "pass2": "...TAG...", "pass3": "...RECONCILE..." } }
]}POST /v1/orgs/:orgId/memories/:memId/review
Resolve a queued item. Either way it leaves the queue (reviewed = 1).
// Approve as-is
{ "action": "approve" }
// Approve but override the tags (edit-then-approve in one call)
{ "action": "approve", "tags": ["client-status"] }
// Dismiss without changing tags
{ "action": "dismiss" }Pass visibility: the auditable write trail
GET /v1/orgs/:orgId/debug/passes?limit=20&author=
Surfaces the auditable three-pass write trail and the retrieval RBAC trail, newest turn first:
kind: "write": Pass 1 EXTRACT (facts), Pass 2 TAG (per-fact tags + confidence), Pass 3 RECONCILE (ADD / UPDATE / NONE +mem_id).kind: "retrieval": the RBAC filter trail (allowed tags, wildcard flag, permitted/dropped counts, and a sample of dropped memories).
This is how you prove, after the fact, exactly what the AI remembered, how it tagged it, and what it was (and wasn't) allowed to retrieve.
Putting it together: the salary example
At Acme Inc., the whole team shares one memory, so who sees what actually matters. To keep sensitive facts in the right hands, they create a tag called compensation. From now on, whenever anyone's AI logs something about pay, whether a salary, a raise, or a bonus, the classifier automatically labels that memory with the compensation tag.
A tag on its own doesn't grant or deny anything; it just marks what a memory is. The access decision comes from how roles use it. Acme gives their Accounting and Executive roles permission to read compensation memories, and deliberately leaves it off their Sales and Support roles. Same shared memory, same facts flowing in, but who can pull a salary back out is now a property of your role, not a setting anyone toggles per-memory.
Here's how that plays out the day Cindy in Accounting logs a raise.
- Cindy (role: Accounting) tells her AI, "Dana in Engineering is being bumped to a $185k base next cycle." The write-worker extracts the fact, the classifier tags it
compensationwith high confidence, and it lands in the shared org vault, recorded in the audit trail with Cindy as the author. - An executive later asks their AI, "What's Dana's new salary?" Their
mk_org_key resolves the Executive role →"*"permits every tag → the memory is merged into context. The exec gets the answer instantly, with no digging through HR threads. - A salesperson asks their AI the exact same question. Their key resolves to the Sales role, which doesn't hold the
compensationtag, so the salary memory is dropped at the data boundary and never enters their prompt. Their AI simply doesn't know it. Same for Support. The sensitive fact is structurally unreachable for them, not hidden by a prompt that could be jailbroken, but never retrieved in the first place. - The safety net: a messy chat turn produces a low-confidence tag, because the classifier isn't sure whether a number is a salary or a deal size. Instead of guessing and possibly mis-filing comp data into a tag the whole sales team can see, it lands in the review queue. An admin opens it, sees the three-pass trail (what was extracted, how it was tagged, why), corrects the tag, and approves. The AI never silently committed the mistake.
That's team-wide AI memory that's unified (the exec's AI knows what Cindy logged), scalable (it's just keys, roles, and tags), secure (sales and support can't see salaries, enforced at the data layer), and safe (every change is audited, and the AI asks for a human when it's unsure).