# Multi-Tenancy {#multi-tenancy}


MQUAD uses a **hybrid multi-tenancy model** with subdomain-based identification for users and header-based isolation for service-to-service communication.


## Tenant Identification {#tenant-identification}



### Subdomain (User-Facing) {#subdomain-user-facing}


```
https://acme.mquad.local/api/v1/users
       └── subdomain
```


### Header (Service-to-Service) {#header-service-to-service}


```
X-Tenant-ID: <tenant-uuid>
```


## Tenant Middleware {#tenant-middleware}


The tenant middleware **must be first** in the FastAPI middleware stack:

```python
# backend/api/interfaces/middlewares/tenant_middleware.py
app.add_middleware(TenantMiddleware)  # FIRST

app.add_middleware(AuthMiddleware)     # SECOND
app.add_middleware(CORSMiddleware)     # THIRD
```

Middleware implementation:

```python
class TenantMiddleware:
    async def __call__(self, request, call_next):
        # 1. Extract from subdomain
        tenant = await self._resolve_from_subdomain(request)

        # 2. Extract from header (service calls)
        tenant_id = request.headers.get("X-Tenant-ID")

        # 3. Set in request state
        request.state.tenant_id = tenant_id or (tenant.id if tenant else None)

        return await call_next(request)
```


## Query Filtering {#query-filtering}


All database queries MUST filter by `tenant_id`:

```python
async def list_surveys(tenant_id: str):
    query = select(Survey).where(Survey.tenant_id == tenant_id)
    result = await db.execute(query)
    return result.scalars().all()
```


## RBAC Helpers {#rbac-helpers}


Use `common/rbac_helpers.py` — never check `is_super_admin` from JWT payload:

```python
from common.rbac_helpers import is_super_admin, is_host_admin

# Super admin has "super:admin" or "full:access" permission
if is_super_admin(user_permissions):
    # Full system access

# Host admin has "host:manage_tenants" permission
if is_host_admin(user_permissions):
    # Manage tenant-level settings
```


## Frontend API Calls {#frontend-api-calls}


Always use `getApiClient()` — never raw `fetch()`:

```typescript
import { getApiClient } from "@/lib/utils";
const apiClient = getApiClient(accessToken);
// Auto-handles auth + tenant headers
```


## Tenant States {#tenant-states}


| State | `is_active` | `is_dormant` | `is_trial` | Description |
|-------|-------------|--------------|------------|-------------|
| Active Trial | true | false | true | Trial period |
| Dormant | true | true | false | Trial expired |
| Active Paid | true | false | false | Active subscription |
| Inactive | false | false | false | Host deactivated |
| Suspended | false | false | false | Policy violation |
