> ## 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.

# Architecture

> The four surfaces of gavAI — Builder, public API, per-tenant runtime, and end-user subdomain — and how a request travels through them.

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 FeatureTable = ({columns, rows, featuredRow, caption, variant}) => <div className={`gv-feature-table ${variant ? `gv-feature-table--${variant}` : ""}`}>
    <table>
      {caption && <caption className="gv-feature-table__caption">{caption}</caption>}
      <thead>
        <tr>
          {columns.map(col => <th key={col.key} scope="col">
              <span className="gv-feature-table__header">{col.label}</span>
            </th>)}
        </tr>
      </thead>
      <tbody>
        {rows.map((row, i) => {
  const rowClass = [i === featuredRow ? "gv-feature-table__row--featured" : "", row.chosen ? "gv-feature-table__row--chosen" : ""].filter(Boolean).join(" ");
  return <tr key={i} className={rowClass}>
              {columns.map(col => {
    const v = row[col.key];
    if (variant === "tradeoff" && Array.isArray(v)) {
      const pill = col.key === "pros" ? "gv-feature-table__pill--pro" : col.key === "cons" ? "gv-feature-table__pill--con" : "";
      return <td key={col.key}>
                      {v.map((item, j) => <span key={j} className={`gv-feature-table__pill ${pill}`}>{item}</span>)}
                    </td>;
    }
    return <td key={col.key}>{v}</td>;
  })}
            </tr>;
})}
      </tbody>
    </table>
  </div>;

export const NumberedStep = ({n, title, children}) => <li className="gv-step">
    <span className="gv-step__index">{n}</span>
    <div className="gv-step__body">
      <h3 className="gv-step__title">{title}</h3>
      <div className="gv-step__content">{children}</div>
    </div>
  </li>;

export const NumberedSteps = ({children}) => <ol className="gv-steps">{children}</ol>;

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, technical buyers" readingTime="5 min" level="Concepts" />

gavAI has four surfaces: a web **Builder** where you compose pages, a public **REST API** that everything talks to, a per-tenant **runtime** that serves your published app, and the **end-user subdomain** your visitors land on. By the end of this page you will have a picture of what each surface owns, how they connect, and where data lives — enough to integrate gavAI from the outside or reason about an issue that crosses layers.

## The four surfaces, end to end

A change to your app travels through all four surfaces between the moment you drag a block and the moment a visitor sees the page. The shape:

```mermaid theme={null}
flowchart LR
    O[Owner] -->|drag-drop| B[Builder<br/>console.gavai.app]
    D[Developer / CI] -->|HTTPS| API[Public API<br/>api.gavai.app/v1]
    B -->|HTTPS| API
    API -->|publish| RT[Tenant runtime<br/>edge]
    V[Visitor] -->|browser| RT
    RT -.->|custom CNAME| V
    RT -.->|tenant subdomain| V
```

Each box is one surface. They are wired in one direction (Builder writes through the API; the API writes through to the runtime; the runtime serves visitors), but every surface talks to the same REST API — the Builder is just one client of it.

## Walk through a request

<NumberedSteps>
  <NumberedStep n="01" title="Builder">
    A non-technical owner opens the web Builder at `console.gavai.app` and composes pages by dragging and dropping PageBlocks. The Builder stores the result as a JSON page document — no code, no deploy pipeline.
  </NumberedStep>

  <NumberedStep n="02" title="Public API">
    The Builder, the CLI, and the official SDKs all talk to `api.gavai.app/v1`. Every operation — reading a page, patching a schema, publishing a deployment — goes through the same REST API. Authentication is a bearer token on every request.
  </NumberedStep>

  <NumberedStep n="03" title="Runtime">
    When you publish, your page document is deployed to a dedicated runtime instance for your workspace. The runtime renders pages server-side on each request, then rehydrates them into a WebAssembly bundle in the browser for interactivity.
  </NumberedStep>

  <NumberedStep n="04" title="Tenant subdomain">
    End users land at `{tenant}.gavai.app`. If you have attached a custom domain via the Domains API, they land at your CNAME instead. Either way, the runtime for your workspace handles the request.
  </NumberedStep>
