Projects, Sites, Environments
Three levels of nesting under a tenant. Each owns specific data.
Project
A content workspace. One project's content is fully isolated from another project's, even within the same tenant.
A project owns:
- Content types + field definitions (the schema)
- Roles + permission grants
- API keys + webhooks
- Sites
- Environments
- Locales (via
LocaleDefinitionrows) - Audit log entries
POST /api/v1/projects
{ "name": "Marketing", "slug": "marketing" }
Project creation auto-provisions Production + Draft environments and assigns the creating user as Admin.
Site
A website inside a project. Sites carry hostnames, locales, default + supported locales, fallback chain, locale resolution strategy (prefix / subdomain / header), root route, and preview config.
A site owns:
- Hostnames (the routing key)
- Default locale + supported locales
- Fallback chain
- Root route entry (resolves at
/) - Preview config (
urlPattern+secret)
A project can have many sites. Each site owns its own subtree of TreeNode rows; siteId=null rows are global content shared across all sites.
POST /api/v1/projects/demo/sites
{
"name": "Marketing",
"slug": "marketing",
"hostnames": ["marketing.example.com"],
"defaultLocale": "en-US",
"supportedLocales": ["en-US", "fr-CA"],
"localeResolution": "prefix"
}
Environment
A copy of a project's content. V1 created a single Production environment automatically; V2 supports many. Each environment owns its own:
- Content entries (
ContentEntry.environmentId) - Tree nodes
- Field values
- Locale states
- Versions
- Route index
What stays shared at the project level (not copied between environments):
- Schema (content types + fields)
- Media assets
- Sites
- API keys + webhooks
- Roles + users
- Taxonomies
The schema is the contract — versions of it across environments would defeat the purpose.
POST /api/v1/projects/demo/environments
{ "name": "Staging", "slug": "staging", "cloneFromSlug": "production" }
cloneFromSlug copies all content from the source environment into the new one. Without it, the new environment is empty.
See Environments for promotion, locking, and the API in detail.
Routing
Path resolution runs at the (project, environment, site, locale) tuple:
- Hostname → site
- (site, locale, path) → RouteIndex row
- Entry → renderer
When a project has a non-default environment selected (via the admin UI's environment switcher or the X-Krios-Environment header), reads scope to that environment's content. The default-environment fallback is what serves the public delivery API.