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 it

Project 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 keyMember session
Looks likemsk_…msess_…
FromPOST /v1/projects, returned oncePOST /v1/auth/login, 12-hour expiry
SeesExactly one projectEvery project in the org
Used byYour product's serverThe 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 once

Add 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-Project header.
  • 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.