App SDKan app that already knows the space.

Space Apps run inside Wakato with the window.wakato SDK: their own records, the Space's real tables and contacts, mail, the device, AI models and payments, each behind a capability.

A Space App is a small web app that lives in a Space: a check-in board, a quote builder, a content planner, a game. It runs in a sandboxed iframe on the web and inside the phone app, and it reaches Wakato only through one object, window.wakato, injected by the host. Every method returns a Promise.

The sandbox blocks fetch, XHR and WebSockets, and silently ignores alert, confirm and prompt. Everything the app needs goes through the SDK, and the app draws its own dialogs.

How apps are built

Apps are written by agents over MCP. Connect a coding agent with a Space key (see Use with Claude Code) holding the apps scope, and it works through these tools:

  1. get_app_sdk_reference and introspect_capabilities: the full SDK text and the capability keys and models this Space allows. Read them first.
  2. create_app for the first draft, then edit_app_code for every change (find-and-replace patches, never the whole document again). Saves are checked for syntax and runtime errors, and capabilities the code uses are declared for you.
  3. test_app: runs the app headless against sample records and reports a pass or fail, with optional click and expect steps.
  4. publish_app: goes live and records a version. rollback_app and get_app_logs are there when something breaks.

The App Builder in the Space is a manual editor for people. There is no AI inside it; the authoring happens through your agent.

Apps are Preact + HTM (or Phaser for games), styled with Tailwind, and must work at phone width (about 390px).

The standalone rule

An app's core job must work for a person clicking buttons while every agent's computer is off. If the app generates text, images, speech or video, the app calls wakato.gateway.* itself. Work that genuinely needs a machine (ffmpeg, long browser automation) is declared in app_schema.agent_features and queued with wakato.agent.requestWork, so any agent can pick it up.

The app's own records

wakato.data.* is the app's private store: JSON records grouped by model name.

const open = await wakato.data.query("orders", {
  filters: { status: ["open", "late"], total: { gt: 50 } },
  order_by: "created_at",
  ascending: false,
  limit: 20,
  count: true,          // adds a non-enumerable `total`
});

const rec = await wakato.data.insert("orders", { customer: "Ana", total: 120, status: "open" });
await wakato.data.update("orders", rec.id, { status: "shipped" });

const stop = wakato.data.onChange(({ event, model, record }) => redraw());

Totals are computed on the server: wakato.data.aggregate(model, { group_by, metrics: ["count", "sum:total"] }). Never sum a query result past 200 rows.

The Space's own things

An app is a view over the Space's real data, never a copy. These doors act as the person using the app, with exactly their permissions.

const { space, my_role } = await wakato.space.get();       // timezone included
const { members } = await wakato.space.members();

const { tables } = await wakato.tables.list();
const leads = tables.find((t) => t.plural_label === "Leads");
const { rows } = await wakato.tables.query(leads.id, {
  filters: { status: "new" },
  sort: [{ column_key: "created_at", direction: "desc" }],
  limit: 20,
});
wakato.tables.onChange(leads.id, ({ event, row }) => applyChange(event, row));

await wakato.contacts.create({ first_name: "Ana", email: "ana@example.com", tags: ["Waitlist"] });
await wakato.mail.send({ to: "ana@example.com", subject: "Thanks", text: "We got your request." });
await wakato.notify.send({ title: "New lead", body: "Ana joined the waitlist", to: "admins" });

Tables are addressed by id; read tables.columns(id) before writing, since a row's fields are the column keys. wakato.tables.myAccess(id) tells the app what this person may do so it draws only those buttons.

Beyond these, the SDK covers tasks, documents, forms, bookings, meetings, calendar, the shop and its orders, jobs, the inbox, Wakato Pass, the books, social, SMS and calls, search and the activity trail. get_app_sdk_reference lists every method.

The device

Each device door is asked of the person first (a consent bar, the camera or file picker, then the system prompt). A refusal rejects with declined, which is a normal answer.

try {
  const pos = await wakato.device.getLocation({ accuracy: "precise" });
  const { now } = await wakato.app.now();   // the server's clock, not the phone's
  await wakato.tables.insert(checkins.id, { lat: pos.lat, lng: pos.lng, at: now });
} catch (e) {
  showBanner("No location shared");
}

Also scanCode, capturePhoto, pickFile, share, vibrate and watchLocation. Call them from a button, never on load.

AI models

Calls are charged to the Space wallet and attributed to the app.

const { content } = await wakato.gateway.llm({
  messages: [{ role: "user", content: "Three subject lines for a spring sale" }],
});

const { job_id } = await wakato.gateway.run(videoEndpointKey, { prompt });
const job = await wakato.gateway.waitForJob(job_id);

gateway.listEnabledEndpoints() says which models the Space allows.

Money, the outside world and paper

  • payments.createLink / createInvoice hand people a pay page on the shop run from the Space. An app never takes a card itself.
  • http.fetch(url, opts) goes out through Wakato and may carry the Space's secrets as ${secret:NAME}, filled in at the edge and never shown to the app. Private addresses are refused; redirects are not followed.
  • render.pdf({ html }) turns markup into a PDF in the Space's assets.

The agent, in the app

  • wakato.agent.chat.* is the person's own line with the Space's agent: open, send, messages, onMessage, ask.
  • wakato.agent.bot.send(text) talks to the agent's External Reach on its website channel: instant, no machine to wake.
  • wakato.agent.requestWork(feature, inputs) queues a declared machine task.

Every door belongs to a capability, such as tables.read, mail.send, device.location or gateway.llm. An app built by an agent is deny-by-default: it holds only what app_schema.required_capabilities declares, and the owner can take any capability back in the app's details. A refused call says which capability is missing.

Running on a clock

Declare schedules and Wakato runs a named pipeline on that clock, as the Space owner, with nobody looking:

"schedules": {
  "daily_summary": { "cron": "0 18 * * 1-5", "timezone": "Europe/Paris", "pipeline": "summary" }
}

Up to 20 schedules; a bad cron is refused at save. Space automations also hear the app's records through the app_record_created, app_record_updated and app_record_deleted triggers.

Webhooks into an app

const { url } = await wakato.app.webhookUrl("stripe");

Give that URL to another system. Each POST becomes one record of model _webhook_stripe, which the app reads with data.query or watches with data.onChange (capability webhooks.receive, up to 256 KB per delivery and 120 a minute).

Try it safely

Someone who can change the app can switch on Try it safely in the app's header. Reads stay real; anything that writes, sends, spends or leaves Wakato answers { would: true } and does nothing. Only people who may edit the app can use it, checked on the server.

A guide for agents

An app can carry a plain-text guide for agents (up to 20,000 characters): what it is for, what each model holds, where to start, what never to do. Set it with agent_guide on create_app / update_app, or in the app's details under For agents. get_app returns it first to any agent that opens the app, so "work in the Quotes app" is enough.

Next

On this page