</NumberedSteps>

## Where data lives

Every surface owns specific data. Knowing which store holds what makes it easier to reason about backups, exports, and what a breach in any one store would expose.

<FeatureTable
  caption="Which store owns what across the platform"
  columns={[
{ key: "surface", label: "Surface" },
{ key: "stores",  label: "What it stores" }
]}
  rows={[
{ surface: "Builder app document", stores: "The page tree and schema — the declarative description of your app." },
{ surface: "Workspace database",   stores: "Records written by your app's blocks and workflows (managed Postgres, one database per workspace)." },
{ surface: "Workspace files",      stores: "Files uploaded via the storeFile capability (object storage)." },
{ surface: "End-user auth pool",   stores: "Your app's end-user accounts and sessions (one user pool per workspace)." },
{ surface: "Secrets store",        stores: "API keys and credentials you configure for capabilities — encrypted per workspace." }
]}
/>

## Where AI fits

The same operations the Builder UI performs are exposed to AI hosts over MCP. The MCP server (`gavai mcp` via the CLI, or `@gavai/mcp-server` as an npm package) connects Claude Desktop, Cursor, and other MCP hosts to the public API. An AI connected over MCP can read your page document, compute a patch, and publish the result — same artifacts, same endpoints, different driver.

In addition to the read-and-patch flow, the runtime exposes a separate authenticated *agent surface*. This is a long-lived session a remote agent — yours or one of the platform-provided automations — holds open to read context, dispatch capability calls, and observe results without re-authenticating on every request. The agent surface is rate-limited per session, replayable (so an agent that gets disconnected mid-run can resume from the last acknowledged event), and audited end-to-end so a workspace owner can review what an agent did after the fact.

## Audit and replay

Every state-changing operation across the four surfaces — a page patch in the Builder, a publish through the API, a capability dispatch in the runtime, a domain attach — emits an audit record. The records form a hash-linked chain so tampering with a single entry breaks verification of every entry after it. Reads:

<ArrowList>
  <ArrowItem>**The audit log is per workspace.** No record from workspace A appears in workspace B's chain. The chain root is exposed to workspace owners but never shared across workspaces.</ArrowItem>
  <ArrowItem>**Records include the principal that initiated the action.** Workspace user, API token id, or agent session id — whichever was on the request. This is the same identifier that appears in the platform's request logs.</ArrowItem>
  <ArrowItem>**Records include the request id.** Use it to correlate an audit entry with the matching runtime log line and, where applicable, the downstream provider's request id.</ArrowItem>
  <ArrowItem>**Records are immutable once written.** The hash-chain head moves forward; entries are never edited in place. A correction is a new entry that supersedes an earlier one and names the entry it supersedes.</ArrowItem>
</ArrowList>

Most users will never read the audit log directly — the console surfaces a filtered view per resource. Programmatic access is through the [Logs API](/api-reference/resources/logs), which exposes the same records alongside runtime traces.

## Related

<Columns cols={2}>
  <BrandCard eyebrow="ISOLATION" title="Multi-tenant model" href="/concepts/multi-tenant" icon="building">
    How each workspace is isolated at the runtime, data, and identity layers.
  </BrandCard>

  <BrandCard eyebrow="AI" title="MCP overview" href="/mcp/overview" icon="plug">
    The MCP servers that expose the platform to Claude Desktop, Cursor, and friends.
  </BrandCard>

  <BrandCard eyebrow="REFERENCE" title="API introduction" href="/api-reference/introduction" icon="code">
    The REST API every surface talks to — auth, errors, pagination, idempotency.
  </BrandCard>

  <BrandCard eyebrow="UI MODEL" title="PageBlocks" href="/concepts/pageblocks" icon="layout-grid">
    The page document the Builder writes and the runtime renders.
  </BrandCard>
</Columns>
