Python multi-tenancy
Multi-tenant ClickHouse analytics in Python, without a filter to forget
Declare the tenant column once on the dataset. Every query from your jobs, FastAPI endpoints, and agents runs inside one customer's data, and a query without a trusted tenant fails instead of returning everyone's rows.
Isolation
Fail-closed tenant key
Tenant source
Authenticated principal only
Runs in
Jobs, FastAPI, agents
One missing WHERE leaks a customer
When every query has to remember WHERE org_id = %s, the one that forgets returns another customer’s data. Code review is the only guard.
The tenant ends up in the request body
APIs that accept a tenant as a parameter let any caller ask for any tenant. The scope has to come from authentication, not input.
Each surface re-implements the rule
Background jobs, API routes, and AI agents all need the same scoping, and each one is a new place to get it wrong.
Declare it once
The dataset owns the tenant rule
Set tenant_key on the dataset and hypequery adds the tenant predicate to every query it plans. The tenant comes from a trusted execution context, never from the query itself.
- A query without a tenant fails with
tenant-required - Filtering on the tenant field from a query is rejected
analytics.for_tenant(...)binds a client to one tenant for a whole request- The same rule applies to relationships and joined datasets
datasets.py
Tenant key on the dataset
Ordinary Python you review and version with the rest of your app.
HTTP endpoints
Serve it with the tenant taken from auth
hypequery Serve resolves the tenant from the authenticated principal. No header, query parameter, or body field can set or change it.
Mark an endpoint tenant="required" to refuse callers without a tenant, or "forbidden" for internal, tenant-free routes.
A request body that tries to set a tenant is rejected with a validation error, so the scope can only narrow, never widen.
api.py
Tenant-scoped dataset endpoint
A resolver can return one tenant or none. It cannot grant several tenants or all of them.
Where teams usually get stuck
Questions teams ask
Row-level security for ClickHouse in Python
hypequery enforces tenant scope in the query it generates. You can still add ClickHouse row policies underneath as a second layer.
Multi-tenant FastAPI analytics
Dataset endpoints take the tenant from your authenticator, so every customer-facing chart is scoped the same way.
Tenant isolation for background jobs
Jobs pass a trusted tenant through ExecutionContext. A job that forgets fails loudly instead of aggregating every customer.
Same rules for AI agents
Agents connected over MCP run as one principal with one tenant, using the same datasets and the same scope.
Further reading
Go deeper where it actually helps
Dataset multi-tenancy
Tenant keys, execution context, and tenant-bound clients.
Open guide
Serve multi-tenancy
Resolve tenants from the principal and require them per endpoint.
Open guide
Authentication
Bearer tokens, API keys, and principals.
Open guide
Multi-tenant analytics in TypeScript
The same model on the TypeScript side.
Open guide
Next step
Add a tenant key to one dataset
Define one dataset with tenant_key, run a query without a tenant, and watch it refuse.
Open source, and it runs in your own backend. Want it hosted?