Multi-Tenant Model
How Tenancy Works
Each tenant is an organizations row. The platform resolves organization_id from
the incoming hostname on every request using a 4-strategy resolver:
custom_domainmatch — tenant’s own domain (e.g.app.{theirdomain}.com)primary_hostnamematch (e.g.app.gritcert.com)- Slug subdomain match (e.g.
tenantslug.gritcert.com) - Dev fallback (local only)
Isolation Guarantees
- Every DB query on a tenant route is scoped with
WHERE organization_id = ? - A student enrolled at two tenants has two separate enrollment rows, two certificates, and two compliance records. Data never crosses tenant boundaries.
- Platform admin routes (
/api/platform/*) are completely separate from tenant routes and require a different auth middleware.
Tenant Setup
When a new tenant is provisioned via the admin console:
organizationsrow created with slug and hostname- Course entitlements granted via
organization_courses - First admin invited via Clerk invitation flow
- DNS configured (subdomain on gritcert.com or custom domain)
Current Tenants
Tenant names, domains, and course entitlements are not published here. Use the GritCert Admin Console at admin.gritcert.com to view live tenant status.
Last updated on