Skip to main content

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 LocaleDefinition rows)
  • 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:

  1. Hostname → site
  2. (site, locale, path) → RouteIndex row
  3. 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.