Conventions
Every tool returns pretty-printed JSON. A few patterns repeat:ok: falseis 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 astatuscall, 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 optionalprojectId. Where it comes from, in order of authority:
.gaia/project.jsonin 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.- The tool that created or selected the app, earlier in the same conversation.
apps_list, when you have neither.
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.
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.
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.
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.
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
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.
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 thecode 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.
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:
- Only
rwfiles are writable —views/,routes/,db/models/,public/css/,public/js/. Everything else is framework-owned and push rejects it. server.jsbelongs to the registrar. Write the view and the route; push wires them up.- 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.
- Treat validator output as binding. Those ~100 rules each encode a production incident someone already had. A rejection is the rule being right.
- Pass
baseHashwhen editing. Astalerejection 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.