A capability invocation, in 10 lines
Here is aForm block that emails the visitor a thank-you when they submit:
page.json
sendEmail — along with the arguments. The runtime takes it from there: it validates the args against sendEmail’s input schema, dispatches the call to the transactional email gateway, and returns the result. The page author never writes an SDK call, never holds an email domain credential, never sees a network handle.
The to value here is a literal address. In a real Form binding, you’d reference the submitted email field with template syntax — see the Form block reference for the full binding pattern.
The mental model: a typed bridge
A capability is a typed bridge between the declarative block tree and a real-world side effect. The block says what should happen (sendEmail with these args); the runtime decides how it happens (call the email gateway with this domain, these credentials, this template).
Two properties fall out of that shape:
- Pages stay declarative. A block describes its intent; it never imports an SDK or holds a secret. The capability name and args are all the page knows.
- The runtime owns I/O. Validation, dispatching, and error handling live in one place. A call that fails validation never reaches the backend.
The 6 capabilities
Every workspace ships with these. Each has a Zod-validated input schema and a Zod-validated output schema; the runtime rejects calls and responses that don’t match. The per-capability reference page is the source of truth for each one’s exact input and output shape.Two ways a page invokes a capability
From a block (declarative)
Most invocations come from blocks. AForm block declares the capability that fires on submit; the runtime validates the arguments against the input schema before dispatching. The earlier example showed sendEmail; the same pattern works for collectPayment on a Cta button or storeFile on a file input.
page.json
Across the iframe boundary
When a tenant page runs inside an iframe — for example, embedded on a marketing site — capability calls cross the boundary via a postMessage bridge. The bridge serializes each call to the edge proxy at{tenant}.gavai.run/_capability, which forwards it to the runtime. You do not write this plumbing; it is automatic.
The shape of the call is identical to an in-page invocation; only the transport differs. A block that works at acme.gavai.app works embedded on acme.com without changes.
Versioning
Capabilities are semver’d. The runtime ships new versions as the platform evolves — sometimes adding optional fields, occasionally introducing breaking changes that get a new major version. If you want to pin a page to a specific release and avoid breakage when the platform ships a v2, add aversion field to the invocation:
invocation.json
version unpinned and the runtime uses the latest stable version. Pin to 1.x and the runtime guarantees a 1.x release for as long as one exists — useful for long-lived page documents you don’t want to revisit when major versions ship.