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

# Capabilities

> Typed actions a page can invoke at runtime — payments, email, storage, queries, and auth.

export const ExpectedOutput = ({caption = "Expected output", children}) => <div className="gv-output">
    <div className="gv-output__label">
      <svg className="gv-output__icon" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
        <polyline points="15 10 20 15 15 20" />
        <path d="M4 4v7a4 4 0 0 0 4 4h12" />
      </svg>
      <span>{caption}</span>
    </div>
    <div className="gv-output__body">{children}</div>
  </div>;

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 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 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 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 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="CAPABILITIES" />

<PageMeta audience="Developers" readingTime="8 min" level="Overview" />

Pages need to *do* things — charge a card, send an email, store an upload, read a row from the database, identify the signed-in user. Capabilities are how. Each one is a named, typed action with a Zod-validated input schema, a typed output schema, and a backend the platform manages on your behalf. By the end of this page you will know which capabilities ship with every workspace, how the runtime validates and dispatches them, and how to invoke one from a block.

The split is intentional: the page declares *what should happen* (`collectPayment`, `sendEmail`); the runtime owns *how it happens* (Stripe Connect, the transactional email gateway, object storage). You never touch a third-party SDK or hold a vendor API key.

## A first invocation

Here is a CTA button on a page wired to open Stripe Checkout for a plan:

```json page.json theme={null}
{
  "block": "Cta",
  "props": {
    "headline": "Start your Pro trial",
    "cta_label": "Subscribe",
    "on_click": {
      "capability": "collectPayment",
      "args": {
        "plan_id": "plan_pro_monthly",
        "success_url": "https://app.acme.com/welcome",
        "cancel_url": "https://app.acme.com/pricing"
      }
    }
  }
}
```

That is the whole interaction. The page declares `collectPayment` by name with required args. When a user clicks the button, the runtime validates the args against the capability's input schema, dispatches to Stripe through the workspace's Connect account, and hands back a `checkout_url` for the page to redirect to. No SDK import, no API key in the page, no provider-specific code.

## The catalogue

Six capabilities ship with every workspace. Each has a unique `id`, a semver `version`, a typed input and output schema, and a backend that is either auto-provisioned or guided through onboarding.

<FeatureTable
  columns={[
{ key: "capability", label: "Capability" },
{ key: "what", label: "What it does" },
{ key: "backend", label: "Backend" },
{ key: "provisioning", label: "Provisioning" }
]}
  rows={[
{
  capability: "`collectPayment`",
  what: "Open a Stripe Checkout session for a published billing plan.",
  backend: "Stripe",
  provisioning: "Workspace completes Stripe Connect onboarding"
},
{
  capability: "`listPlans`",
  what: "Return the workspace's published billing plans with pricing.",
  backend: "Stripe",
  provisioning: "Plans defined in workspace billing config"
},
{
  capability: "`sendEmail`",
  what: "Send a templated email to a recipient.",
  backend: "Transactional email gateway",
  provisioning: "Workspace verifies a sending domain"
},
{
  capability: "`storeFile`",
  what: "Upload a file and return a signed or public URL.",
  backend: "Object storage",
  provisioning: "Auto-provisioned per workspace"
},
{
  capability: "`authUser`",
  what: "Return the currently signed-in end-user's profile.",
  backend: "End-user identity service (per-workspace user pool)",
  provisioning: "Auto-provisioned per workspace"
},
{
  capability: "`runQuery`",
  what: "Read rows from the workspace's Postgres database via a typed DSL.",
  backend: "Managed Postgres",
  provisioning: "Schema declared in page document"
}
]}
/>

<Columns cols={2}>
  <BrandCard eyebrow="CAPABILITY" title="collectPayment" icon="credit-card" href="/capabilities/collect-payment">
    Open a Stripe Checkout session for a known billing plan and redirect on success.
  </BrandCard>

  <BrandCard eyebrow="CAPABILITY" title="listPlans" icon="list" href="/capabilities/list-plans">
    Fetch the workspace's published plans with pricing, currency, and feature lists.
  </BrandCard>

  <BrandCard eyebrow="CAPABILITY" title="sendEmail" icon="envelope" href="/capabilities/send-email">
    Send a templated transactional email through the platform's email gateway.
  </BrandCard>

  <BrandCard eyebrow="CAPABILITY" title="storeFile" icon="file-arrow-up" href="/capabilities/store-file">
    Upload a file to the workspace's object-store bucket and get back a signed or public URL.
  </BrandCard>

  <BrandCard eyebrow="CAPABILITY" title="authUser" icon="user" href="/capabilities/auth-user">
    Read the signed-in user's profile, roles, and metadata from the active session.
  </BrandCard>

  <BrandCard eyebrow="CAPABILITY" title="runQuery" icon="database" href="/capabilities/run-query">
    Query the workspace's Postgres database with a typed filter DSL — no raw SQL.
  </BrandCard>
