> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gavai.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-tenant model

> Why gavAI gives every workspace its own isolated runtime, its own Postgres database, and its own subdomain — and what you can rely on as a result.

export const PullQuote = ({children}) => <p className="gv-pull-quote">
    <span className="gv-pull-quote__mark" aria-hidden="true">“</span>
    <span className="gv-pull-quote__body">{children}</span>
  </p>;

export const PageMeta = ({audience, readingTime, prereqs, level}) => {
  const items = [audience && ({
    key: "audience",
    label: "AUDIENCE",
    value: audience
  }), readingTime && ({
    key: "time",
    label: "READ",
    value: readingTime
  }), level && ({
    key: "level",
    label: "LEVEL",
    value: level
  }), prereqs && prereqs.length > 0 && ({
    key: "prereqs",
    label: "PREREQS",
    value: prereqs.join(" · ")
  })].filter(Boolean);
  if (items.length === 0) return null;
  return <div className="gv-page-meta" role="group" aria-label="Page metadata">
      {items.map(item => <div className="gv-page-meta__item" key={item.key}>
          <span className="gv-page-meta__label">{item.label}</span>
          <span className="gv-page-meta__value">{item.value}</span>
        </div>)}
    </div>;
};

export const BrandCard = ({eyebrow, title, href, icon, badge, cta, featured = false, horizontal = false, children}) => {
  const classes = ["gv-card", featured && "gv-card--featured", horizontal && "gv-card--horizontal"].filter(Boolean).join(" ");
  const inner = <>
      {icon && <span className="gv-card__icon" aria-hidden="true">
          <Icon icon={icon} />
        </span>}
      <div className="gv-card__main">
        {(eyebrow || badge) && <div className="gv-card__meta">
            {eyebrow && <Eyebrow label={eyebrow} tone="muted" />}
            {badge && <span className="gv-card__badge">{badge}</span>}
          </div>}
        <h3 className="gv-card__title">{title}</h3>
        {children && <div className="gv-card__body">{children}</div>}
        {cta && <span className="gv-card__cta">
            {cta}
            <svg className="gv-card__cta-arrow" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <line x1="7" y1="17" x2="17" y2="7" />
              <polyline points="7 7 17 7 17 17" />
            </svg>
          </span>}
      </div>
    </>;
  return href ? <a className={classes} href={href}>
      {inner}
    </a> : <div className={classes}>{inner}</div>;
};

export const ArrowItem = ({children}) => <li className="gv-arrow-item">
    <span className="gv-arrow-item__arrow" aria-hidden="true">{"↳︎"}</span>
    <span className="gv-arrow-item__content">{children}</span>
  </li>;

export const ArrowList = ({children}) => <ul className="gv-arrow-list">{children}</ul>;

export const Eyebrow = ({label, tone = "default"}) => <div className={`gv-eyebrow gv-eyebrow--${tone}`}>
    <span className="gv-eyebrow__bar" aria-hidden="true" />
    <span className="gv-eyebrow__label">{label}</span>
  </div>;

<Eyebrow label="CONCEPT" />

<PageMeta audience="Developers, security reviewers" readingTime="6 min" level="Concepts" />

A SaaS platform that lets you build apps for *your* customers has to decide what isolation between those customers looks like. gavAI's answer is the strictest available: every workspace gets its own isolated runtime, its own managed Postgres database, its own subdomain, and its own configuration. Nothing is shared between workspaces at the runtime or data layer. By the end of this page you will know what's isolated, what you can rely on as a result, and how workspace identity shows up in API calls.

## What "multi-tenant" means here

Most multi-tenant platforms put many tenants in one runtime and one database, separated by a `workspace_id` column or row-level security. That works, but a single misconfigured query can leak across the boundary. gavAI does the opposite: each tenant gets dedicated infrastructure. A request that lands on `acme.gavai.app` cannot, by construction, touch any data that doesn't belong to Acme — because the runtime handling that request has no credentials, no connection string, and no DNS handle for any other tenant.

```mermaid theme={null}
flowchart LR
    subgraph Acme[Acme workspace]
      AW[Isolated runtime]
      AD[(Postgres)]
      AC[Config + secrets]
    end
    subgraph Globex[Globex workspace]
      GW[Isolated runtime]
      GD[(Postgres)]
      GC[Config + secrets]
    end
    UA[Visitor →<br/>acme.gavai.app] --> AW
    UB[Visitor →<br/>globex.gavai.app] --> GW
    AW <--> AD
    AW <--> AC
    GW <--> GD
    GW <--> GC
```

The Acme runtime has Acme's database connection string and only Acme's. The Globex runtime has Globex's. There is no shared data plane that a misconfigured query could traverse.

## What's isolated

<ArrowList>
  <ArrowItem>**Runtime** — a dedicated runtime instance that reads and writes only that tenant's data.</ArrowItem>
  <ArrowItem>**Data** — a separate managed Postgres database for that workspace's records and entities.</ArrowItem>
  <ArrowItem>**URL** — a customer-branded subdomain at `{tenant}.gavai.app`, or a custom CNAME attached via the Domains API.</ArrowItem>
  <ArrowItem>**Config** — theme, enabled capabilities, auth settings, and workflow definitions are all scoped per workspace and do not bleed between tenants.</ArrowItem>
</ArrowList>

## What you can rely on

Tenants cannot cross-tenant read, write, or invoke capabilities against another workspace's data. The runtime enforces this at the per-tenant boundary — a request arriving at `acme.gavai.app` can only touch the Acme database, Acme secrets, and Acme configuration.

<PullQuote>If a customer asks "can another tenant on the platform see our data?", the answer is no — because the infrastructure makes the question unanswerable in the affirmative, not because a row-level filter is correctly configured.</PullQuote>

## How workspace identity shows up in the API

Every authenticated request operates in the context of one workspace. There are two ways the API knows which one.

**OAuth session tokens.** A `gst_…` session token is bound to the workspace the user signed into. If you hold a session and need to act on a different workspace, call `POST /v1/workspaces/{slug}/switch` to re-scope the session.

**Workspace-scoped API keys.** A `gak_live_…` or `gak_test_…` key is bound to a single workspace at creation time. Machines and CI pipelines mint a key per workspace; the key cannot accidentally be used against another workspace because the binding is permanent.

The shape of the URL reflects the binding too: workspace-scoped endpoints live under `/v1/workspaces/{slug}/…`, and the slug in the URL must match the workspace the credential is bound to.

## Where to read more

For the full security architecture — threat model, data handling commitments, penetration-testing posture, incident response — read the security overview. The summary on this page is the *what*; the security pages are the *how* and *why*.

<Columns cols={2}>
  <BrandCard eyebrow="SECURITY" title="Security overview" href="/security/overview" icon="shield-halved">
    Threat model, key management, audit posture.
  </BrandCard>

  <BrandCard eyebrow="SECURITY" title="Multi-tenant isolation" href="/security/multi-tenant-isolation" icon="building-shield">
    The full breakdown of how the runtime, data, and identity boundaries are enforced.
  </BrandCard>

  <BrandCard eyebrow="REFERENCE" title="Workspaces API" href="/api-reference/resources/workspaces" icon="building">
    The endpoints that create, switch, and inspect workspaces.
  </BrandCard>

  <BrandCard eyebrow="INTERNALS" title="Architecture" href="/concepts/architecture" icon="cpu">
    How the Builder, the API, and the per-tenant runtime connect.
  </BrandCard>
</Columns>
