Reference
Multi-tenancy
Every Aaly project is multi-tenant capable the moment it's created. Tenant scoping isn't a filter you write into a query — it's applied by the platform on every request, for every entity, without an opt-in.
The guarantee
A request carrying one tenant's token cannot read, list, update, or delete
another tenant's record. A cross-tenant read on a record by ID returns 403,
not a filtered-out 404 — the platform knows the record exists and is
refusing access to it, which is the property that makes "there is no query
you can write that returns another tenant's row" true rather than aspirational.
The two project-level flags
Set on the project (create_project / update_project via MCP, or in the
dashboard):
| Flag | Controls |
|---|---|
publicSignup | Whether the public can self-register a new tenant via /auth/signup at all. Default false — a new project starts locked down. |
multiTenant | Once publicSignup is true: whether every signup without a tenantId creates its own separate tenant (true, the multi-org SaaS case), or only the very first signup bootstraps one shared tenant and everyone after it must join with that tenantId (false, the default). Has no effect while publicSignup is false. |
{ "multiTenant": true, "publicSignup": true }
How a tenant gets created
publicSignup: false(a new project's default) — end users cannot create a new tenant via/auth/signupat all. You provision the first (and any later) tenant yourself viaPOST /tenants, which also creates that tenant's first user. This is the locked-down starting state, on purpose.publicSignup: true,multiTenant: false— the first sign-up without atenantIdbootstraps the project's one shared tenant; every sign-up after that must include that tenant'stenantIdto join it.publicSignup: true,multiTenant: true— every sign-up without atenantIdcreates its own new, separate tenant.- A sign-up with an existing
tenantIdjoins that tenant and shares its data with every other member of it, regardless of either flag above.
Each tenant also carries its own publicSignup flag (default true,
independent of the project-level one) — so you can lock one specific tenant
down to owner-provisioned users only, without affecting any other tenant on
the same project.
Full sign-up/sign-in request and response shapes are on Auth.
Why this matters more than it sounds like it should
The most common failure mode in an AI-generated backend isn't the first version — it's the fifth change, when a tenant filter that was correct in the original code quietly goes missing from one new route. Because scoping here is enforced by the platform reading the request's token, not by a query a person or a model wrote, there's no line of application code where that filter could be forgotten in the first place.