Multi-tenant SaaS
Serve many of your own end-users under one project — isolated memory, per-user metering, and fair rate limits.
If you're building a product on ThinkingRoot — an app where your users each get their own memory — one project can serve all of them. Isolation, metering, and rate-limiting are per end-user and enforced at the gateway, so you don't have to provision a project per customer.
The whole model rests on one header: X-TR-User. A request that carries it
is scoped to that end-user's namespace (u_{userId}); a request without it is
an unscoped project call that reaches your shared/main workspace.
One credential, many users
You hold one tr_sk_… project key. You never mint a key per end-user — you name
the end-user per request with X-TR-User. The gateway does the isolation.
The SDK does it by construction
The @thinkingroot/sdk splits the surface so the safe path is the
default:
import { thinkingroot } from "@thinkingroot/sdk";
const tr = thinkingroot({
gatewayUrl: process.env.TR_GATEWAY!, // https://api.thinkingroot.com
projectKey: process.env.TR_PROJECT_KEY!, // tr_sk_…
});
// Per-end-user memory — recall/store/capsule live ONLY on a scoped client.
const alice = tr.scope("alice");
await alice.store([{ statement: "Alice prefers dark mode" }]);
const hits = await alice.recall("what are Alice's UI preferences?");
// Project-level operations — branches, functions, flows, sources, streaming.
const ws = tr.workspace(); // defaults to "main"
const sources = await ws.sources();tr.scope(userId)sendsX-TR-Useron every call and pins it tou_{userId}. There is no parameter to reach another user's data — a blank id throws (fail-closed).tr.workspace(name)is unscoped (never sendsX-TR-User) and operates on a named workspace (defaultmain).
Isolation is enforced at the gateway
Even if a caller hand-rolls HTTP, the gateway's scope guard holds the line:
- A
u_*namespace is reachable only whenX-TR-Userauthenticates as exactly that user. No header, or a mismatched one, is rejected. - The shared brain (
main/shared) is readable by anyone, but a scoped user cannot write to it — promotion to shared is a separate, gated path. - A
tr_sk_…key resolves to exactly one project's daemon, so cross-project access is impossible by construction; this guards cross-end-user access within your project.
See Tenant isolation for the full model.
Meter each end-user
Every metered (2xx) call is tagged with its X-TR-User. Break usage down per
end-user — exactly what you need to bill or quota your own customers:
curl "$TR_GATEWAY/v1/projects/$PROJECT_ID/usage/by-user?days=30&limit=100" \
-H "Authorization: Bearer $SESSION_JWT"[
{ "user_id": "alice", "events": 1843, "units": 1843, "last_at": "2026-06-05T09:12:00Z" },
{ "user_id": "bob", "events": 271, "units": 271, "last_at": "2026-06-04T22:40:00Z" }
]Results are top-N by units (limit, max 1000) over the last days (max 365).
Only end-user-scoped calls appear — unscoped project traffic has no end-user to
attribute.
Fair rate limits
Rate limiting is per end-user within the project: a scoped request gets its
own bucket ({project}#{user}), so one noisy end-user can't drain a shared
allowance and starve the others. Unscoped project calls share the project
bucket.
Every response carries the budget, and a 429 tells you exactly when to retry:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1780000000
Retry-After: 10The SDK honors Retry-After automatically (bounded retries; configure with
maxRetries, default 2):
const tr = thinkingroot({ gatewayUrl, projectKey, maxRetries: 3 });
// A transient 429 is retried with the gateway's Retry-After; a persistent one
// throws HttpError(429) so you can surface it.Putting it together
A typical request flow for one of your end-users:
- Your backend authenticates your user (your auth, your session).
- It calls ThinkingRoot with your project key and
X-TR-User: <that user>— or justtr.scope(userId)with the SDK. - The gateway confines the call to
u_{userId}, meters it under that user, and rate-limits that user's own bucket. - You read
/usage/by-userto bill or quota them.
No per-customer provisioning, no key sprawl, and isolation you can't forget to turn on.