Skip to main content
You don’t call these yourself — your AI client does, based on what you ask it in plain language. This page is here for when you want to know exactly what happened, ask for something specific, or debug a client that picked the wrong tool. The connector exposes 12 tools by default, plus 2 more when you grant author mode.

Conventions

Every tool returns pretty-printed JSON. A few patterns repeat:
  • ok: false is a report, not a crash. A rejected dataset sync or a drifted payload comes back as a normal result saying nothing was written. Your client should read it, not retry it.
  • outcome: "running" is not a timeout. Long operations hand the wait back rather than risk the call being dropped. The answer is a status call, never a second start.
  • URLs come back as exact strings. They’re also emitted as resource links. Never construct a Gainable URL by hand — an app’s own hostname isn’t reachable without a key the launcher injects.

Which app a tool acts on

Most tools take an optional projectId. Where it comes from, in order of authority:
  1. .gaia/project.json in the working folder — written when a tool returns a project anchor, read on the next session. This is what makes a folder remember its app.
  2. The tool that created or selected the app, earlier in the same conversation.
  3. apps_list, when you have neither.
The “currently active” app is remembered per account, not per conversation. On a client the connector can’t isolate — one that reconnects before every call, like Cowork — it refuses to guess and asks you to name the app instead. An active app also goes stale after 30 minutes of no use.

Lifecycle

project_setup

Returns a starter for a new Gainable project: a README.md, a CLAUDE.md for your domain context, data/ and apps/ folders, and the same context condensed into a block you can paste into a project’s Instructions box when there’s no folder to write to. Your client should offer this once, ask before writing anything, and read your existing context instead if you already have some.

apps_list

Lists the apps in the connected account, most recently used first.

apps_select

Sets which app the following tools act on. An ambiguous name comes back as a list of matches rather than a guess.

Building a new app

import

Turns an Excel or CSV file into a dataset attached to a project, ready to build from. Three ways to send the file, tried in this order:
1

upload_url — start here

The connector returns a URL; your client POSTs the file straight to it. No size limit, and the bytes never enter its context.
2

upload_chunk — when that can't connect

Some sandboxes block network egress. The base64 goes up in slices instead: no egress needed, no size limit, but it costs context.
3

fileBase64 — small files only

Simplest, but the whole string passes through your client’s context, so it’s capped around 40 KB.
A file that won’t fit one road means take the next road. It never means shrink, split, or convert the file. Sending a “cleaned up” workbook doesn’t import a simpler app — it imports a different and wrong one.
After attach, the build conversation starts with build.

build

Drives the whole new-app journey: a contract conversation through five phases — data → derives → seed → ui → autonomy — and then the deterministic build pipeline.
The harness can’t read files. If you point at a spec document, your client must paste its complete contents into message. A path or a one-line summary makes the server design from generic domain knowledge instead of your spec.Spreadsheets are the exception — they go through import, never into build as a spec.
action:"run" takes 90–180 seconds. Only outcome:"built" means done; anything else means keep watching with status.
“Build an app from this” always means a new app — even from the same spreadsheet you used an hour ago. Same data is not the same app, and silently attaching new data to an existing one is rarely what anyone wants. Only target an existing app when you name it: “add this to the Summit tracker”.

Changing a built app

chat

One turn: a server-side planner decides the change and the Build Agent applies it. This is the right tool for almost every edit — adding a field, fixing a view, changing a layout, adjusting behaviour. If the planner needs a decision it can’t make, the result is outcome:"needs_clarification" with a question. Answer it as a new chat call — the server recognises the pending clarification and merges it in.
Never re-send the same chat message. The turn keeps running server-side whatever happens to the connection, so a second call isn’t a retry: it runs the planner again and stacks a duplicate plan card someone has to clear by hand.

Shipping

publish

Deploys the app to its public https://<slug>.gainable.app URL — the same as the Publish button in the web app. Idempotent.

share

Reports the app’s links and who can actually open each one: the public URL when it’s published, and the in-Gainable launcher link for teammates. It mints nothing — it only tells you what exists.
Reach for this whenever you’re about to send someone a link. The failure worth avoiding is mailing a teammate a URL that 404s for them.

Datasets

import bootstraps an app from a spreadsheet. These four make a standalone dataset that a script keeps fresh — create once, sync forever. See Recurring data pipelines for the full workflow.

dataset_create

Seed with 5–20 real rows. The analyzer infers column types and picks the primary key from actual values, and that choice is frozen for the dataset’s life — a one-row seed produces a schema you cannot fix later.

dataset_schema

Returns the write contract: the required keys per sheet, the primary key, and the expected form of every field. Read this before writing any collector. Each field carries a writeAs describing exactly what to emit:

dataset_sync

Pushes a full snapshot, or re-fetches from upstream when no rows are given.
Every sync replaces everything. Rows absent from the payload are deleted. Send the complete set every time — if a collector failed halfway, abort rather than syncing what it managed to collect.

dataset_list

Returns each dataset’s provider, row count, last sync, and whether each source is syncable from a payload or only re-fetchable from upstream.

Author mode

Two extra tools appear only when you granted the code scope — the “Also let it write code in your apps” checkbox at sign-in. You can turn it on or off later from Account → Connections without reconnecting.
This is a scope, not a setting, for two reasons. A token that can build apps shouldn’t automatically be able to author code into them. And a model can’t over-reach for a tool that isn’t in its list, which is a harder guarantee than a description asking it not to.
Only ask for author mode when you actually want it. Ordinary requests — “add KPIs to the sponsors view”, “redesign the deals page”, “fix the kanban bug” — belong to chat, which is one cheap turn and does them well. Author mode reads conventions, reads files, and runs validators. It’s the exception, not the upgrade.

code_context

Step one: read before you write. manifest returns the app index — every file with its path, mode, and hash — plus the list of convention guides, with no file contents. Your client then fetches the two or three guides for the area it’s touching and the files it will actually change, rather than everything. app_file returns the content and a hash. That hash goes back as baseHash when you push.

code_push

Step two: check your work, then commit it. push validates the whole batch, writes it atomically, registers new view routes, commits to the app’s git history, and records the session in the project’s chat history — which is why summary is mandatory: it’s what the human sees alongside their own refinements. The rules that make this work:
  1. Only rw files are writableviews/, routes/, db/models/, public/css/, public/js/. Everything else is framework-owned and push rejects it.
  2. server.js belongs to the registrar. Write the view and the route; push wires them up.
  3. Push the whole stack of a feature in one call — view, route, and model together. The cross-file validators judge the batch as a whole, so a view whose route is “coming next” is correctly rejected.
  4. Treat validator output as binding. Those ~100 rules each encode a production incident someone already had. A rejection is the rule being right.
  5. Pass baseHash when editing. A stale rejection means the file changed server-side since you read it: re-read, re-apply, re-push. Don’t drop the hash to force it through.

When something goes wrong

Next steps

Recurring data pipelines

The full dataset workflow, end to end.

Overview

How the connector fits together.