Start
Build with an AI agent
Send one link and the other person's AI agent knows how to build on dabadata, the realtime app backend: this page. The agent reads a short index written for it, learns the API, the rules and the safe way to write, then builds the app with your project's keys - and, through the hosted MCP server, can put that app inside ChatGPT or Claude, where people use it with live widgets.
The one link
To a person, send dabadata.com/docs/agents/. To their agent, the address that matters is https://dabadata.com/llms.txt: a plain-text index in the llms.txt format with the exact first steps and a link to every page as markdown. The rest of the kit:
| Address | What it is |
|---|---|
/llms.txt | The index: what dabadata is, "Start here" in seven steps, every feature group, every docs page with one line about it. |
/llms-medium.txt | Every docs page in brief: its description and each section's first paragraph, small enough for a chat assistant's browser. |
/llms-full.txt | Every docs page in one markdown file, in reading order, for agents that prefer to read everything at once. |
/docs/<page>/index.md | Each docs page as clean markdown, for example /docs/concurrency/index.md. |
/v1/openapi.json | Every HTTP route as OpenAPI 3.1: credentials, request and response shapes, error bodies. |
/mcp/<project id> | The project's own MCP server, once the definition turns it on. |
Give the agent the keys
Sign up (free, no card), open the console's Keys page and switch to the test environment: build there first. Copy the publishable key, and press Rotate next to the secret key to see a new one (a secret key is shown once). Put both in the project's .env, which the agent can read and your browser code never ships:
DABADATA_URL=https://dabadata.com
DABADATA_PUBLISHABLE_KEY=pk_test_... # safe in a browser: names the project, grants nothing
DABADATA_SECRET_KEY=sk_test_... # server only: bypasses every ruleThe console's Apps page has the same keys, the project's MCP address and a ready prompt built from them.
The prompt
Paste this into Claude Code, Cursor, Codex or any agent that can read the web and your files, and replace the last line with what you want:
Read https://dabadata.com/llms.txt and follow it. My dabadata project keys are in .env
as DABADATA_URL, DABADATA_PUBLISHABLE_KEY and DABADATA_SECRET_KEY (the test
environment). Define the tables and rules with PUT /v1/schema - a dry run first, then
apply. Use atomic operators or /v1/transact for anything that changes a count or a
balance, keep the secret key on the server, and tell me how to open the result.
Build: a shared expenses app for my flat - who paid what, and who owes whom.The agent comes back with a definition (tables, columns, one rule per operation), applies it, and writes the app around the JavaScript client. When a call is refused, the error carries the rule's explanation and a trace id, so the agent can fix the rule or the call instead of guessing.
Connect the MCP server
Every environment can be an MCP server. Turn it on in the definition (the agent can do this too):
"mcp": { "enabled": true, "name": "Team expenses" }The address is https://dabadata.com/mcp/<project id> for live and https://dabadata.com/mcp/<project id>_test for test. What a client may do depends on the credential it brings: the secret key gets the developer tools (build and debug the app, rules bypassed); an end user who signed in gets the app's own tools, under the rules.
Claude Code
claude mcp add --transport http dabadata https://dabadata.com/mcp/prj_example12345_test \
--header "Authorization: Bearer $DABADATA_SECRET_KEY"Cursor
In .cursor/mcp.json (keep the file out of git, it holds the key):
{
"mcpServers": {
"dabadata": {
"url": "https://dabadata.com/mcp/prj_example12345_test",
"headers": { "Authorization": "Bearer sk_test_..." }
}
}
}ChatGPT, claude.ai and Claude Desktop
This is how people use the app inside the chat, with tables, forms and records drawn as interactive widgets that update live. Add the MCP address as a custom connector (in ChatGPT under the apps and connectors settings, with developer mode on; in Claude under Settings, Connectors, "Add custom connector"). There is no key to paste: when the project has hosted end-user accounts turned on (auth.users.enabled), the chat starts an OAuth sign-in, the person signs in or signs up on the project's own page, and every call the chat makes is that person's, held to the rules and written to the trace with source: "mcp".
What the agent can do
With the secret key, the agent gets the developer tools besides the app's own:
| Tool | What it does |
|---|---|
describe_app | Tables, columns, tools, and for the caller whether each of read, create, update and delete is allowed, with the rule's explanation. The first call. |
get_definition | The definition in force, as JSON. |
apply_definition | Validate and apply a definition, exactly like PUT /v1/schema. With dryRun: true it answers the plan and changes nothing; every problem comes back at once. |
widget_put, widgets_list | Store a custom widget (one HTML document, served as ui://app/<name>) and point a table at it with mcp.tables.<table>.widget. |
transact | Several writes and checks in one all-or-nothing transaction, like POST /v1/transact. |
trace_recent | The newest trace rows: who did what, the rule, the decision and why. |
simulate | Would this user be allowed this operation? Nothing changes, nothing is traced. |
sql_read | Read-only SQL on the environment's tables. |
<table>_list, _get, _create, _update, _delete | The app's own tools, one set per table. These are what people in a chat get, under the rules. |
The safe-writes rule
Never read a value, compute a new one and write it back: two people doing it at once both write the same number and one update is lost. In the measurement, with 256 shoppers at once fewer than 2% of naive purchases came out right. Ask the database to do the arithmetic instead:
// Wrong: read, compute, write
const p = await db.get("products", id);
await db.update("products", id, { stock: p.stock - 1 });
// Right: an operator, computed inside the UPDATE
await db.update("products", id, { stock: db.dec(1) });
// Right: several rows, all or nothing, with a check that must hold
await db.transact([
{ op: "check", table: "products", id, expect: { stock: { $gte: 1 } } },
{ op: "update", table: "products", id, values: { stock: { $dec: 1 } } },
{ op: "insert", table: "orders", values: { product: id, qty: 1 } },
]);Add unique columns and checks such as "stock_nonneg": "stock >= 0" to the definition so the database refuses what must never happen. Safe concurrent writes has every operator, expect, ifAbsent and the transaction rules.
Start from a template
A template is a ready definition (tables, rules and the mcp block) plus its custom widgets. The curated ones are public:
curl https://dabadata.com/v1/templates{ "templates": [ { "slug": "expenses", "title": "Shared expenses", "description": "...", "tables": ["expenses"] } ] }Pick one when you create a project in the console (or the agent sends "template": "<slug>" with POST /v1/workspaces/:id/projects), then ask the agent to change it rather than start from nothing. The console's Apps page lists them too.
Example: shared expenses inside ChatGPT in five steps
- Connect your coding agent. Sign up, copy the test keys into
.envand add the MCP server to Claude Code as above (the definition needs"mcp": { "enabled": true }first: put it in on the console's Definition page, or let the agent do it over HTTP). - Describe the app. "Read https://dabadata.com/llms.txt. Make a shared expenses app: anyone signed in sees the expenses, adds their own, and edits or deletes only what they paid. Amounts are positive. People sign up by themselves. Show the expenses as a table in the chat."
- The agent applies it. It calls
describe_app, thenapply_definitionwithdryRun: true, reads the plan, and applies:It checks the result with{ "roles": ["admin", "member"], "tables": { "expenses": { "columns": { "title": { "type": "text" }, "amount": { "type": "float" }, "paid_by": { "type": "userId" }, "settled": { "type": "bool", "default": false } }, "checks": { "positive": "amount > 0" }, "rules": { "read": "authenticated", "create": "authenticated", "update": { "any": [{ "role": "admin" }, { "owner": "paid_by" }] }, "delete": { "any": [{ "role": "admin" }, { "owner": "paid_by" }] } } } }, "auth": { "users": { "enabled": true, "defaultRole": "member" } }, "mcp": { "enabled": true, "name": "Team expenses", "instructions": "Track shared expenses. Amounts are in euros.", "tables": { "expenses": { "widget": "table", "describe": "One row per expense" } } } }simulate("may a member delete someone else's expense?" - denied, with the reason) and, if you want a summary card, writes a custom widget withwidget_put. - Add the connector. In ChatGPT add a custom connector with
https://dabadata.com/mcp/prj_example12345_test. ChatGPT opens the project's sign-in page; you sign up with your email and are back in the chat. Your flatmates do the same. - Use it. "I paid 42.50 for dinner." The chat calls
expenses_createas you and shows the expenses table. When a flatmate adds a row from their own chat, it appears in yours while you watch: the widget holds a live, read-only subscription under your read rule. Every call is in the console's Trace, with who made it and the rule that let it through.
When it works on test, apply the same definition to live (the console's Definition page, or the agent with the live secret key) and add the live address as the connector.