Concepts
Tenancy
The second customer is where this gets decided. Either their data was separated from the first customer's on day one, or you are about to do a migration while both of them are live. Mycel picks the first option and makes you carry a small amount of ceremony from the start.
The model
Org
└── Project ← the isolation unit. Everything carries project_id
├── API key (msk_…) machine credential, pinned to ONE project
├── Wedges allowlist; empty means all
└── Clients your customers, and the connections they own
Member (human) → belongs to an Org → sees every project in itProject is the boundary. Tasks, events, connections, clients, knowledge, cases, records and schedules all carry project_id, and every read is filtered through one chokepoint. A row with no project_id is out of scope for everyone — it fails closed rather than becoming visible to all.
Client is not a tenant. Clients live inside a project and are the people you serve. Whether you give each customer their own project or keep them as clients in one is the real decision: separate projects for separate businesses or separate blast radii; clients within a project for customers of the same service.
Two credentials, two shapes
| Project API key | Member session | |
|---|---|---|
| Looks like | msk_… | msess_… |
| From | POST /v1/projects, returned once | POST /v1/auth/login, 12-hour expiry |
| Sees | Exactly one project | Every project in the org |
| Used by | Your product's server | The portal |
A key cannot be pointed at a different project, which is what makes it safe to put in your product's environment. Members are broader, so writes need to be unambiguous: a member in more than one project must send X-Mycel-Project: <id> or the write returns 400 specify a project (X-Mycel-Project header). Both are bearer tokens; there are no cookies anywhere in the kernel.
There is a third, entirely separate credential: the client portal session (mcli_…). It is deliberately not resolvable by the founder plane — presenting one to /v1/tasks is an unrecognised credential, not an authorised-but-empty request.
Roles
owner, admin, operator, viewer. Today the only route that enforces one is POST /v1/projects, which requires owner or admin. Do not assume viewer prevents approving something — it does not, yet. Enforce anything finer in your own proxy.
First boot
The kernel creates a default org, project and owner member with stable ids, so a laptop and a deployment behave the same. MYCEL_API_KEY is that project's key; MYCEL_OWNER_EMAIL and MYCEL_OWNER_PASSWORD are the portal login. Leave them unset and both are regenerated on every restart and printed to the console.
POST /v1/auth/login → { token: "msess_…", member, projects }
GET /v1/me → who am I, which projects, what role
GET /v1/projects
POST /v1/projects → { project, api_key } # owner/admin, key shown onceAdd a project per customer
curl -sX POST http://localhost:4000/v1/projects \
-H "authorization: Bearer $MEMBER_SESSION" \
-H "content-type: application/json" \
-d '{ "name": "acme-ltd", "wedges": ["invoice-chaser"] }'The wedges allowlist is worth using even with one customer. Empty means every wedge on disk is reachable; listing them means a half-finished wedge you dropped in for testing cannot be invoked against a paying project. 403 wedge "x" is not enabled for this project is a good error to see.
Failure modes
- 404 where you expected 403.Out-of-scope ids return not-found on purpose, so probing cannot confirm that another tenant's record exists. Do not read it as a bug.
- Everything vanishes on restart. Without
MYCEL_DATABASE_URL, tenants and their data are in memory. Isolation is enforced either way, but only Postgres makes it durable — and the isolation tests only run with it configured. - Member writes rejected with 400. More than one project and no
X-Mycel-Projectheader. - Traces are scoped like tasks, not separately.
GET /v1/tasks/:id/tracefolds the task's own event rows and takes the same scope check the task read does, so a trace is exactly as reachable as the run it describes — no third-party trace store, and nothing to keep in sync. Operator plane only: it carries costs and tool arguments, which the client plane deliberately never shows.
What to read next
Business OS & GTM — what a project is meant to become, and why blueprints package a whole business rather than a task.