</Columns>

## How the runtime dispatches a call

When a block fires a capability, three things happen before any provider code runs:

```mermaid theme={null}
sequenceDiagram
    participant B as Block
    participant R as Runtime
    participant W as Workspace config
    participant P as Provider
    B->>R: invoke "sendEmail" { template, to, variables }
    R->>R: validate args against input schema
    R->>W: which provider runs sendEmail?
    W-->>R: transactional email gateway (domain verified)
    R->>P: dispatch send
    P-->>R: { message_id, queued_at }
    R-->>B: validated result envelope
```

<ArrowList>
  <ArrowItem>**Validation.** The runtime resolves the capability by name and parses `args` with its Zod input schema. Calls missing required fields or with wrong types are rejected as `invalid_input` before the backend sees them.</ArrowItem>
  <ArrowItem>**Resolution.** The runtime reads the workspace's backend config and routes the call. A `sendEmail` call goes to the email gateway; a `collectPayment` call goes to the workspace's Stripe Connect account.</ArrowItem>
  <ArrowItem>**Execution.** The provider runs, returns a typed result, and the runtime validates the output before handing it back to the block. A backend that returns the wrong shape surfaces as a runtime error rather than a silent success.</ArrowItem>
</ArrowList>

## Binding a capability to a block

Most capability calls live inside a block prop — typically `on_click`, `on_submit`, or `data_source`. The block declares which capability to call and what arguments to pass; the runtime evaluates form-field references against the submitted data before dispatch.

```json form-block.json theme={null}
{
  "block": "Form",
  "props": {
    "schema_table": "leads",
    "fields": ["name", "email"],
    "on_submit": {
      "capability": "sendEmail",
      "args": {
        "template": "lead-thankyou",
        "to": "ada@acme.com",
        "variables": { "first_name": "Ada" }
      }
    }
  }
}
```

The runtime validates `args` against `sendEmail`'s input schema before dispatching. Invalid arguments return an error envelope — the capability never fires.

## Across the iframe boundary

When a tenant page is embedded as an iframe (for example, on a marketing site), capability calls cross the iframe boundary via a postMessage bridge. The bridge serializes each call to the edge proxy at `{tenant}.gavai.run/_capability`, which forwards to the runtime. You do not write this plumbing — it is automatic.

## Versioning

Capabilities use semver. A page document can pin a capability version so a future v2 doesn't break it:

```json pinned-version.json theme={null}
{
  "capability": "collectPayment",
  "version": "1.x",
  "args": {
    "plan_id": "plan_pro_monthly",
    "success_url": "https://app.acme.com/thanks",
    "cancel_url": "https://app.acme.com/pricing"
  }
}
```

Omitting `version` uses the latest stable version of that capability.

## The error envelope

Every capability failure returns the same envelope so a block can handle errors uniformly regardless of which capability failed:

<ExpectedOutput caption="Error envelope shape">
  ```json error-envelope.json theme={null}
  {
    "ok": false,
    "error": {
      "code": "stripe_not_connected",
      "message": "This workspace has not completed Stripe Connect onboarding.",
      "details": {}
    }
  }
  ```
</ExpectedOutput>

Input-schema violations always use `code: "invalid_input"` and include a field-level `details` object describing which fields failed and why. Each capability page enumerates its other error codes.

## Custom capabilities

<Note>
  Custom capabilities are on the roadmap. Workspaces cannot yet register arbitrary capabilities. Webhook-backed and runtime-bound capability handlers are coming — treat custom capabilities as "coming soon" until the roadmap ships.
</Note>

## Where to go next

<Columns cols={2}>
  <BrandCard eyebrow="GUIDE" title="Accept payments" icon="credit-card" href="/guides/accept-payments">
    End-to-end: connect Stripe, define plans, wire a CTA to `collectPayment`.
  </BrandCard>

  <BrandCard eyebrow="GUIDE" title="Send email from a form" icon="envelope" href="/guides/send-email">
    Verify a sending domain and bind `sendEmail` to a Form block's `on_submit`.
  </BrandCard>

  <BrandCard eyebrow="CONCEPT" title="PageBlocks" icon="cube" href="/pageblocks/overview">
    The document model that wires blocks to capability invocations.
  </BrandCard>

  <BrandCard eyebrow="API" title="API reference" icon="code" href="/api-reference/introduction">
    Programmatic endpoints for plans, secrets, billing, and workspace config.
  </BrandCard>
</Columns>
