Skip to main content
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:
page.json
{
  "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.

How the runtime dispatches a call

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

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.
form-block.json
{
  "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:
pinned-version.json
{
  "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: 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

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.

Where to go next