# gtable > Build on gtable from a script, a terminal or an agent: the REST API, the MCP servers and the CLI, for the Studio and for every app, each acting as the person whose credential it is. Answer from these pages rather than from memory: they describe the current API version (/v1). gtable has two planes. The Studio (https://studio.gtable.app) is where builders define apps; its Builder API is the same for everyone. Each app runs in its suite's Runtime at https://{suite}.gtable.app/{app}, and its App API is generated per caller: the real OpenAPI document is https://{suite}.gtable.app/{app}/v1/openapi.json, masked to the tables and fields that caller can read. The App API reference here is the generic contract and names no real table or field. Never invent table or field names; read them from the caller's own schema. The OpenAPI documents at /docs/openapi/builder.json and /docs/openapi/app.json are authoritative for request and response shapes. Where a guide and the reference disagree, the reference wins. Every credential acts as one person and can never do more than that person. There is no admin key. Keys start with gt_ (keys issued before the rename start with dva_ and still work). gtable is pre-release and not open to sign-ups. The CLI is not published to npm yet: do not tell anyone to run it with npx. Every page is also served as Markdown: append `.md` to its URL. Cite the HTML URL, which is the canonical one. --- # gtable developer documentation Source: https://gtable.app/docs A gtable app is a database plus permissions. Everything a person can do in it, a machine can do too: through the **REST API**, through **MCP** for agents, and soon through the **CLI**. All three are generated from one definition, and every one of them acts as a person, with that person's permissions and nothing more. > **Info: gtable is in private pre-release** > > gtable is not open to sign-ups yet. These pages describe the platform as it runs today, for > the builders and teams already using it. If you were invited into an app, everything here > works with your own account: start with [Connect an agent](/docs/agents/setup) or the > [Quickstart](/docs/start/quickstart). ## Two products, two APIs gtable has two sides, and each has its own API. Knowing which one you need is most of getting started. | | **The Studio** | **An app** | | ----- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------ | | Who | builders, who define apps | the people invited into an app | | Where | `https://studio.gtable.app` | `https://{suite}.gtable.app/{app}` | | REST | the Builder API, `/v1` on the Studio | the App API, `/{app}/v1` on the suite | | MCP | `https://studio.gtable.app/mcp` | `https://{suite}.gtable.app/mcp`, one server for every app of the suite | | Can | create apps, change tables and fields, write permission rules, manage people | read and write the records you may see, save views, build your own pages | [Read how the two fit together](/docs/start/studio-and-runtime). [**Connect an agent**](/docs/agents/setup) One prompt to paste into Claude, ChatGPT, Cursor or VS Code. The agent connects itself and you sign in. [**Quickstart**](/docs/start/quickstart) Make a key, read a table, write a record, in five minutes with curl. [**App API reference**](/docs/app-api/records/list) Records, views, comments, files, SQL, undo and the change feed of an app. [**Builder API reference**](/docs/builder-api/apps/list) Organisations, suites, apps, schema, permission rules, people and domains. ## Three things that are true everywhere **A credential acts as a person.** An API key, an OAuth token and an agent's connection all belong to someone. A key made by someone in Sales sees what Sales sees. There is no service account with a view of everything. See [Authentication](/docs/start/authentication). **What you cannot read does not exist for you.** A field you may not read is not blanked: it is absent from the records, from the schema, from your OpenAPI document and from your agent's tools, and no error names it. See [Permissions](/docs/start/permissions). **Every change can be traced, and most can be undone.** Each write, from any surface, lands in the app's change history. See [Undo and history](/docs/api/undo). --- # The Studio and the Runtime Source: https://gtable.app/docs/start/studio-and-runtime gtable is two products built on one definition. The **Studio** is where an app is designed. The **Runtime** is where the people invited into it use it. The app's definition lives beside its data, so a published app keeps working without the Studio. ## Organisations, suites and apps ```text organisation who pays and who builds builders sign in at studio.gtable.app └── suite a set of apps with ONE login acme.gtable.app, or your own domain └── app tables, permission rules, pages acme.gtable.app/crm ``` A **suite** is a login boundary: its apps share one directory of people, one sign-in page and one address. The same person in two suites is two accounts. An **app** lives under its suite's address at its own path, so the CRM of the suite `acme` is `https://acme.gtable.app/crm`. ## The two planes | | **Studio (builder plane)** | **Runtime (app plane)** | | ----------- | ------------------------------------------ | --------------------------------------------------------------- | | Who | builders of an organisation | people invited into a suite's apps | | Signs in at | `studio.gtable.app` | the suite's own address | | REST | `https://studio.gtable.app/v1` | `https://{suite}.gtable.app/{app}/v1` | | OpenAPI | one public document, the same for everyone | one per app and per person, generated on request | | MCP | `https://studio.gtable.app/mcp` | `https://{suite}.gtable.app/mcp`, plus `/{app}/mcp` for one app | | Credentials | builder keys and OAuth grants | personal keys and OAuth grants, per suite | **The planes are a wall, not a filter.** A key made in an app is not a weaker builder key: the Studio does not recognise it at all (`401`), and the reverse is true too. An app credential can never be granted a builder scope such as `schema:write`, so no consent screen can even offer an end user's agent the power to change an app's structure. **A builder is not automatically a member of their own apps.** The Studio reaches every record of an app through its own routes (`/v1/apps/{appId}/tables/{tableId}/records`), with every write recorded under the builder's name. Inside the app's Runtime, a builder is a person like any other, and sees what their groups give them. ## Why an app has an API of its own Every app answers its own API under its own path, and that API is shaped like the person calling it: - `GET /{app}/v1/openapi.json` is generated on each request from the schema **you** can see: your tables, and only the fields you can read. A table you cannot open is not in the document. - The MCP tools you receive are built the same way. `tableId` is a closed list of your tables, and a record is typed with your readable fields only. - A suite on its own domain serves the same paths there, with no `gtable.app` in them: `https://portal.example.com/crm/v1`. That is why the [App API reference](/docs/app-api/records/list) on this site is a **generic contract**: the operations and their shapes, with no table or field named. Your real document is at `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential, and there is no anonymous version of it. The Studio's [Builder API](/docs/builder-api/apps/list) is the opposite case: it describes the platform, not anybody's data, so its document is public at `https://studio.gtable.app/v1/openapi.json` and is the same for every reader. ## One MCP server per suite A suite answers MCP at `https://{suite}.gtable.app/mcp`, one address for every app of the suite you belong to. Its first tool, `apps_list`, tells an agent which apps it can reach, and every app tool takes an `appId`. Adding an app to the suite does not make anybody reconfigure their client. `https://{suite}.gtable.app/{app}/mcp` serves a single app, which is the right address for an integration that must touch that app and nothing else. See [MCP servers](/docs/agents/overview). --- # Quickstart Source: https://gtable.app/docs/start/quickstart This takes five minutes and needs an app you can sign in to. Everything below acts as you: the key sees exactly what you see in the app, and nothing more. 1. **Make a key** In your app, open **Settings**, then **Connections**, then **For developers**, and create a key. Pick the scopes it needs (`records:read` and `records:write` for this guide). Copy it now: it is shown once. An app lives under its suite's address, at its own path: ```bash export GTABLE_TOKEN="gt_live_…" export GTABLE_APP="https://your-suite.gtable.app/your-app" ``` 2. **See what you can reach** ```bash curl "$GTABLE_APP/v1/schema" -H "Authorization: Bearer $GTABLE_TOKEN" ``` The answer lists your tables and, in each, the fields you can read. Table ids look like `tbl_8fj3kq2wmv5d` and field ids like `fld_3ns8wq5ke2pb`. Ids never change; names can. 3. **Read some records** ```bash curl "$GTABLE_APP/v1/tables/tbl_8fj3kq2wmv5d/records?limit=5" \ -H "Authorization: Bearer $GTABLE_TOKEN" ``` Every response has the same envelope. A record is keyed by field id, and `meta.fields` says which name each id has today, so you can show names without a second call: ```json { "data": [{ "id": "rec_4nqw8e2hzk7s", "fld_3ns8wq5ke2pb": "First record", "fld_7qk2mv9xp4ht": 20500 }], "meta": { "nextCursor": "eyJ…", "hasMore": true, "fields": { "fld_3ns8wq5ke2pb": "Name", "fld_7qk2mv9xp4ht": "Amount" } } } ``` 4. **Write one** Send an `Idempotency-Key` with every write. If the request is retried, and in a CI job it will be, you get the first answer back instead of a second row. ```bash curl -X POST "$GTABLE_APP/v1/tables/tbl_8fj3kq2wmv5d/records" \ -H "Authorization: Bearer $GTABLE_TOKEN" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"records":[{"Name":"Second record","Amount":1000}]}' ``` When you write, a field's exact name works in place of its id. A key that is neither is refused with a `422` naming it, so a typo never becomes an empty row. 5. **Hand it to an agent** In the same **Connections** screen, choose **Copy setup prompt** and paste it into Claude, ChatGPT, Cursor or VS Code. The agent adds the server and you sign in in your browser. See [Set up your agent](/docs/agents/setup). ## Where next - [Conventions](/docs/api/conventions): the envelope, pagination, filters and how records look on the wire. - [Retries and idempotency](/docs/api/idempotency): making a script safe to run twice. - [The App API reference](/docs/app-api/records/list): every operation of an app. - Your own OpenAPI document, which describes your tables: `curl "$GTABLE_APP/v1/openapi.json" -H "Authorization: Bearer $GTABLE_TOKEN"`. --- # Permissions Source: https://gtable.app/docs/start/permissions Every read and every write in an app, from the grid, the API, MCP or the built-in assistant, goes through the same permission check. There is no route around it, and no key that skips it. ## The model A builder gives each app **groups**, and each group its own permissions: on which tables it may do which operations, optionally narrowed to some rows and some fields. A person can be in several groups. Their access is the **union** of their groups, taken per field and per operation. There is no order, no deny and no override. Adding a group can only ever add access, and what no group allows, nobody has. No group is special to the platform: there is no admin group in an app. Administration happens in the Studio, on the other plane. ## Four questions, answered separately | Question | Example | | ------------- | -------------------------------------- | | Which tables | Support cannot see invoices at all | | Which rows | A rep sees the deals they own | | Which fields | Nobody outside Finance sees the margin | | Which actions | Read yes, delete no | ## What "cannot read" means A field you cannot read is **absent**, not blanked. It is not in the record, not in the schema you are served, not in your OpenAPI document, not in your agent's tool list, and not named in any error, because naming it would be the very leak the rule exists to prevent. A row you cannot see is answered as if it did not exist. That is also why an app's API reference on this site names no table and no field. Each person's document is generated from their own view of the app. See [Why an app has an API of its own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). ## Filters cannot be used to guess A filter can only narrow what you would see anyway. One case needs a rule: a field you may read on only **some** of your rows ("Finance sees the margin, on won deals"). Filtering or sorting by it would let you count rows outside your scope, so it is refused with `403`. `GET /v1/app` lists, per table, the fields you can see but not filter or sort by (`constrained`), so a client can grey them out before asking. ## Credentials narrow, never widen A key or an agent connection carries scopes (`records:read`, `records:write`, …). Scopes narrow what a credential may do; they never add to what its person may do. A key with `records:write`, made by someone who may only read, still cannot write. See [Authentication](/docs/start/authentication). ## Before changing a rule Builders can ask what someone would see, without granting anything. `rules.simulate` answers with the rows and fields a set of groups would get, broken down per group so you can see which group grants what: ```http POST https://studio.gtable.app/v1/apps/{appId}/rules/simulate ``` See [Ask what someone would see](/docs/builder-api/rules/simulate). --- # Authentication Source: https://gtable.app/docs/start/authentication Every credential gtable issues belongs to a person. An API key, an OAuth access token and an agent's connection all act **as** someone, with that person's permissions, narrowed by the scopes the credential was given. There is no ambient admin key and no service account. ## Send it as a bearer token ```http Authorization: Bearer gt_live_… ``` Keys start with `gt_`. Keys issued before gtable took its current name start with `dva_`; they keep working and need no change. An OAuth access token has the same format and is checked the same way. A credential is minted on **one plane** and is only accepted there. A key made in an app works in that app; an agent connected to a suite reaches the apps ticked when it was approved; a Studio key works on the Studio's Builder API. Presented to the other side, it is not a weaker credential, it is no credential at all (`401`). ## API keys | | In an app | In the Studio | | ------- | ------------------------------------------------- | ------------------------------------------------------------------ | | Where | **Settings**, **Connections**, **For developers** | the **Developer** page | | Acts as | you, in that app | you, as a builder of **one** organisation, chosen when you make it | | Expiry | 90 days | 90 days by default, or a number of days you choose, or never | A key is shown once, when it is made. Revoking it, in the same place, takes effect at once. **Keys are made and revoked by a person in a browser.** A key, or an agent connected by OAuth, cannot make another key or revoke one, and neither operation is an MCP tool. That way a key that leaks can be revoked and stays revoked: it cannot have minted a successor. **A Studio key acts in one organisation.** It cannot reach your other organisations, even though you can. ## Scopes | Scope | Lets a credential | Available in an app | | --------------- | ------------------------------------------------------------------------------------------------------ | ------------------- | | `records:read` | read the rows you can see, their comments and history, your saved views, SQL reads, the change history | yes | | `records:write` | create, change and delete rows, comment, upload files, undo | yes | | `schema:read` | see the tables and fields you have access to, and the app's rules | yes | | `schema:write` | change tables, fields and rules | Studio only | | `pages:read` | read pages, their code and history | yes | | `pages:write` | create, edit and publish pages: an app's in the Studio, your own in an app | yes | | `members:read` | see who is in an app and the directory | yes | | `members:write` | add people, change their groups, make invite links | Studio only | | `account:read` | see organisations, suites, apps, plan and usage | Studio only | | `account:write` | start, leave or delete an organisation, close your account | Studio only | A credential can only ever be **narrower** than the person behind it. A key with `records:write`, made by someone who may only read, still cannot write. Check what a credential is holding: ```bash curl "https://your-suite.gtable.app/your-app/v1/me" -H "Authorization: Bearer $GTABLE_TOKEN" ``` ## OAuth 2.1, for agents and tools MCP clients and other tools connect with OAuth, so nobody pastes a key into them. The Studio and every suite are each their own authorisation server. - **Discovery.** `/.well-known/oauth-authorization-server` (RFC 8414) and `/.well-known/oauth-protected-resource` (RFC 9728) on the Studio and on every suite's address. An unauthenticated MCP request answers `401` with a `WWW-Authenticate` header naming where to authorise, which is why a client can connect with nothing but a URL. - **Authorization code with PKCE**, `S256` only. Every client is public; there is no client secret, no `plain` method and no implicit flow. - **Device flow** (`urn:ietf:params:oauth:grant-type:device_code`), for a machine with no browser: a CI runner, a server over SSH. - **Dynamic client registration** (RFC 7591) at `/oauth/register`. A client that publishes its own metadata document needs no registration at all. - **Refresh tokens rotate.** Each refresh returns a new one. Presenting an old one again is treated as proof that somebody has a copy, and the whole grant is revoked. | Endpoint | Path, on the Studio or the suite's address | | -------------------- | ------------------------------------------ | | Authorize | `/authorize` | | Token | `/oauth/token` | | Device authorization | `/oauth/device` | | Registration | `/oauth/register` | | Revocation | `/oauth/revoke` | An access token lasts 30 days and its refresh token 90. ### Consent is a person in a browser The consent page shows the name the client gave itself **and** the address its answer will be sent to. Check that address before you approve. The scopes granted are the ones you tick, intersected with what you hold. On a suite, you also choose which of its apps the connection may reach; in the Studio, which organisation. ## Limits on signing in Signing in and the OAuth endpoints are limited per address per minute, and answer `429` with `rate_limited` when the limit is reached. Wait a minute and retry. --- # MCP servers Source: https://gtable.app/docs/agents/overview gtable speaks the Model Context Protocol over Streamable HTTP, with OAuth sign-in. An agent connected to it acts as the person who approved it, with that person's permissions. The fastest way to connect one is [Set up your agent](/docs/agents/setup); this page explains the addresses. ## The addresses | Address | What it reaches | Use it for | | ------------------------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------- | | `https://{suite}.gtable.app/mcp` | every app of the suite you are in, as far as you ticked them at sign-in | **the default**: Claude, ChatGPT, Cursor, a person's own agent | | `https://{suite}.gtable.app/{app}/mcp` | one app | an integration that must touch that app and nothing else | | `https://{suite}.gtable.app/mcp/code` | Code Mode, on the first app of the connection | an agent that writes code rather than calling tools one by one | | `https://{suite}.gtable.app/{app}/mcp/code` | Code Mode, on one app | the same, pointed at a chosen app | | `https://studio.gtable.app/mcp` | the Studio: organisations, suites, apps, schema, rules, people | a builder's agent that designs apps | | `https://studio.gtable.app/mcp/code` | Code Mode on the Builder API | the same, as code | A suite on its own domain serves the same paths there. ## Why one server per suite A suite is one address with several apps under it. One server for all of them means one entry in a person's client, and adding an app to the suite does not make anybody reconfigure. Three things make that work: - **`apps_list`** returns the apps this connection can open, with the id to pass back. An agent can ask "the CRM or the support desk?" rather than guess. - **`appId`** on every app tool. It is optional when the connection reaches exactly one app, and required beyond that. Leaving it out is answered with a sentence that names your apps. - **`tableId` is a closed list** of the tables you can read, across the apps of the connection. A model cannot even name a table it may not read. The suite server does not attach every table's record shape to its tool descriptions, which would resend every app's schema on every turn of a conversation. An agent calls `schema_get` on the app it cares about. The per-app server keeps the shapes in its tools, where they cost nothing. ## What the agent is told is what you may see The tool list is generated for each connection from the same masked schema the API uses. A table you cannot read is not listed: not refused when called, absent. A field you cannot read is not in any tool's input or output. Tools outside the connection's scopes are not offered. When you approve a connection to a suite, you choose which of its apps it may reach. An agent connected to the suite but ticked only for one app sees nothing of the others, and membership is checked again on every call, so the list can only narrow. ## The Studio's server `https://studio.gtable.app/mcp` is the builder plane: create an app, add tables and fields, write permission rules, ask what a person would see with `rules_simulate`, manage the directory. It is bound to the one organisation chosen on the consent screen. It also serves `skills_list` and `skills_get`, the manual an agent reads before building: how permissions are shaped, how to check what a schema change would break, how page code is edited. ## This documentation has one too These docs have their own MCP server at `https://gtable.app/docs/mcp`, with `search`, `fetch` and `list_pages` over the public pages. It needs no sign-in and reaches no data. --- # Set up your agent Source: https://gtable.app/docs/agents/setup The quickest way to connect an agent is to let it connect itself. gtable hands you a short prompt that carries your own address; the agent reads a guide, works out which client it is running in, adds the server and asks you to sign in. 1. **Copy the setup prompt** In an app, open **Settings**, then **Connections**, and choose **Copy setup prompt**. In the Studio, the same button is on the **Developer** page. 2. **Paste it into your agent** Claude Code, the Claude app, ChatGPT, Cursor, VS Code, Codex or any other client that speaks remote MCP. 3. **Sign in when your browser opens** The consent page is on your own address. You can tick only some of your apps: the agent then sees nothing of the others. For a suite, the prompt reads like this, with your suite's address in place of `your-suite.gtable.app`: ```text Connect this agent to your-suite.gtable.app by following https://your-suite.gtable.app/agent-setup.md. The MCP server is https://your-suite.gtable.app/mcp (Streamable HTTP, OAuth sign-in in my browser). Then confirm what I can reach, without changing any data. ``` For the Studio: ```text Connect this agent to the gtable Studio by following https://studio.gtable.app/agent-setup.md. The MCP server is https://studio.gtable.app/mcp (Streamable HTTP, OAuth sign-in in my browser). Then confirm what I can reach, without changing any data. ``` Setting up changes no data. The agent's last step is to list what it can reach, and it is told not to create, update or delete anything to test the connection. ## Set it up yourself These are the steps the guide gives the agent, for a suite at `https://your-suite.gtable.app/mcp`. For the Studio, use `https://studio.gtable.app/mcp` and the name `gtable-studio`. **Claude Code** Run this in a terminal, in the project you want it for: ```bash title="Terminal" claude mcp add --transport http your-suite https://your-suite.gtable.app/mcp ``` Then type /mcp inside Claude Code, choose your-suite and Authenticate. Your browser opens a sign-in page. **Claude app** In Claude (web or desktop), open Customize, then Connectors, then Add custom connector. Paste and choose Connect. On a team plan an owner may have to add it first. **ChatGPT** Custom connectors depend on your ChatGPT plan and on what your workspace admin allows. In the desktop app: Settings, MCP servers, Add server. Choose Streamable HTTP, paste , then Authenticate. **Cursor** Add this to .cursor/mcp.json in your project (or \~/.cursor/mcp.json for every project): ```json title=".cursor/mcp.json" { "mcpServers": { "your-suite": { "url": "https://your-suite.gtable.app/mcp" } } } ``` Then press Connect next to your-suite in Cursor's MCP settings. **VS Code** Add this to .vscode/mcp.json in your project. VS Code uses "servers", not "mcpServers": ```json title=".vscode/mcp.json" { "servers": { "your-suite": { "type": "http", "url": "https://your-suite.gtable.app/mcp" } } } ``` Then press Start above your-suite in that file and sign in. **Codex** Run this in a terminal: ```bash title="Terminal" codex mcp add your-suite --url https://your-suite.gtable.app/mcp codex mcp login your-suite ``` Restart Codex so it loads the new tools. **Other** Any client that speaks remote MCP: add a Streamable HTTP server at . It signs in with OAuth on its own: no key to paste. A client that only takes a key can use an API key, made in Settings, Connections, For developers. ## The guide your agent reads Every suite serves this guide at `/agent-setup.md` on its own address, and the Studio at `https://studio.gtable.app/agent-setup.md`, each with its own address written in. It is public and names nothing about your apps. This is the one a suite serves: ````markdown # Connect this agent to your-suite.gtable.app This server gives an agent the apps of this workspace: their tables and records, as far as the person's own permissions allow. Every call acts as the person who signs in, never more. - MCP server: https://your-suite.gtable.app/mcp (Streamable HTTP) - Sign-in: OAuth, discovered from the server. The person signs in and approves in their own browser. - Canonical guide: https://your-suite.gtable.app/agent-setup.md ## Rules - Follow the person's request and your host's permission policy. This guide authorizes nothing beyond that. - Never ask for a password, API key, OAuth token or callback URL in the conversation. - Setup changes no data. Do not create, update or delete anything to test the connection. - If a working connection to https://your-suite.gtable.app/mcp already exists, reuse it. If its sign-in expired, use its reconnect action. - Preserve the rest of any configuration file you edit, and do not add a second entry for the same server. - Do not disable host approvals or change tool auto-grants. ## 1. Find out which client you are Identify the actual client (Claude Code, the Claude app, ChatGPT, Cursor, VS Code, Codex, another one), not just the model. Ask the person only if it cannot be determined. Use only that client's recipe below. ## 2. Add the server Use `your-suite` as the server name. ### Claude Code Run this in a terminal, in the project you want it for: ```sh claude mcp add --transport http your-suite https://your-suite.gtable.app/mcp ``` Then type /mcp inside Claude Code, choose your-suite and Authenticate. Your browser opens a sign-in page. ### Claude app In Claude (web or desktop), open Customize, then Connectors, then Add custom connector. Paste https://your-suite.gtable.app/mcp and choose Connect. On a team plan an owner may have to add it first. ### ChatGPT Custom connectors depend on your ChatGPT plan and on what your workspace admin allows. In the desktop app: Settings, MCP servers, Add server. Choose Streamable HTTP, paste https://your-suite.gtable.app/mcp, then Authenticate. ### Cursor Add this to .cursor/mcp.json in your project (or ~/.cursor/mcp.json for every project): ```json { "mcpServers": { "your-suite": { "url": "https://your-suite.gtable.app/mcp" } } } ``` Then press Connect next to your-suite in Cursor's MCP settings. ### VS Code Add this to .vscode/mcp.json in your project. VS Code uses "servers", not "mcpServers": ```json { "servers": { "your-suite": { "type": "http", "url": "https://your-suite.gtable.app/mcp" } } } ``` Then press Start above your-suite in that file and sign in. ### Codex Run this in a terminal: ```sh codex mcp add your-suite --url https://your-suite.gtable.app/mcp codex mcp login your-suite ``` Restart Codex so it loads the new tools. ### Other Any client that speaks remote MCP: add a Streamable HTTP server at https://your-suite.gtable.app/mcp. It signs in with OAuth on its own: no key to paste. A client that only takes a key can use an API key, made in Settings, Connections, For developers. If you cannot change the client's configuration yourself, give the person that client's steps above, at most four, and wait. A prompt alone does not install a connector: do not say it is connected. ## 3. Let the person sign in The first connection opens a consent page on https://your-suite.gtable.app. The person signs in and approves there. They may tick only some of the workspace's apps: respect that choice. A client may need a reload or a new session before the tools appear. ## 4. Verify, without changing anything Call `apps_list` (the apps this connection reaches and their tables), then `me_get` on one of them. Report the apps and tables back to the person. Report separately: server configured, sign-in completed, tools visible in this conversation, and any step left to the person (a reload, an approval). If a step is not verified, say so. Being listed in a configuration file is not proof of a connection. ```` --- # Tools Source: https://gtable.app/docs/agents/tools Every MCP tool is an operation of the REST API, generated from the same definition. A new endpoint is a new tool the day it ships, and the two cannot describe different things. ## Names A tool is named after the operation it mirrors, with the dots turned into underscores: `records.list` is `records_list`, `schema.get` is `schema_get`, `rules.simulate` is `rules_simulate`. Every page of the [App API](/docs/app-api/records/list) and [Builder API](/docs/builder-api/apps/list) references names its tool. Operation names never change silently: renaming one would break every agent and script written against it, so an old name is deprecated first and kept until a published date. ## Hints Each tool carries the MCP annotations a client uses to decide when to ask you first: | Hint | Set when | | ----------------- | ---------------------------------------------------------------------- | | `readOnlyHint` | the operation only reads | | `destructiveHint` | it deletes, or rewrites many rows at once: a bulk delete, a SQL update | | `idempotentHint` | calling it twice is the same as calling it once | A client that asks before destructive calls therefore asks at the right moments. ## Lists A list tool returns `{ data, meta }`. While `meta.hasMore` is true, pass `meta.nextCursor` back as `cursor` for the next page. The tool descriptions ask the model to narrow its filter first, because walking pages spends a conversation's context on paging rather than on thinking. Lists that are short by nature (a record's comments, your saved views of a table) take a `limit` and have no cursor. ## Errors come back as results A refusal is returned as a tool result marked `isError`, with the same `code` and `message` the REST API would give, not as a protocol error. The model reads it and adjusts. See [Errors](/docs/api/errors). ## What is not a tool, on purpose | Operation | Why not | Instead | | ---------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | | Uploading a file | a tool call is JSON a model writes, and a file is bytes | upload with the REST API, then attach the returned descriptor with an ordinary record write, which is a tool | | Making or revoking a key | a credential that can mint credentials turns one leak into a permanent one | a person, in a signed-in browser | | A person's set-password link | it is a way to become that person | handed over by a builder, from the Studio | ## Large writes Saving a page's code sends the whole file. Through MCP that means the model writes every byte as tokens, which is slow and expensive for a large page. For anything big, use the REST API (`PUT …/pages/{pageId}/code`) from a script. --- # Code Mode Source: https://gtable.app/docs/agents/code-mode Code Mode serves the same API as two tools instead of one per operation. It suits an agent that writes code well and has a task of many steps: one script replaces a conversation of fifty tool calls, each costing a round trip and a share of the context window. | Address | Document it works against | | ------------------------------------------- | ------------------------------------------------------------------ | | `https://{suite}.gtable.app/{app}/mcp/code` | that app, as you see it | | `https://{suite}.gtable.app/mcp/code` | the first app of the connection; use the per-app address to choose | | `https://studio.gtable.app/mcp/code` | the Builder API | ## The two tools **`search`** takes a JavaScript filter and runs it over the OpenAPI document of what this connection may call. Operations outside its scopes are removed from the document before it is handed over, so `search` can never describe something `execute` would refuse. **`execute`** runs one script. The script calls operations through the API, as you, with your permissions, and returns a result. ## The sandbox The script runs in an isolated Worker with **no outbound network**: it can reach gtable's API through what it is given and nothing else on the internet. | Limit | Value | | -------------------- | ----------------------------- | | Run time | 30 seconds | | API requests per run | 100 | | Script size | 64 KB | | Result size | 256 KB, truncated beyond that | Credentials cannot be made or revoked from a script, any more than from a tool: those operations answer only a person in a browser. --- # Safety and undo Source: https://gtable.app/docs/agents/safety ## Writes happen as you A write from an MCP client is applied at once, with your permissions, exactly as if you had made the call yourself. It is not held for approval: asking first is your client's job, and most clients ask before running a tool marked destructive. An agent can never do more than you can, because it has no identity of its own to borrow. ## Everything lands in the history Every write, from any surface, is recorded with who made it, when, and how it arrived: the grid, a key, or an agent. A create, edit or delete of records can be undone from the change history, in the app or through the API (`changesets.mine`, then `changesets.undo`). One request is one entry, so an agent's import of five hundred rows is one thing to take back. See [Undo and history](/docs/api/undo). Deleting a table, a field, an app or a suite asks for its current name as `confirm`, from an agent as from a person, so a loop over the wrong list is refused rather than obeyed. ## gtable's own assistant proposes The assistant built into gtable works differently: it **proposes** a change, shows you what it would do, and you apply it. Its proposals are recorded the same way, and an applied one is undone the same way. ## Give an agent no more than it needs - Connect it to the **suite**, and tick only the apps it should reach. - For a production integration, prefer a single app's address (`/{app}/mcp`) or a key with only the scopes it uses. - Revoke a connection from the same **Connections** screen; it stops at once. ## Setting up changes nothing The [setup guide](/docs/agents/setup) tells an agent to verify its connection by reading only (`apps_list`, then `me_get`), and never to create, update or delete anything to test it. --- # REST API overview Source: https://gtable.app/docs/api/overview gtable has two REST APIs, one per plane, built from the same registry as the MCP tools and the CLI. They share their conventions: the envelope, pagination, errors, idempotency and versioning work the same on both. | | **Builder API** | **App API** | | ----------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------- | | Base URL | `https://studio.gtable.app/v1` | `https://{suite}.gtable.app/{app}/v1` | | For | builders: organisations, suites, apps, schema, rules, people, domains | anyone invited into an app: records, views, comments, files, SQL, undo | | Credentials | Studio keys, Studio OAuth | your key in that app, or an OAuth connection to the suite | | OpenAPI | public, `https://studio.gtable.app/v1/openapi.json` | per person, `https://{suite}.gtable.app/{app}/v1/openapi.json` | | Reference | [Builder API](/docs/builder-api/apps/list) | [App API](/docs/app-api/records/list) | ## The App API is generated for you An app's OpenAPI document is generated on every request from the schema the caller can see. Its `tableId` parameter is a closed list of your tables, and each table's record is typed with only the fields you can read. Someone with a different role gets a different document from the same URL. There is no anonymous copy. So the [App API reference](/docs/app-api/records/list) here is the **generic contract**: every operation and its shape, with no table and no field named. To generate a client, or to give an agent exact types, fetch your own: ```bash curl "https://your-suite.gtable.app/your-app/v1/openapi.json" \ -H "Authorization: Bearer $GTABLE_TOKEN" ``` The generic contracts are also published here, for tools that want a file: [`/docs/openapi/app.json`](/docs/openapi/app.json) and [`/docs/openapi/builder.json`](/docs/openapi/builder.json). ## Builders reach data through the Studio A builder is not a member of their own apps by default, and does not need to be. The Builder API reads and writes any app's records under `/v1/apps/{appId}/…`, with every write recorded under the builder's name. See [Data, as the builder](/docs/builder-api/data/records-list). ## Before you write code - [Conventions](/docs/api/conventions): the envelope, records on the wire, pagination. - [Errors](/docs/api/errors): the codes to switch on. - [Retries and idempotency](/docs/api/idempotency): make a write safe to repeat. - [Versioning](/docs/api/versioning): what can change, and how you are told. --- # Conventions Source: https://gtable.app/docs/api/conventions ## The envelope Success and failure have one shape each, and a list is an object with an array in it: ```json { "data": [], "meta": { "nextCursor": null, "hasMore": false } } ``` ```json { "error": { "code": "validation_failed", "message": "…", "details": {} } } ``` Switch on `error.code`, never on the message. See [Errors](/docs/api/errors). ## Records on the wire A record is keyed by **field id**, because an id survives a rename and a name does not. The id-to-name map travels with every list, in `meta.fields`: use it to show a name, never to key on one. ```json { "data": [{ "id": "rec_4nqw8e2hzk7s", "created_at": "2026-09-05T16:53:39.058Z", "fld_dd7kbtsjuhdc": "First record", "fld_jx99fwfvhv3x": 42 }], "meta": { "hasMore": false, "nextCursor": null, "fields": { "fld_dd7kbtsjuhdc": "Name", "fld_jx99fwfvhv3x": "Margin" } } } ``` Anything that points at a record or a person reads back with both halves, so nobody needs a second request to show it: | Field type | Reads back as | Written as | | ----------------------------- | ------------------------------------------------------------------------ | --------------------------------- | | link | `[{ "id", "label" }]`, the label being the linked record's primary field | ids, or the same objects | | user, created by, modified by | `[{ "id", "label" }]`, the label being a name or an email | ids | | attachment | `[{ "id", "name", "size", "mime", "url", … }]` | the descriptor an upload returned | Two values catch people: a **percent** holds the fraction (`0.15` is 15%), and a **duration** holds milliseconds (one hour is `3600000`). A select holds an option **id**, which is what lets its label be renamed. Dates are ISO 8601 in UTC. ## Writing by name On `records.create` and `records.update`, a key may be a field's id **or its exact name**, resolved before anything is stored. A key that is neither is refused with a `422` that names it, so a typo is loud rather than an empty row. `id` and `created_at` are ignored, so echoing a record back is safe. Email, URL and phone fields keep the text you send. A suspect format is still saved, and the response lists it under `warnings` (`code: "suspect_format"`). A warning is a successful save: do not retry it. ## Pagination Opaque cursors, `limit` capped at 200, never offsets: an offset over a table people are writing to skips and repeats rows silently. Pass `meta.nextCursor` back as `cursor` until `meta.hasMore` is false. A cursor the list did not give out is a `422`, not a silent restart. ```http GET /v1/tables/tbl_8fj3kq2wmv5d/records?limit=100 GET /v1/tables/tbl_8fj3kq2wmv5d/records?limit=100&cursor=eyJ… ``` Lists that are short by nature (a record's comments, a page's versions, your views of a table) take a `limit` and have no cursor; the reference says which. > **Note** > > Sorting and paging do not yet agree. The cursor follows creation order, so paging a list you > also **sort** can skip or repeat rows at page edges. If you need a sorted, complete list, fetch > the pages unsorted and sort them yourself, or ask for a saved view. ## Inputs are strict An unknown query parameter or body key is a `422` that names it, never silently ignored. A `limit` must be a whole number written in digits. ## Replacing a whole document A permission document, one group's permissions and a page's code are replaced whole. Reading one gives you its `revision` (or a page's version); send it back as `baseRevision` (or `baseVersion`) and a save made after somebody else changed it is refused with `409` instead of erasing their change. Without it, the last write wins. `records.update` takes an optional `baseVersion` too: the `changesetId` of the last write you saw on that record. ## Deleting Deleting an organisation, a suite, an app, a table or a field takes its current name as `?confirm=`, the same proof the Studio asks a person to type; closing an account takes its email address. A call without it is refused and deletes nothing. ```http DELETE /v1/apps/{appId}/tables/{tableId}?confirm=Old%20deals ``` Deleting a table or a field is also refused, with the list of what reads it (rules, views, pages, other fields), until you send `acknowledge=true`. --- # Errors and limits Source: https://gtable.app/docs/api/errors Every failure has the same shape, on both APIs and in MCP tool results: ```json { "error": { "code": "forbidden", "message": "…", "details": {} } } ``` Switch on `code`. The codes are stable across versions; the message is a sentence for a person and may be reworded. ## Codes | Code | HTTP | Means | | ------------------- | ---- | -------------------------------------------------------------------------------------- | | `unauthenticated` | 401 | No usable credential, or one from the other plane | | `forbidden` | 403 | Your permissions or your credential's scopes do not cover this | | `not_found` | 404 | No such thing, or none you can see | | `conflict` | 409 | Something changed underneath you, or an idempotency key was reused for another request | | `validation_failed` | 422 | The request is wrong; the message names the parameter or key | | `rate_limited` | 429 | Too many requests: wait and retry | | `payment_required` | 402 | The plan or the credit does not allow it: waiting will not help | | `not_implemented` | 501 | Not available on this deployment | | `internal` | 500 | Our fault. Retry with the same `Idempotency-Key` | ## What an error never contains The name of a field you cannot read. "You cannot read `salary`" tells you a field called `salary` exists, which is exactly what the rule was hiding. Errors name the operation, never the thing being protected, and a hidden field gets the same answer as a field that never existed. Likewise, something you cannot reach (missing, deleted, another organisation's) is the same `404` with the same sentence, whichever it is. ## Saved, with warnings Email, URL and phone fields keep the text you send. A suspect format still succeeds, and the response carries a `warnings` array (`code: "suspect_format"`, with the record, the field and a message) for the fields you can see. Do not retry a write because it has warnings. Invalid numbers, impossible dates, unknown choices and values over a field's limit fail with `validation_failed`. Send numbers or numeric strings, and ISO dates such as `2026-09-22`; timestamps must carry a timezone. A failed create batch saves none of its rows. ## Why 402 and 429 differ A rate limit is about speed and clears on its own. A spend limit is about money and does not. Conflating them would tell someone who ran out of credit to wait, and they would wait forever. ## Rate limits today - **Signing in and the OAuth endpoints** are limited per address per minute and answer `429` with `rate_limited` beyond that. - **`/v1` and MCP**, on both planes, are limited per credential (per person for a browser session): 1,200 requests a minute and 300 per 10 seconds. A request with no credential is limited per address, 600 a minute. Beyond that the answer is `429` with `rate_limited` and a `RateLimit-Policy` header describing the limit. A well-behaved client backs off and retries; with an `Idempotency-Key`, a retried write is safe. --- # Retries and idempotency Source: https://gtable.app/docs/api/idempotency Networks drop answers, and scripts retry. Without protection, a retried `POST` creates a second row. Send an `Idempotency-Key` header with every write, to an app or to the Studio: ```bash curl -X POST "https://your-suite.gtable.app/your-app/v1/tables/tbl_8fj3kq2wmv5d/records" \ -H "Authorization: Bearer $GTABLE_TOKEN" \ -H "Idempotency-Key: 6f1c2b8e-1d2a-4f0e-9a51-2f1d7e0c3b44" \ -H "Content-Type: application/json" \ -d '{"records":[{"Name":"Imported row"}]}' ``` ## What happens | You send | You get | | ----------------------------------------------- | --------------------------------------------------------------------------- | | a new key | the request runs; its answer is kept for 24 hours | | the same key, the same request, within 24 hours | the first answer again, with `Idempotency-Replayed: true`, and nothing runs | | the same key with a different request | `409 conflict`: a key names one request | A key is any unique string up to 255 characters; a UUID per logical operation is the usual choice. Keys are scoped to the plane, the person and the operation, so two people can never collide. ## Which calls take it Every write the reference marks with an `Idempotency-Key` header. A `PUT` or a `PATCH` repeated is already the same as once, so the Studio keeps no answer for those: sending the same one twice is safe on its own. ## Together with retries - Retry `429`, `500` and network failures with the **same** key and an increasing delay. - Do not retry `4xx` answers other than `429`: the request itself is wrong, and will be wrong again. - A write that succeeded with `warnings` is a success. Do not retry it. --- # Versioning Source: https://gtable.app/docs/api/versioning ## The API The version is in the path: `/v1`. Every response names the contract version that answered in the `x-gtable-api-version` header, and the OpenAPI document carries the same number in `info.version`. - **Additive changes ship without a new path**: a new operation, a new optional parameter, a new key in a response. Write clients that ignore keys they do not know. - **Operation names never change silently.** They are also the MCP tool names and the CLI commands, so renaming one would break agents and scripts without warning. An operation being retired is marked `deprecated` in the reference and answers with a `Deprecation` header and a `Link` to what replaces it. Once it has a date after which it may stop answering, a `Sunset` header says when. No operation is deprecated today. - Renaming or removing a key in a body or a query goes through the same deprecation. - A breaking change to many operations at once would be a new path, `/v2`. ## Pages A page's code is stored as **immutable versions**. Every save builds a new version and keeps the old ones; publishing points the page at one of them. So: - rolling back is publishing an older version, with no rebuild; - two people using a page during a publish each keep the version they loaded; - `pages.versions` lists every version with who saved it, when, and whether it built. ```http GET /v1/apps/{appId}/pages/{pageId}/versions POST /v1/apps/{appId}/pages/{pageId}/publish { "version": 4 } ``` To keep editing from an older version, read its code with `GET …/code?version=4` and save it as a new version. ## Records Every write appends to the app's history: who, when, how it arrived, and the record before and after. See [Undo and history](/docs/api/undo). --- # Filtering, views and SQL Source: https://gtable.app/docs/api/filtering-and-sql Every way of narrowing a list is AND-ed with your permissions: it can only narrow what you would see anyway. ## Saved views Ask for a saved view instead of rebuilding its filters. It is one parameter, it stays correct when someone edits the view, and it is usually a much smaller answer. ```http GET /v1/tables/tbl_8fj3kq2wmv5d/records?viewId=viw_e4ks9mz2bh7w ``` A view belongs to whoever made it; you are handed your own views and nobody else's. ## Search, filters, sorts and where | Parameter | Takes | | --------- | --------------------------------------------------------- | | `search` | a substring, matched over the text fields you can read | | `filter` | a filter tree as JSON, the same shape a saved view stores | | `sorts` | a JSON array of `{ "fieldId", "direction" }` levels | | `where` | a SQL-style condition, in the dialect below | ```http GET /v1/tables/tbl_8fj3kq2wmv5d/records?filter={"id":"root","kind":"group","mode":"and","children":[{"id":"c1","kind":"condition","fieldId":"fld_7qk2mv9xp4ht","op":"gt","value":50000}]} GET /v1/tables/tbl_8fj3kq2wmv5d/records?where=Stage = 'won' AND Amount > 50000 ``` URL-encode the values in real requests. A field in a filter, a sort or a grouping may be named by id or by exact name. One you cannot read is refused with a `422` that lists the fields you can, and never names the hidden one. A field you can read on only some of your rows cannot be filtered or sorted on (`403`), because counting matches would reveal rows outside your scope. `GET /v1/app` lists those fields per table under `constrained`. To turn a sentence into a filter tree, `filters.suggest` takes the sentence and returns a tree you can inspect before you use it. ## SQL `POST /v1/sql/query` reads; `POST /v1/sql/execute` changes many rows. Nothing you send is run as written: the statement is parsed, every column is checked against what you may read, every value becomes a bound parameter, and your permissions are added to the `WHERE`. ```sql SELECT stage, COUNT(*) AS n, SUM(amount) AS total FROM Deals GROUP BY stage ORDER BY n DESC SELECT name, amount FROM Deals WHERE "Expected close" > CURRENT_DATE LIMIT 20 UPDATE Deals SET probability = 100 WHERE stage = 'won' DELETE FROM Deals WHERE stage = 'lost' AND amount < 1000 ``` What the dialect covers: - one statement over one table, named by name, plural or id; fields by name or id, with double quotes around a name that has spaces; - `AND OR NOT`, comparisons, `[NOT] LIKE`, `[NOT] IN (…)`, `IS [NOT] NULL`, `[NOT] BETWEEN`, both forms of `CASE`; - `LENGTH LOWER UPPER TRIM ABS ROUND COALESCE SUBSTR DATE STRFTIME IFNULL INSTR REPLACE`, and `COUNT SUM AVG MIN MAX` with `GROUP BY`; - on a link field, `HAS ANY ('rec_…', …)`, `HAS ALL (…)` and `IS EMPTY`. No `JOIN`, no subquery, no `INSERT` (use `records.create`). A `SELECT` returns at most 1,000 rows. A column you cannot read is "Unknown column", the same as one that does not exist. A field you can read on only some rows reads as `NULL` on the others, inside sums and expressions too. An `UPDATE` or `DELETE` touches up to 500 rows, each through the ordinary permission checks, and is **one** change in the history, undone in one step. Without a `WHERE` it is refused unless you send `confirm: true`. `dryRun: true` returns the count and the first ids and changes nothing. `sql.query` refuses a write and `sql.execute` refuses a `SELECT`, so a key holding only `records:read` gets exactly what its scope says. Builders have the same two operations on any app, under `/v1/apps/{appId}/sql/…` on the Studio. --- # Undo and history Source: https://gtable.app/docs/api/undo Every write to an app, from the grid, a key, an agent or a builder, is recorded: who, when, how it arrived, and each record before and after. A record's history, the change list and undo all come from that one record of changes. ## Undo a change **In an app**, `changesets.mine` lists the changes you made that you can still see, newest first, and says for each whether it can be undone (`revertible`) and, when not, where it is changed back instead. `changesets.undo` takes one back. ```http GET https://your-suite.gtable.app/your-app/v1/changesets POST https://your-suite.gtable.app/your-app/v1/changesets/{changesetId}/revert ``` **In the Studio**, a builder lists and undoes every change of an app: ```http GET https://studio.gtable.app/v1/apps/{appId}/changesets POST https://studio.gtable.app/v1/apps/{appId}/changesets/{changesetId}/revert ``` What to expect: - **One request is one change.** A batch of 500 creates, a bulk delete or a SQL update is one entry, undone in one step. - **All of it or none of it.** Every row is put back in one transaction. A row deleted since cannot be restored and is listed in `skipped` with the reason (`status: "partly reverted"`); any other refusal undoes nothing. - **A deleted record comes back whole**: same id, same autonumber, its links to records that still exist, and its comments. - **Someone changed it since?** The first attempt is refused with a sentence naming the field and whether it was you. Send `confirm: true` to go ahead. - **An undo is itself a change**, so undoing an undo is just undo again. - Record creates, edits and deletes can be undone this way. Comments, saved views, pages and the app's tables and fields are changed back elsewhere, and the list says where. ## A record's history `records.history` returns one entry per field that actually moved, plus when the record was created, commented on or deleted. A field you cannot read produces no entry you can see. ## The trash Deleting a table or a field is not undone from the change list, but it is not lost either: what it held goes to the app's trash for thirty days. `trash.restore` puts it back with the same ids, values, links and the rules that named it. ```http GET https://studio.gtable.app/v1/apps/{appId}/trash POST https://studio.gtable.app/v1/apps/{appId}/trash/{trashId}/restore ``` A deleted app can be restored for thirty days too, with `apps.restore`. --- # Events and webhooks Source: https://gtable.app/docs/api/events ## Pull what changed The events feed lists what changed in an app, oldest first, from the last event you saw: ```http GET https://your-suite.gtable.app/your-app/v1/events?since=chg_01J…&limit=200 ``` Pass `meta.nextCursor` back as `cursor` while `meta.hasMore` is true, and keep the last id you processed for next time. `since` and `cursor` both mean "the last change I saw". Events go through your permissions like a read: - a row you cannot see produces no event for you; - a field you cannot read is absent from the events you do get; - a deleted row is reported as the bare fact that it went, never its former contents; - someone who is not a member of the app is refused, rather than handed an empty feed. Builders follow changes from the Studio's change list (`GET /v1/apps/{appId}/changesets`); there is no builder events feed. ## Webhooks **Not available yet.** gtable does not send webhooks today. When it does, deliveries will follow [Standard Webhooks](https://www.standardwebhooks.com/): a signed id, timestamp and body, verifiable with any Standard Webhooks library, retried with backoff. The events feed above is how a webhook consumer will catch up after being down, so code written against it keeps its value. --- # What this app is, to you Source: https://gtable.app/docs/app-api/app/get `GET https://your-suite.gtable.app/your-app/v1/app` The app's name and address, which surfaces its builder switched on, its theme, YOUR groups in it, and the pages your groups may open. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Never the app's whole group list: who else exists is the builder's business, not an app user's. Requires the `schema:read` scope. Operation `app.get`. MCP tool `app_get`. CLI, once published: `gtable app get`. --- # Read the schema you can see Source: https://gtable.app/docs/app-api/schema/get `GET https://your-suite.gtable.app/your-app/v1/schema` Returns the tables and fields your permissions allow. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). A field you cannot read is absent, it is not listed as hidden, because naming it would be the leak. Requires the `schema:read` scope. Operation `schema.get`. MCP tool `schema_get`. CLI, once published: `gtable schema get`. --- # List records Source: https://gtable.app/docs/app-api/records/list `GET https://your-suite.gtable.app/your-app/v1/tables/{tableId}/records` Lists records from one table, filtered by your permissions and optionally by a saved view. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Paginated with an opaque cursor; ask for a view rather than paging when you only need what a user would see. Requires the `records:read` scope. Operation `records.list`. MCP tool `records_list`. CLI, once published: `gtable records list`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `tableId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | -------- | ------- | -------- | ---------------------- | | `limit` | integer | no | Default `50`. | | `cursor` | string | no | Up to 512 characters. | | `viewId` | string | no | Up to 64 characters. | | `search` | string | no | Up to 200 characters. | | `filter` | string | no | Up to 4000 characters. | | `sorts` | string | no | Up to 1000 characters. | | `where` | string | no | Up to 4000 characters. | --- # Create records Source: https://gtable.app/docs/app-api/records/create `POST https://your-suite.gtable.app/your-app/v1/tables/{tableId}/records` Creates one or more records. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Keys are field ids or exact field names; a key that is not a field you can see is refused. A field you can see but not write is dropped and counted in `droppedFields`, and a record none of whose fields could be written is refused. Send an Idempotency-Key header to make a retry safe. Email, URL and phone text is preserved; visible suspect values are returned in warnings (code suspect\_format, recordId, fieldId, fieldName, message). A warning is a successful save, not a reason to retry. Invalid numeric values, ratings and ISO dates fail with validation\_failed/422. A failing create batch saves no rows. Requires the `records:write` scope. Operation `records.create`. MCP tool `records_create`. CLI, once published: `gtable records create`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `tableId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | --------- | --------- | -------- | --------------- | | `records` | object\[] | yes | 1 to 500 items. | --- # Count records Source: https://gtable.app/docs/app-api/records/count `GET https://your-suite.gtable.app/your-app/v1/tables/{tableId}/records/count` How many records match, for the same view, filter and search a list would use. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Ask this rather than paging to the end when you only need the number. Requires the `records:read` scope. Operation `records.count`. MCP tool `records_count`. CLI, once published: `gtable records count`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `tableId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ---------------------- | | `viewId` | string | no | Up to 64 characters. | | `search` | string | no | Up to 200 characters. | | `filter` | string | no | Up to 4000 characters. | | `sorts` | string | no | Up to 1000 characters. | | `where` | string | no | Up to 4000 characters. | --- # Total a column Source: https://gtable.app/docs/app-api/records/summarise `GET https://your-suite.gtable.app/your-app/v1/tables/{tableId}/records/summary` Reductions over the records a list would return: `summaries` is a JSON object of field id to one of sum, avg, median, min, max, range, stdev, count, filled, empty, unique, checked, unchecked or a percentage of those. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Values come back raw and unformatted. A pair that cannot be computed exactly answers null; a field you cannot read is refused, listing the fields you can. Requires the `records:read` scope. Operation `records.summarise`. MCP tool `records_summarise`. CLI, once published: `gtable records summarise`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `tableId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | ----------- | ------ | -------- | ---------------------- | | `viewId` | string | no | Up to 64 characters. | | `search` | string | no | Up to 200 characters. | | `filter` | string | no | Up to 4000 characters. | | `sorts` | string | no | Up to 1000 characters. | | `where` | string | no | Up to 4000 characters. | | `summaries` | string | yes | Up to 2000 characters. | --- # Count records per value Source: https://gtable.app/docs/app-api/records/groups `GET https://your-suite.gtable.app/your-app/v1/tables/{tableId}/records/groups` How many records sit under each value of one column, for the same set a list would return. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Keys are raw stored values, ordered the way that column sorts. `fieldId` takes the field's id or exact name; a field you cannot read is refused, listing the fields you can. Requires the `records:read` scope. Operation `records.groups`. MCP tool `records_groups`. CLI, once published: `gtable records groups`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `tableId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | --------- | ------- | -------- | ---------------------- | | `viewId` | string | no | Up to 64 characters. | | `search` | string | no | Up to 200 characters. | | `filter` | string | no | Up to 4000 characters. | | `sorts` | string | no | Up to 1000 characters. | | `where` | string | no | Up to 4000 characters. | | `fieldId` | string | yes | Up to 200 characters. | | `limit` | integer | no | | --- # Read one record Source: https://gtable.app/docs/app-api/records/get `GET https://your-suite.gtable.app/your-app/v1/tables/{tableId}/records/{recordId}` Returns a single record with the fields your permissions allow. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Requires the `records:read` scope. Operation `records.get`. MCP tool `records_get`. CLI, once published: `gtable records get`. ## Path parameters | Name | Type | Required | Description | | ---------- | ------ | -------- | ----------- | | `tableId` | string | yes | | | `recordId` | string | yes | | --- # Update a record Source: https://gtable.app/docs/app-api/records/update `PATCH https://your-suite.gtable.app/your-app/v1/tables/{tableId}/records/{recordId}` Applies a partial update; the body is {"fields":{…}}, keyed by field id or exact name. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). If the change moves the record outside what you can see, the response says so rather than pretending the record vanished. Email, URL and phone text is preserved; visible suspect values are returned in warnings (code suspect\_format, recordId, fieldId, fieldName, message). A warning is a successful save, not a reason to retry. Invalid numeric values, ratings and ISO dates fail with validation\_failed/422. A failing create batch saves no rows. Requires the `records:write` scope. Operation `records.update`. MCP tool `records_update`. CLI, once published: `gtable records update`. ## Path parameters | Name | Type | Required | Description | | ---------- | ------ | -------- | ----------- | | `tableId` | string | yes | | | `recordId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ------------- | ------ | -------- | -------------------- | | `fields` | object | yes | | | `baseVersion` | string | no | Up to 64 characters. | --- # Delete a record Source: https://gtable.app/docs/app-api/records/delete `DELETE https://your-suite.gtable.app/your-app/v1/tables/{tableId}/records/{recordId}` Deletes one record. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Recorded in the op log with its links and its comments, so changesets.undo puts it back whole. Requires the `records:write` scope. Operation `records.delete`. MCP tool `records_delete`. CLI, once published: `gtable records delete`. ## Path parameters | Name | Type | Required | Description | | ---------- | ------ | -------- | ----------- | | `tableId` | string | yes | | | `recordId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # Delete several records Source: https://gtable.app/docs/app-api/records/delete-many `POST https://your-suite.gtable.app/your-app/v1/tables/{tableId}/records/delete` Deletes up to 500 records in one request. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Each row goes through the same permission check as a single delete; a row you may not delete is reported in `failed` and does not stop the others. The batch is ONE entry in the op log, so `changesetId` undoes all of it at once. Requires the `records:write` scope. Operation `records.deleteMany`. MCP tool `records_deleteMany`. CLI, once published: `gtable records delete-many`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `tableId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ----------- | --------- | -------- | --------------- | | `recordIds` | string\[] | yes | 1 to 500 items. | --- # Duplicate a record Source: https://gtable.app/docs/app-api/records/duplicate `POST https://your-suite.gtable.app/your-app/v1/tables/{tableId}/records/{recordId}/duplicate` Copies the fields you can both read and write into a new record. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). A field you cannot read is not carried over, the copy is made from what you can see, never from what is there, and comments and history stay with the original, because they are about that record and not about its shape. Requires the `records:write` scope. Operation `records.duplicate`. MCP tool `records_duplicate`. CLI, once published: `gtable records duplicate`. ## Path parameters | Name | Type | Required | Description | | ---------- | ------ | -------- | ----------- | | `tableId` | string | yes | | | `recordId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # Read a record's history Source: https://gtable.app/docs/app-api/records/history `GET https://your-suite.gtable.app/your-app/v1/records/{recordId}/history` Every change to this record, newest first, one entry per field that actually moved, setting a field to the value it already held is not history. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Also carries the record being created, commented on and deleted. Fields you cannot read produce no entry you can see. Requires the `records:read` scope. Operation `records.history`. MCP tool `records_history`. CLI, once published: `gtable records history`. ## Path parameters | Name | Type | Required | Description | | ---------- | ------ | -------- | ----------- | | `recordId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | --------- | ------- | -------- | ------------- | | `limit` | integer | no | Default `50`. | | `tableId` | string | yes | | --- # List the saved views of a table Source: https://gtable.app/docs/app-api/views/list `GET https://your-suite.gtable.app/your-app/v1/tables/{tableId}/views` Your own saved views of this table, in the order you arranged them. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). A view is one user's arrangement and there is no other kind, so nobody else's is ever here, and neither is a builder's, because a builder has none. Empty is an ordinary answer: a table with no saved views is the table itself. Requires the `records:read` scope. Operation `views.list`. MCP tool `views_list`. CLI, once published: `gtable views list`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `tableId` | string | yes | | --- # Save a new view of a table Source: https://gtable.app/docs/app-api/views/create `POST https://your-suite.gtable.app/your-app/v1/tables/{tableId}/views` Visible to you alone; there is no other kind. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). `config` fills in defaults for whatever is left out: an empty filter, no sorts, nothing hidden. Requires the `records:write` scope. Operation `views.create`. MCP tool `views_create`. CLI, once published: `gtable views create`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `tableId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | -------- | ------ | -------- | --------------------- | | `name` | string | yes | Up to 120 characters. | | `config` | object | no | | --- # Read one saved view Source: https://gtable.app/docs/app-api/views/get `GET https://your-suite.gtable.app/your-app/v1/views/{viewId}` One of yours. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Anybody else's is refused exactly like a record you cannot see, the difference would confirm it exists. Requires the `records:read` scope. Operation `views.get`. MCP tool `views_get`. CLI, once published: `gtable views get`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `viewId` | string | yes | | --- # Change a saved view Source: https://gtable.app/docs/app-api/views/update `PATCH https://your-suite.gtable.app/your-app/v1/views/{viewId}` Only a view that is yours. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). `config` merges shallowly: a key you send replaces that key, anything you leave out keeps its saved value. Requires the `records:write` scope. Operation `views.update`. MCP tool `views_update`. CLI, once published: `gtable views update`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `viewId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | -------- | ------ | -------- | --------------------- | | `name` | string | no | Up to 120 characters. | | `config` | object | no | | --- # Delete a saved view Source: https://gtable.app/docs/app-api/views/delete `DELETE https://your-suite.gtable.app/your-app/v1/views/{viewId}` Only a view that is yours, which is every view you can see. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). A table with none left is the table itself, so there is no last one to protect. Requires the `records:write` scope. Operation `views.delete`. MCP tool `views_delete`. CLI, once published: `gtable views delete`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `viewId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # Reorder a table's views Source: https://gtable.app/docs/app-api/views/reorder `POST https://your-suite.gtable.app/your-app/v1/tables/{tableId}/views/reorder` `ids` in the order you want them. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). An id that is not one of yours is ignored. Requires the `records:write` scope. Operation `views.reorder`. MCP tool `views_reorder`. CLI, once published: `gtable views reorder`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `tableId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ----- | --------- | -------- | --------------- | | `ids` | string\[] | yes | 1 to 200 items. | --- # Read a record's comments Source: https://gtable.app/docs/app-api/comments/list `GET https://your-suite.gtable.app/your-app/v1/tables/{tableId}/records/{recordId}/comments` The notes users have left on this record, oldest first. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). A comment is about the record, never a field of it. If you cannot read the record you are refused rather than told it has none, the difference would confirm it exists. Requires the `records:read` scope. Operation `comments.list`. MCP tool `comments_list`. CLI, once published: `gtable comments list`. ## Path parameters | Name | Type | Required | Description | | ---------- | ------ | -------- | ----------- | | `tableId` | string | yes | | | `recordId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | ------- | ------- | -------- | ------------- | | `limit` | integer | no | Default `50`. | --- # Comment on a record Source: https://gtable.app/docs/app-api/comments/create `POST https://your-suite.gtable.app/your-app/v1/tables/{tableId}/records/{recordId}/comments` Leaves a note on a record. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). `mentions` carries the user ids named with @ in the body; anyone in it who cannot see this record is dropped, because naming them would either leak that the record exists or send them to a refusal. Ask `comments.mentionable` for the list you may name. Requires the `records:write` scope. Operation `comments.create`. MCP tool `comments_create`. CLI, once published: `gtable comments create`. ## Path parameters | Name | Type | Required | Description | | ---------- | ------ | -------- | ----------- | | `tableId` | string | yes | | | `recordId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ---------- | --------- | -------- | ---------------------- | | `text` | string | yes | Up to 4000 characters. | | `mentions` | string\[] | no | Up to 20 items. | --- # Who may be named on this record Source: https://gtable.app/docs/app-api/comments/mentionable `GET https://your-suite.gtable.app/your-app/v1/tables/{tableId}/records/{recordId}/comments/mentionable` The users who can currently see THIS record, which is who an @ may name. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Evaluated per record and not per table, because two rows of one table can have different audiences. Requires the `records:read` scope. Operation `comments.mentionable`. MCP tool `comments_mentionable`. CLI, once published: `gtable comments mentionable`. ## Path parameters | Name | Type | Required | Description | | ---------- | ------ | -------- | ----------- | | `tableId` | string | yes | | | `recordId` | string | yes | | --- # Edit your own comment Source: https://gtable.app/docs/app-api/comments/update `PATCH https://your-suite.gtable.app/your-app/v1/comments/{commentId}` Only your own, refused on the server rather than hidden in the interface. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). The comment is marked as edited. Requires the `records:write` scope. Operation `comments.update`. MCP tool `comments_update`. CLI, once published: `gtable comments update`. ## Path parameters | Name | Type | Required | Description | | ----------- | ------ | -------- | ----------- | | `commentId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ---------- | --------- | -------- | ---------------------- | | `text` | string | yes | Up to 4000 characters. | | `mentions` | string\[] | no | Up to 20 items. | --- # Delete your own comment Source: https://gtable.app/docs/app-api/comments/delete `DELETE https://your-suite.gtable.app/your-app/v1/comments/{commentId}` Only your own. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). The record's history keeps saying a comment was added and now also says one was removed: a log that erased its own evidence would be no log at all. Requires the `records:write` scope. Operation `comments.delete`. MCP tool `comments_delete`. CLI, once published: `gtable comments delete`. ## Path parameters | Name | Type | Required | Description | | ----------- | ------ | -------- | ----------- | | `commentId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # Upload a file for an attachment field Source: https://gtable.app/docs/app-api/files/upload `POST https://your-suite.gtable.app/your-app/v1/tables/{tableId}/files` Two steps, on purpose. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). This one takes the BYTES and answers with a descriptor: `{ id, name, size, mime, key, uploadedAt }`. Writing that descriptor into an attachment field is an ordinary record write, so a file lands on a record through the same permission check, the same op log and the same undo as any other value. Send the file as `multipart/form-data` under `file`; `recordId` says which record it will belong to, so an upload that is never used can be swept. A descriptor you did not get from here is refused: its `key` names an R2 object, and inventing one would be naming somebody else's bytes. Requires the `records:write` scope. Operation `files.upload`. Not an MCP tool: a file is bytes and a tool call is JSON; upload with REST or the CLI. CLI, once published: `gtable files upload`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `tableId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body Sent as `multipart/form-data`. | Field | Type | Required | Description | | ---------- | ------ | -------- | ---------------------------------------------------------------------------------- | | `file` | string | yes | The bytes. | | `recordId` | string | yes | The record the file will be attached to, so an upload never attached can be swept. | --- # Read with SQL Source: https://gtable.app/docs/app-api/sql/query `POST https://your-suite.gtable.app/your-app/v1/sql/query` Runs one SELECT as you and returns rows and columns. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Fast for questions a filter cannot ask: aggregates, GROUP BY, CASE, functions. The dialect: SELECT \ FROM \ \[WHERE …] \[GROUP BY …] \[ORDER BY …] \[LIMIT n OFFSET m]; UPDATE \
SET col = expr \[WHERE …]; DELETE FROM \
\[WHERE …]. One table per statement; field names or ids (double-quote names with spaces); 'text', numbers, TRUE, FALSE, NULL, CURRENT\_DATE; AND OR NOT, = \<> \< \<= > >=, \[NOT] LIKE, \[NOT] IN (…), IS \[NOT] NULL, \[NOT] BETWEEN, CASE WHEN cond THEN v … \[ELSE v] END and CASE expr WHEN value THEN v … \[ELSE v] END; a select takes its choice's label (any case) or id; functions LENGTH LOWER UPPER TRIM ABS ROUND COALESCE SUBSTR DATE STRFTIME IFNULL INSTR REPLACE; link columns: col HAS ANY ('id', …), col HAS ALL (…), col IS EMPTY. Your text is never executed: it is compiled against your own permissions, and a column you may not read is simply an unknown column. At most 1000 rows; add LIMIT. Requires the `records:read` scope. Operation `sql.query`. MCP tool `sql_query`. CLI, once published: `gtable sql query`. ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ----- | ------ | -------- | ----------------------- | | `sql` | string | yes | Up to 20000 characters. | --- # Change many rows with SQL Source: https://gtable.app/docs/app-api/sql/execute `POST https://your-suite.gtable.app/your-app/v1/sql/execute` Runs one UPDATE or DELETE as you. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). The statement names the rows and the new values; each row is then written through the same checks as a single edit, so a row you may not change is skipped. The whole statement is one changeset, returned as changesetId, so one revert undoes it. Up to 500 rows per statement. Without a WHERE it refuses unless confirm is true; dryRun counts first. The dialect: SELECT \ FROM \
\[WHERE …] \[GROUP BY …] \[ORDER BY …] \[LIMIT n OFFSET m]; UPDATE \
SET col = expr \[WHERE …]; DELETE FROM \
\[WHERE …]. One table per statement; field names or ids (double-quote names with spaces); 'text', numbers, TRUE, FALSE, NULL, CURRENT\_DATE; AND OR NOT, = \<> \< \<= > >=, \[NOT] LIKE, \[NOT] IN (…), IS \[NOT] NULL, \[NOT] BETWEEN, CASE WHEN cond THEN v … \[ELSE v] END and CASE expr WHEN value THEN v … \[ELSE v] END; a select takes its choice's label (any case) or id; functions LENGTH LOWER UPPER TRIM ABS ROUND COALESCE SUBSTR DATE STRFTIME IFNULL INSTR REPLACE; link columns: col HAS ANY ('id', …), col HAS ALL (…), col IS EMPTY. Your text is never executed: it is compiled against your own permissions, and a column you may not read is simply an unknown column. Requires the `records:write` scope. Operation `sql.execute`. MCP tool `sql_execute`. CLI, once published: `gtable sql execute`. ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | --------- | ------- | -------- | ----------------------- | | `sql` | string | yes | Up to 20000 characters. | | `confirm` | boolean | no | | | `dryRun` | boolean | no | | --- # Turn a sentence into a filter Source: https://gtable.app/docs/app-api/filters/suggest `POST https://your-suite.gtable.app/your-app/v1/tables/{tableId}/filters/suggest` Turn a sentence into a filter > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). A small model call, not the assistant: given the table's fields, a few example values you can see, and the filter already in place, it returns the complete filter that should apply, or a refusal in plain words when the sentence is not about narrowing rows. Costs credits. Requires the `records:read` scope. Operation `filters.suggest`. MCP tool `filters_suggest`. CLI, once published: `gtable filters suggest`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `tableId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ---------- | ---------------- | -------- | --------------------- | | `sentence` | string | yes | Up to 600 characters. | | `current` | FilterGroupInput | no | | --- # What was changed here, and what can be undone Source: https://gtable.app/docs/app-api/changesets/mine `GET https://your-suite.gtable.app/your-app/v1/changesets` Recent write bundles in this app, newest first, one bundle is one atomic group of operations by one user at one moment, which is what somebody means by "that thing I just did". > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Each says whether it has since been undone, and `revertible` says whether it can be. You see the log of the app you are in; what you may UNDO is still decided by your own permissions, one operation at a time. Requires the `records:read` scope. Operation `changesets.mine`. MCP tool `changesets_mine`. CLI, once published: `gtable changesets mine`. ## Query parameters | Name | Type | Required | Description | | ------- | ------- | -------- | ------------- | | `limit` | integer | no | Default `50`. | --- # Undo a change Source: https://gtable.app/docs/app-api/changesets/undo `POST https://your-suite.gtable.app/your-app/v1/changesets/{changesetId}/revert` Replays a bundle's operations backwards, through the same permission compiler as any other write, so undoing a delete re-creates the row only if you could have created it. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). An update puts back ONLY the fields it changed, so a colleague's edit to another field of the same record survives. If somebody has changed one of those same fields since, it refuses and says whose: send `confirm: true` to overwrite theirs anyway. Only a bundle of record creates, edits and deletes can be undone: the changesets list marks each one `revertible`, and anything else (a comment, a saved view, a page, a change to the app's shape) is refused up front with the reason rather than half-applied. Every row it can put back is undone in ONE transaction, and a row deleted since is listed in `skipped` rather than failing the rest. Requires the `records:write` scope. Operation `changesets.undo`. MCP tool `changesets_undo`. CLI, once published: `gtable changesets undo`. ## Path parameters | Name | Type | Required | Description | | ------------- | ------ | -------- | ----------- | | `changesetId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | --------- | ------- | -------- | ----------- | | `confirm` | boolean | no | | --- # Catch up on what changed Source: https://gtable.app/docs/app-api/events/list `GET https://your-suite.gtable.app/your-app/v1/events` Events since a cursor, so a consumer that was down can catch up itself instead of asking us to replay. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). This is the pull side of webhooks. Requires the `records:read` scope. Operation `events.list`. MCP tool `events_list`. CLI, once published: `gtable events list`. ## Query parameters | Name | Type | Required | Description | | -------- | ------- | -------- | --------------------- | | `limit` | integer | no | Default `50`. | | `cursor` | string | no | Up to 512 characters. | | `since` | string | no | | --- # Make a Template page your own Source: https://gtable.app/docs/app-api/my/pages-fork `POST https://your-suite.gtable.app/your-app/v1/pages/{pageId}/fork` The Template page is already in the app. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). This takes it as a page of your own to change, editable, deletable, visible to nobody else, and it replaces the Template for you. Refused unless one of your groups may build pages; for a group that may not, a Template is as fixed as a locked page. Requires the `pages:write` scope. Operation `my.pages.fork`. MCP tool `my_pages_fork`. CLI, once published: `gtable my pages fork`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `pageId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # Rename a page you own Source: https://gtable.app/docs/app-api/my/pages-rename `PATCH https://your-suite.gtable.app/your-app/v1/pages/{pageId}` Changes the name of a copy you took, and nothing else about it. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). A builder's page keeps the name the builder gave it. Requires the `pages:write` scope. Operation `my.pages.rename`. MCP tool `my_pages_rename`. CLI, once published: `gtable my pages rename`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `pageId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ------ | ------ | -------- | --------------------- | | `name` | string | yes | Up to 120 characters. | --- # Delete a page you own Source: https://gtable.app/docs/app-api/my/pages-delete `DELETE https://your-suite.gtable.app/your-app/v1/pages/{pageId}` Only your own copies. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). A builder's page is not yours to remove; a copy you no longer want is. Requires the `pages:write` scope. Operation `my.pages.delete`. MCP tool `my_pages_delete`. CLI, once published: `gtable my pages delete`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `pageId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # Create a page of your own Source: https://gtable.app/docs/app-api/my/pages-create `POST https://your-suite.gtable.app/your-app/v1/pages` A new page that is yours alone, empty until you save code to it. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Refused unless one of your groups may build pages. Requires the `pages:write` scope. Operation `my.pages.create`. MCP tool `my_pages_create`. CLI, once published: `gtable my pages create`. ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | --------- | ------ | -------- | --------------------- | | `name` | string | yes | Up to 120 characters. | | `tableId` | string | no | Up to 64 characters. | --- # Read a page's code Source: https://gtable.app/docs/app-api/my/pages-code-get `GET https://your-suite.gtable.app/your-app/v1/pages/{pageId}/code` The current version of a page you can open, or an earlier one by number. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Null when nothing has been saved yet. Requires the `pages:read` scope. Operation `my.pages.code.get`. MCP tool `my_pages_code_get`. CLI, once published: `gtable my pages code get`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `pageId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | --------- | ------- | -------- | ----------- | | `version` | integer | no | | --- # Save a new version of your page's code Source: https://gtable.app/docs/app-api/my/pages-code-set `PUT https://your-suite.gtable.app/your-app/v1/pages/{pageId}/code` Only a page you own. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Never overwrites: every save is a version. A note says why, for the history. Send `baseVersion` (the version you read) to be refused with 409 if somebody saved since. Requires the `pages:write` scope. Operation `my.pages.code.set`. MCP tool `my_pages_code_set`. CLI, once published: `gtable my pages code set`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `pageId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ------------- | ------------------------------------------- | -------- | ------------------------ | | `source` | string | yes | Up to 400000 characters. | | `note` | string | no | Up to 200 characters. | | `via` | "assistant" or "hand" or "api" or "restore" | no | | | `baseVersion` | integer | no | At least 0. | --- # The history of a page you can open Source: https://gtable.app/docs/app-api/my/pages-versions `GET https://your-suite.gtable.app/your-app/v1/pages/{pageId}/versions` Every saved version, newest first: who saved it, when, the note they left, whether it builds, and which one users are looking at. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Requires the `pages:read` scope. Operation `my.pages.versions`. MCP tool `my_pages_versions`. CLI, once published: `gtable my pages versions`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `pageId` | string | yes | | --- # Publish a version of your page Source: https://gtable.app/docs/app-api/my/pages-publish `POST https://your-suite.gtable.app/your-app/v1/pages/{pageId}/publish` Points the page at a version that has already been built, the latest by default, or `version` to go back to an earlier one. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Every save builds, so this never compiles anything and never fails on the code; a version that did not build says so rather than being published. Requires the `pages:write` scope. Operation `my.pages.publish`. MCP tool `my_pages_publish`. CLI, once published: `gtable my pages publish`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `pageId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | --------- | ------- | -------- | ----------- | | `version` | integer | no | At least 1. | --- # Which apps you are in Source: https://gtable.app/docs/app-api/my/apps `GET https://your-suite.gtable.app/your-app/v1/my/apps` Every app in this organisation you have been given access to, with the groups you are in for each. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). This is what the app switcher reads, and what an agent should call before assuming which app it is looking at. Any valid credential for this API: no scope is needed. Operation `my.apps`. MCP tool `my_apps`. CLI, once published: `gtable my apps`. --- # Who this token acts as Source: https://gtable.app/docs/app-api/me/get `GET https://your-suite.gtable.app/your-app/v1/me` The subject behind this credential: the user, their groups, and how the request arrived. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). A key can never do more than the user who made it, and this is how you check which user that is. Any valid credential for this API: no scope is needed. Operation `me.get`. MCP tool `me_get`. CLI, once published: `gtable me get`. --- # Your keys and connected agents in this app Source: https://gtable.app/docs/app-api/me/keys-list `GET https://your-suite.gtable.app/your-app/v1/me/keys` Every credential acting as you here: the keys you made and the AI clients you connected. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Never the secrets themselves. Any valid credential for this API: no scope is needed. Operation `me.keys.list`. MCP tool `me_keys_list`. CLI, once published: `gtable me keys list`. --- # Create a personal API key for this app Source: https://gtable.app/docs/app-api/me/keys-create `POST https://your-suite.gtable.app/your-app/v1/me/keys` Mints a key that acts as you, in this app, with at most the permissions you already have, a key can never do more than the user who made it. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Only from a signed-in browser: a key or a connected agent cannot mint another. Expires in 90 days unless you choose otherwise; `expiresInDays: null` lives until revoked. The key is returned once and never stored in clear. Any valid credential for this API: no scope is needed. Operation `me.keys.create`. Not an MCP tool: keys are made by a person in a signed-in browser. CLI, once published: `gtable me keys create`. ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | --------------- | --------------- | -------- | --------------------- | | `name` | string | yes | Up to 120 characters. | | `scopes` | string\[] | yes | | | `expiresInDays` | integer or null | no | | --- # Revoke one of your credentials Source: https://gtable.app/docs/app-api/me/keys-revoke `DELETE https://your-suite.gtable.app/your-app/v1/me/keys/{credentialId}` Stops a key or a connected agent working, immediately. > **Note: Generic contract** > > Your own version of this operation, with your tables and the fields you can read, is in > your app's document: `https://{suite}.gtable.app/{app}/v1/openapi.json`, behind your credential. > [Why each person gets their own](/docs/start/studio-and-runtime#why-an-app-has-an-api-of-its-own). Only your own, and only from a signed-in browser. Any valid credential for this API: no scope is needed. Operation `me.keys.revoke`. Not an MCP tool: keys are revoked by a person in a signed-in browser. CLI, once published: `gtable me keys revoke`. ## Path parameters | Name | Type | Required | Description | | -------------- | ------ | -------- | ----------- | | `credentialId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # Who is signed in Source: https://gtable.app/docs/builder-api/session/get `GET https://studio.gtable.app/v1/session` The builder this request acts as, how it arrived (a browser session, a key or a connected agent) and the scopes it carries. The organisations and everything below them are other routes. This is what `gtable auth status` checks. Any valid credential for this API: no scope is needed. Operation `session.get`. MCP tool `session_get`. CLI, once published: `gtable session get`. --- # The organisations you build for Source: https://gtable.app/docs/builder-api/orgs/list `GET https://studio.gtable.app/v1/organizations` Every organisation you are a builder of. An organisation pays, holds the plan, and contains suites; this is what the switcher at the top of the Studio reads. Requires the `account:read` scope. Operation `orgs.list`. MCP tool `orgs_list`. CLI, once published: `gtable orgs list`. --- # Start another organisation Source: https://gtable.app/docs/builder-api/orgs/create `POST https://studio.gtable.app/v1/organizations` A second organisation is a second plan and a second invoice, and nothing crosses between them: separate suites, separate builders, separate billing. Use it for a separate company, not for a second customer of the same one, that is a suite. Requires the `account:write` scope. Operation `orgs.create`. MCP tool `orgs_create`. CLI, once published: `gtable orgs create`. ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ------ | ------ | -------- | --------------------- | | `name` | string | yes | Up to 120 characters. | --- # Read an organisation Source: https://gtable.app/docs/builder-api/orgs/get `GET https://studio.gtable.app/v1/organizations/{organizationId}` Its name, its plan, when it was created, and the models its builders' own assistant offers in the Studio. Billing detail is a separate call, because most screens do not need it. Requires the `account:read` scope. Operation `orgs.get`. MCP tool `orgs_get`. CLI, once published: `gtable orgs get`. ## Path parameters | Name | Type | Required | Description | | ---------------- | ------ | -------- | ----------- | | `organizationId` | string | yes | | --- # Change an organisation Source: https://gtable.app/docs/builder-api/orgs/update `PATCH https://studio.gtable.app/v1/organizations/{organizationId}` Its name, what your team sees at the top of the Studio, never touching any suite's login page, and the models your own assistant offers here. That menu is the Studio's alone: what the users in your apps are offered is set per suite, and they see a label rather than a model name. Requires the `schema:write` scope. Operation `orgs.update`. MCP tool `orgs_update`. CLI, once published: `gtable orgs update`. ## Path parameters | Name | Type | Required | Description | | ---------------- | ------ | -------- | ----------- | | `organizationId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ----------------- | --------- | -------- | --------------------- | | `name` | string | no | Up to 120 characters. | | `assistantModels` | object\[] | no | 1 to 5 items. | --- # Delete an organisation Source: https://gtable.app/docs/builder-api/orgs/delete `DELETE https://studio.gtable.app/v1/organizations/{organizationId}` Admins only, and it takes everything with it: every suite, every app, every directory and every address stops answering. The apps' data sits through the grace period so a mistake is ours to undo, but the addresses are released immediately. Refused while a paid subscription is attached: cancel it with billing.cancel and let the period end first, because deleting the organisation would not stop the charges. Send the organisation's name as `confirm`. Requires the `account:write` scope. Operation `orgs.delete`. MCP tool `orgs_delete`. CLI, once published: `gtable orgs delete`. ## Path parameters | Name | Type | Required | Description | | ---------------- | ------ | -------- | ----------- | | `organizationId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | --------- | ------ | -------- | --------------------- | | `confirm` | string | no | Up to 200 characters. | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # Leave an organisation Source: https://gtable.app/docs/builder-api/orgs/leave `POST https://studio.gtable.app/v1/organizations/{organizationId}/leave` You stop being a builder of it and lose every suite under it; nothing of the organisation's is deleted. The last admin cannot leave, because an organisation nobody can administer can never be changed again: make somebody else an admin, or delete the organisation. Requires the `account:write` scope. Operation `orgs.leave`. MCP tool `orgs_leave`. CLI, once published: `gtable orgs leave`. ## Path parameters | Name | Type | Required | Description | | ---------------- | ------ | -------- | ----------- | | `organizationId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # Close your account Source: https://gtable.app/docs/builder-api/account/delete `DELETE https://studio.gtable.app/v1/me` Your sign-in goes, and so does every organisation where you are the only builder, with all of its suites and apps. An organisation with another admin is simply left. Refused while you are the only admin of an organisation where other builders work (make one of them an admin first, on that organisation's Builders page), and while an organisation that would be deleted still has a paid subscription. What each one will be is told to you before you confirm. Send your account's email address as `confirm`. Requires the `account:write` scope. Operation `account.delete`. MCP tool `account_delete`. CLI, once published: `gtable account delete`. ## Query parameters | Name | Type | Required | Description | | --------- | ------ | -------- | --------------------- | | `confirm` | string | no | Up to 320 characters. | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # The builders of an organisation Source: https://gtable.app/docs/builder-api/builders/list `GET https://studio.gtable.app/v1/organizations/{organizationId}/builders` The builders who can open this Studio, their role, and which suites each one may work on. Not the users of the apps, those live in a suite's directory and never see the Studio at all. Requires the `members:read` scope. Operation `builders.list`. MCP tool `builders_list`. CLI, once published: `gtable builders list`. ## Path parameters | Name | Type | Required | Description | | ---------------- | ------ | -------- | ----------- | | `organizationId` | string | yes | | --- # Change what a builder is, or which suites they may work on Source: https://gtable.app/docs/builder-api/builders/update `PATCH https://studio.gtable.app/v1/organizations/{organizationId}/builders/{userId}` Admins only. An admin can do everything and needs no suite list; a member sees exactly the suites they were given and nothing of the others, or every suite of the organisation when `allSuites` is on. The last admin cannot be demoted, so an organisation can never be left with nobody who can change it. Requires the `members:write` scope. Operation `builders.update`. MCP tool `builders_update`. CLI, once published: `gtable builders update`. ## Path parameters | Name | Type | Required | Description | | ---------------- | ------ | -------- | ----------- | | `organizationId` | string | yes | | | `userId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ----------------- | ------------------- | -------- | ----------- | | `role` | "admin" or "member" | no | | | `allSuites` | boolean | no | | | `suiteIds` | string\[] | no | | | `canCreateSuites` | boolean | no | | --- # Plan, usage and limits Source: https://gtable.app/docs/builder-api/billing/get `GET https://studio.gtable.app/v1/organizations/{organizationId}/billing` What this organisation is on, what it is using, and what its plan allows. Limits are counted on the organisation because that is what pays; a suite is never billed for existing. Requires the `account:read` scope. Operation `billing.get`. MCP tool `billing_get`. CLI, once published: `gtable billing get`. ## Path parameters | Name | Type | Required | Description | | ---------------- | ------ | -------- | ----------- | | `organizationId` | string | yes | | --- # Start paying for a plan Source: https://gtable.app/docs/builder-api/billing/checkout `POST https://studio.gtable.app/v1/organizations/{organizationId}/billing/checkout` Admins only. For an organisation with no paid subscription: returns a Stripe Checkout address where a person enters a card, after which the plan applies. Nothing is charged until they complete it there. An organisation that already pays changes plan with billing.change instead. Refused with 501 while billing is not connected. Requires the `account:write` scope. Operation `billing.checkout`. MCP tool `billing_checkout`. CLI, once published: `gtable billing checkout`. ## Path parameters | Name | Type | Required | Description | | ---------------- | ------ | -------- | ----------- | | `organizationId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ---------- | ----------------------------------------- | -------- | -------------------- | | `plan` | "free" or "starter" or "team" or "custom" | yes | | | `interval` | "monthly" or "yearly" | no | Default `"monthly"`. | --- # What changing plan would do, and cost today Source: https://gtable.app/docs/builder-api/billing/preview `POST https://studio.gtable.app/v1/organizations/{organizationId}/billing/preview` Admins only. Changes nothing. Says whether the change is immediate (an upgrade, charged now and prorated, with Stripe's own figure for today), scheduled for the end of the paid period (a downgrade), or needs a checkout (no subscription yet). Refused with 501 while billing is not connected. Requires the `account:read` scope. Operation `billing.preview`. MCP tool `billing_preview`. CLI, once published: `gtable billing preview`. ## Path parameters | Name | Type | Required | Description | | ---------------- | ------ | -------- | ----------- | | `organizationId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ---------- | ----------------------------------------- | -------- | -------------------- | | `plan` | "free" or "starter" or "team" or "custom" | yes | | | `interval` | "monthly" or "yearly" | no | Default `"monthly"`. | --- # Change plan Source: https://gtable.app/docs/builder-api/billing/change `POST https://studio.gtable.app/v1/organizations/{organizationId}/billing/change` Admins only. Up a tier, or monthly to yearly: immediate, prorated and charged now to the card on file; a declined card refuses the change. Down a tier, or yearly to monthly: scheduled for the end of the period already paid for, and withdrawable with billing.cancel (what: pendingChange). With no subscription yet, returns `checkoutUrl` instead. Call billing.preview first to see the figure. Refused with 501 while billing is not connected. Requires the `account:write` scope. Operation `billing.change`. MCP tool `billing_change`. CLI, once published: `gtable billing change`. ## Path parameters | Name | Type | Required | Description | | ---------------- | ------ | -------- | ----------- | | `organizationId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ---------- | ----------------------------------------- | -------- | -------------------- | | `plan` | "free" or "starter" or "team" or "custom" | yes | | | `interval` | "monthly" or "yearly" | no | Default `"monthly"`. | --- # Cancel the subscription, or a scheduled change Source: https://gtable.app/docs/builder-api/billing/cancel `POST https://studio.gtable.app/v1/organizations/{organizationId}/billing/cancel` Admins only. `what: subscription` (the default) stops the subscription at the end of the period already paid for; the plan stays until then and billing.resume undoes it. `what: pendingChange` withdraws a scheduled downgrade and keeps the current plan. Refused with 501 while billing is not connected. Requires the `account:write` scope. Operation `billing.cancel`. MCP tool `billing_cancel`. CLI, once published: `gtable billing cancel`. ## Path parameters | Name | Type | Required | Description | | ---------------- | ------ | -------- | ----------- | | `organizationId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ------ | --------------------------------- | -------- | ------------------------- | | `what` | "subscription" or "pendingChange" | no | Default `"subscription"`. | --- # Keep the plan after cancelling it Source: https://gtable.app/docs/builder-api/billing/resume `POST https://studio.gtable.app/v1/organizations/{organizationId}/billing/resume` Admins only. Undoes a cancellation that has not taken effect yet, so the subscription renews as before and the card is charged at the next renewal. Refused with 501 while billing is not connected. Requires the `account:write` scope. Operation `billing.resume`. MCP tool `billing_resume`. CLI, once published: `gtable billing resume`. ## Path parameters | Name | Type | Required | Description | | ---------------- | ------ | -------- | ----------- | | `organizationId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # Open Stripe's billing portal Source: https://gtable.app/docs/builder-api/billing/portal `POST https://studio.gtable.app/v1/organizations/{organizationId}/billing/portal` Admins only. Returns a short-lived Stripe address where a person updates the card, the billing address and the tax id, and downloads invoices. We never see the card. Refused with 501 while billing is not connected, and refused when the organisation has never paid. Requires the `account:write` scope. Operation `billing.portal`. MCP tool `billing_portal`. CLI, once published: `gtable billing portal`. ## Path parameters | Name | Type | Required | Description | | ---------------- | ------ | -------- | ----------- | | `organizationId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # What is happening across an organisation Source: https://gtable.app/docs/builder-api/analytics/get `GET https://studio.gtable.app/v1/organizations/{organizationId}/analytics` Activity and cost for a window, filterable to one suite or one app. Counts are taken from the platform's own event log, which records that something happened and never what was in it: a row was created in a table, never the row. Use it to answer where the work is, who is actually using an app, and what the assistant is costing. Requires the `account:read` scope. Operation `analytics.get`. MCP tool `analytics_get`. CLI, once published: `gtable analytics get`. ## Path parameters | Name | Type | Required | Description | | ---------------- | ------ | -------- | ----------- | | `organizationId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | --------- | ------- | -------- | ------------- | | `suiteId` | string | no | | | `appId` | string | no | | | `days` | integer | no | Default `30`. | --- # The suites of an organisation Source: https://gtable.app/docs/builder-api/suites/list `GET https://studio.gtable.app/v1/organizations/{organizationId}/suites` Each suite is one user directory and one login page, holding one or more apps. An agency makes one per client; a company usually needs one. Requires the `account:read` scope. Operation `suites.list`. MCP tool `suites_list`. CLI, once published: `gtable suites list`. ## Path parameters | Name | Type | Required | Description | | ---------------- | ------ | -------- | ----------- | | `organizationId` | string | yes | | --- # Create a suite Source: https://gtable.app/docs/builder-api/suites/create `POST https://studio.gtable.app/v1/organizations/{organizationId}/suites` A new directory and a new login page, empty. Nobody is in it, and creating it grants you no access to any app inside it. `slug` is the subdomain it answers on, given one that is taken, the call is refused rather than suffixed, because an address is not a name. Omit it and one is derived from the name. Requires the `schema:write` scope. Operation `suites.create`. MCP tool `suites_create`. CLI, once published: `gtable suites create`. ## Path parameters | Name | Type | Required | Description | | ---------------- | ------ | -------- | ----------- | | `organizationId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ------ | ------ | -------- | --------------------- | | `name` | string | yes | Up to 120 characters. | | `slug` | string | no | Up to 40 characters. | --- # Read a suite Source: https://gtable.app/docs/builder-api/suites/get `GET https://studio.gtable.app/v1/suites/{suiteId}` The suite's name, branding and login switches: which providers its users may sign in with, and whether a stranger may sign themselves up. Requires the `account:read` scope. Operation `suites.get`. MCP tool `suites_get`. CLI, once published: `gtable suites get`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `suiteId` | string | yes | | --- # Change a suite Source: https://gtable.app/docs/builder-api/suites/update `PATCH https://studio.gtable.app/v1/suites/{suiteId}` Renames a suite, changes its subdomain, sets its login page's colour and logo, or flips its providers. A subdomain already taken by another suite is refused rather than suffixed: an address is not a name, and quietly giving somebody `acme-2` would be worse than saying no. Turning a provider off signs nobody out; it stops the next sign-in with it. Requires the `schema:write` scope. Operation `suites.update`. MCP tool `suites_update`. CLI, once published: `gtable suites update`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `suiteId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ------------------------- | --------------- | -------- | --------------------- | | `name` | string | no | Up to 120 characters. | | `slug` | string | no | Up to 40 characters. | | `authPassword` | boolean | no | | | `authGoogle` | boolean | no | | | `allowSelfSignup` | boolean | no | | | `accent` | string or null | no | | | `logoUrl` | string or null | no | | | `assistantMonthlyCredits` | integer or null | no | | | `assistantPresets` | object\[] | no | | --- # Delete a suite Source: https://gtable.app/docs/builder-api/suites/delete `DELETE https://studio.gtable.app/v1/suites/{suiteId}` The suite, its apps, its directory and its addresses, together. The hostnames are released FIRST, so a suite whose row says deleted can never still be answering; the apps go the way `apps.delete` sends them, soft, so their rows sit through the grace period and a mistake is ours to undo. Refused to anyone but an administrator of the organisation, and the Studio asks for two separate confirmations before it is called. Send the suite's name as `confirm`. Never offered to the built-in assistant. Requires the `schema:write` scope. Operation `suites.delete`. MCP tool `suites_delete`. CLI, once published: `gtable suites delete`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `suiteId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | --------- | ------ | -------- | --------------------- | | `confirm` | string | no | Up to 200 characters. | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # List your apps Source: https://gtable.app/docs/builder-api/apps/list `GET https://studio.gtable.app/v1/apps` Every app you build, newest first, across every organisation and suite. Start here when you do not know an app id: every other builder tool needs one. Requires the `account:read` scope. Operation `apps.list`. MCP tool `apps_list`. CLI, once published: `gtable apps list`. ## Query parameters | Name | Type | Required | Description | | --------- | ------- | -------- | --------------------- | | `limit` | integer | no | Default `50`. | | `cursor` | string | no | Up to 512 characters. | | `suiteId` | string | no | Up to 64 characters. | --- # Create an app Source: https://gtable.app/docs/builder-api/apps/create `POST https://studio.gtable.app/v1/apps` Creates an app in a suite, reachable at that suite's address followed by the app's slug. The builder is not automatically a member; no group has special platform privileges. Requires the `schema:write` scope. Operation `apps.create`. MCP tool `apps_create`. CLI, once published: `gtable apps create`. ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | --------- | --------- | -------- | --------------------- | | `suiteId` | string | yes | Up to 64 characters. | | `name` | string | yes | Up to 120 characters. | | `slug` | string | no | | | `tables` | object\[] | no | | --- # Read an app definition Source: https://gtable.app/docs/builder-api/apps/get `GET https://studio.gtable.app/v1/apps/{appId}` The whole definition: tables, views, rules, pages, members, theme, surfaces. Requires the `schema:read` scope. Operation `apps.get`. MCP tool `apps_get`. CLI, once published: `gtable apps get`. ## Path parameters | Name | Type | Required | Description | | ------- | ------ | -------- | ----------- | | `appId` | string | yes | | --- # Rename an app, or change its theme and surfaces Source: https://gtable.app/docs/builder-api/apps/update `PATCH https://studio.gtable.app/v1/apps/{appId}` Changes what the app is called, how it looks to its users (accent, radius, font, density), and which surfaces are switched on for them: data grid, interface, assistant, API, MCP, CLI. Within a minute of switching one off: `api` refuses every call made with a key or a connected token, the CLI's included (the app's own screens, on a signed-in session, keep working); `mcp` refuses the app's MCP endpoints and drops it from the suite's MCP server; `cli` refuses the official command-line tool by its user agent, which with the API on stops the tool and nothing else; `assistant` takes the in-app assistant away; `data` and `interface` hide those tabs. Nothing is deleted by any of them. Requires the `schema:write` scope. Operation `apps.update`. MCP tool `apps_update`. CLI, once published: `gtable apps update`. ## Path parameters | Name | Type | Required | Description | | ------- | ------ | -------- | ----------- | | `appId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ---------- | ------ | -------- | --------------------- | | `name` | string | no | Up to 120 characters. | | `theme` | object | no | | | `surfaces` | object | no | | --- # Delete an app Source: https://gtable.app/docs/builder-api/apps/delete `DELETE https://studio.gtable.app/v1/apps/{appId}` Takes the app off its suite's address, then marks it deleted. Its object, rows and files are kept for thirty days, apps.deleted lists it and apps.restore brings it back at the same path, and then purged for good: the object's storage, its files, its assistant conversations and its rows in the control plane. Credentials and invite links naming it are revoked and stay revoked. Send the app's name as `confirm`. Requires the `schema:write` scope. Operation `apps.delete`. MCP tool `apps_delete`. CLI, once published: `gtable apps delete`. ## Path parameters | Name | Type | Required | Description | | ------- | ------ | -------- | ----------- | | `appId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | --------- | ------ | -------- | --------------------- | | `confirm` | string | no | Up to 200 characters. | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # List a suite's deleted apps that can still be restored Source: https://gtable.app/docs/builder-api/apps/deleted `GET https://studio.gtable.app/v1/suites/{suiteId}/deleted-apps` Apps of this suite deleted in the last thirty days and not yet purged, with when each was deleted and after when it is purged. A deleted suite's apps are not here: its directory went with it. Requires the `schema:read` scope. Operation `apps.deleted`. MCP tool `apps_deleted`. CLI, once published: `gtable apps deleted`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `suiteId` | string | yes | | --- # Restore a deleted app Source: https://gtable.app/docs/builder-api/apps/restore `POST https://studio.gtable.app/v1/apps/{appId}/restore` Brings back an app deleted in the last thirty days, at its own path, with everything in it. Counted against the plan like a new app. The credentials and invite links its deletion revoked stay revoked. Requires the `schema:write` scope. Operation `apps.restore`. MCP tool `apps_restore`. CLI, once published: `gtable apps restore`. ## Path parameters | Name | Type | Required | Description | | ------- | ------ | -------- | ----------- | | `appId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # Read the app's definition log Source: https://gtable.app/docs/builder-api/apps/log `GET https://studio.gtable.app/v1/apps/{appId}/log` Who changed the schema, the rules or the members of this app, and when, newest first. Record-level changes are not here: a builder is not a member of their app unless they add themselves, and this log carries no row data. Requires the `members:read` scope. Operation `apps.log`. MCP tool `apps_log`. CLI, once published: `gtable apps log`. ## Path parameters | Name | Type | Required | Description | | ------- | ------ | -------- | ----------- | | `appId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | ------- | ------- | -------- | ------------- | | `limit` | integer | no | Default `50`. | --- # Add a table Source: https://gtable.app/docs/builder-api/tables/create `POST https://studio.gtable.app/v1/apps/{appId}/tables` Adds a table. Ids are the platform's to mint and are opaque: leave `id` and every field's `id` out and you get `tbl_…` and `fld_…` back in the response. Send them only when you are recreating something whose ids you already hold. Goes through the same validation the Studio UI uses, so the two cannot drift. Requires the `schema:write` scope. Operation `tables.create`. MCP tool `tables_create`. CLI, once published: `gtable tables create`. ## Path parameters | Name | Type | Required | Description | | ------- | ------ | -------- | ----------- | | `appId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ---------------- | ------------- | -------- | ---------------------- | | `name` | string | yes | Up to 120 characters. | | `plural` | string | no | Up to 120 characters. | | `description` | string | no | Up to 2000 characters. | | `id` | string | no | | | `primaryField` | string | no | Up to 120 characters. | | `primaryFieldId` | string | no | | | `fields` | FieldInput\[] | yes | 1 to 200 items. | --- # Rename a table or change its primary field Source: https://gtable.app/docs/builder-api/tables/update `PATCH https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}` The id never changes, it is the column name and what every rule and page points at. The name is what people read. Requires the `schema:write` scope. Operation `tables.update`. MCP tool `tables_update`. CLI, once published: `gtable tables update`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ---------------- | ------ | -------- | --------------------- | | `name` | string | no | Up to 120 characters. | | `plural` | string | no | Up to 120 characters. | | `primaryFieldId` | string | no | | --- # Delete a table and its rows Source: https://gtable.app/docs/builder-api/tables/delete `DELETE https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}` Drops the table and every row in it, with the link fields on other tables that pointed at it, and removes it from every rule that named it. Its rows, its links and its grants go to the app's trash for thirty days first, so trash.restore can put the whole table back with the same ids. Refused with the list of what reads it (409, details.impact) until you send acknowledge=true. Send the table's current name as `confirm`. Requires the `schema:write` scope. Operation `tables.delete`. MCP tool `tables_delete`. CLI, once published: `gtable tables delete`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | ------------- | ------- | -------- | --------------------- | | `confirm` | string | no | Up to 200 characters. | | `acknowledge` | boolean | no | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # Every field type, its settings and its limits Source: https://gtable.app/docs/builder-api/fields/types `GET https://studio.gtable.app/v1/field-types` The catalogue: each type's id, label, which group it belongs to in the picker, whether it is stored, computed or a relation, whether anybody may write to it, its character or magnitude limit, and the shape of its `config`. Read it before inventing a setting, a config key that does not belong to the type is a validation error, not something quietly ignored. Requires the `schema:read` scope. Operation `fields.types`. MCP tool `fields_types`. CLI, once published: `gtable fields types`. --- # Every function a formula may call Source: https://gtable.app/docs/builder-api/fields/functions `GET https://studio.gtable.app/v1/formula-functions` Name, group, what it does, one example, what it returns and how many arguments it takes. A function that is not on this list does not exist: guessing one from another product is refused when the formula is saved, so read this instead. Requires the `schema:read` scope. Operation `fields.functions`. MCP tool `fields_functions`. CLI, once published: `gtable fields functions`. --- # Add a field Source: https://gtable.app/docs/builder-api/fields/create `POST https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}/fields` Adds a field to a table. Leave `id` out: the platform mints an opaque `fld_…` and returns it. Every type carries its own `config`, and sending one that does not belong to the type is a validation error rather than a key nothing reads, call `fields.types` for the shapes. A computed field (lookup, rollup, formula, button) gets no column: it is derived at read time. A `link` creates its mirror field on the other table in the same write, and the two are deleted together. Requires the `schema:write` scope. Operation `fields.create`. MCP tool `fields_create`. CLI, once published: `gtable fields create`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body A object. --- # Change a field's name, type, width or options Source: https://gtable.app/docs/builder-api/fields/update `PATCH https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}/fields/{fieldId}` Its id never changes: the id IS the column. `config` is merged into the field's existing settings, and is checked against the type it will have after this call. Its TYPE may change, and the values are converted in the same transaction, anything that reads as the new type is kept, anything that does not is emptied rather than guessed at. A type change can break an interface page, a view's filter or a permission condition written against the old type, so say what it will do before you propose one. Converting to or from a relation is refused: add the new field and delete the old one. Select options are replaced wholesale; keep an option's `id` to keep the rows that hold it, and a removed option leaves its old values in place. Requires the `schema:write` scope. Operation `fields.update`. MCP tool `fields_update`. CLI, once published: `gtable fields update`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | | `fieldId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | ------------- | ------- | -------- | ----------- | | `acknowledge` | boolean | no | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------------------------- | | `name` | string | no | Up to 120 characters. | | `type` | "text" or "longtext" or "email" or "url" or "phone" or "json" or "number" or "currency" or "percent" or "rating" or "duration" or "select" or "multiselect" or "checkbox" or "user" or "attachment" or "date" or "recordId" or "autonumber" or "createdTime" or "createdBy" or "modifiedTime" or "modifiedBy" or "link" or "lookup" or "rollup" or "formula" or "button" | no | | | `width` | integer | no | At least 60. At most 900. | | `config` | object | no | | --- # Delete a field Source: https://gtable.app/docs/builder-api/fields/delete `DELETE https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}/fields/{fieldId}` Drops a field and its values. The values, the field and the grants that named it go to the app's trash for thirty days first, so trash.restore can put it back with the same id; the app's point-in-time bookmark is recorded too. Refused with the list of what reads it (409, details.impact) until you send acknowledge=true; `schema.impact` asks the same question without changing anything. Send the field's current name as `confirm`. Requires the `schema:write` scope. Operation `fields.delete`. MCP tool `fields_delete`. CLI, once published: `gtable fields delete`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | | `fieldId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | ------------- | ------- | -------- | --------------------- | | `confirm` | string | no | Up to 200 characters. | | `acknowledge` | boolean | no | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # What a schema change would break Source: https://gtable.app/docs/builder-api/schema/impact `POST https://studio.gtable.app/v1/apps/{appId}/schema/impact` Given fields or tables you propose to remove, lists the permission rules, saved views, pages and other fields that read them, the same list `fields.delete` and `tables.delete` are refused with until you send acknowledge=true. Pages are matched by what their code named when it was last built. References are by id, so a rename breaks nothing and is answered with nothing. Advisory: nothing is changed. Requires the `schema:read` scope. Operation `schema.impact`. MCP tool `schema_impact`. CLI, once published: `gtable schema impact`. ## Path parameters | Name | Type | Required | Description | | ------- | ------ | -------- | ----------- | | `appId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | --------- | --------- | -------- | ------------- | | `drops` | object\[] | no | Default `[]`. | | `renames` | object\[] | no | Default `[]`. | --- # List a table's records, as the builder Source: https://gtable.app/docs/builder-api/data/records-list `GET https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}/records` Every row the table holds, paginated with an opaque cursor. Takes the same `filter`, `sorts`, `where` and `search` as the app's own list, and `meta.fields` names each field id. Rows are read with `records:read` and written with `records:write` on both planes; `schema:*` is the tables and fields themselves. Requires the `records:read` scope. Operation `apps.records.list`. MCP tool `apps_records_list`. CLI, once published: `gtable apps records list`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | -------- | ------- | -------- | ---------------------- | | `limit` | integer | no | Default `50`. | | `cursor` | string | no | Up to 512 characters. | | `viewId` | string | no | Up to 64 characters. | | `search` | string | no | Up to 200 characters. | | `filter` | string | no | Up to 4000 characters. | | `sorts` | string | no | Up to 1000 characters. | | `where` | string | no | Up to 4000 characters. | --- # Create records, as the builder Source: https://gtable.app/docs/builder-api/data/records-create `POST https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}/records` Creates one to 500 records in one call, all or nothing: one refused row saves none. Keys are field ids or exact field names; an unknown key is refused rather than dropped. The whole batch is one changeset, returned as `changesetId`. Email, URL and phone text is preserved; visible suspect values are returned in warnings (code suspect\_format, recordId, fieldId, fieldName, message). A warning is a successful save, not a reason to retry. Invalid numeric values, ratings and ISO dates fail with validation\_failed/422. A failing create batch saves no rows. Requires the `records:write` scope. Operation `apps.records.create`. MCP tool `apps_records_create`. CLI, once published: `gtable apps records create`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | --------- | --------- | -------- | --------------- | | `records` | object\[] | yes | 1 to 500 items. | --- # Count a table's records, as the builder Source: https://gtable.app/docs/builder-api/data/records-count `GET https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}/records/count` How many rows match, for the same view, filter and search a list would use. Requires the `records:read` scope. Operation `apps.records.count`. MCP tool `apps_records_count`. CLI, once published: `gtable apps records count`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ---------------------- | | `viewId` | string | no | Up to 64 characters. | | `search` | string | no | Up to 200 characters. | | `filter` | string | no | Up to 4000 characters. | | `sorts` | string | no | Up to 1000 characters. | | `where` | string | no | Up to 4000 characters. | --- # Total a column, as the builder Source: https://gtable.app/docs/builder-api/data/records-summarise `GET https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}/records/summary` Reductions over the rows a list would return. `summaries` is a JSON object of field id (or exact name) to reduction; values come back raw and unformatted. An unknown field is refused, listing the table's fields. Requires the `records:read` scope. Operation `apps.records.summarise`. MCP tool `apps_records_summarise`. CLI, once published: `gtable apps records summarise`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | ----------- | ------ | -------- | ---------------------- | | `viewId` | string | no | Up to 64 characters. | | `search` | string | no | Up to 200 characters. | | `filter` | string | no | Up to 4000 characters. | | `sorts` | string | no | Up to 1000 characters. | | `where` | string | no | Up to 4000 characters. | | `summaries` | string | yes | Up to 2000 characters. | --- # Count a table's records per value, as the builder Source: https://gtable.app/docs/builder-api/data/records-groups `GET https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}/records/groups` How many rows sit under each value of one column, ordered the way that column sorts. `fieldId` takes the field's id or exact name; an unknown one is refused, listing the table's fields. Requires the `records:read` scope. Operation `apps.records.groups`. MCP tool `apps_records_groups`. CLI, once published: `gtable apps records groups`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | --------- | ------- | -------- | ---------------------- | | `viewId` | string | no | Up to 64 characters. | | `search` | string | no | Up to 200 characters. | | `filter` | string | no | Up to 4000 characters. | | `sorts` | string | no | Up to 1000 characters. | | `where` | string | no | Up to 4000 characters. | | `fieldId` | string | yes | Up to 200 characters. | | `limit` | integer | no | | --- # Read one record, as the builder Source: https://gtable.app/docs/builder-api/data/records-get `GET https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}/records/{recordId}` The whole row, every field. Nothing is masked on the builder plane. Requires the `records:read` scope. Operation `apps.records.get`. MCP tool `apps_records_get`. CLI, once published: `gtable apps records get`. ## Path parameters | Name | Type | Required | Description | | ---------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | | `recordId` | string | yes | | --- # Update a record, as the builder Source: https://gtable.app/docs/builder-api/data/records-update `PATCH https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}/records/{recordId}` A partial update: only the fields sent are changed. The body is \{"fields":\{…}}, keyed by field id or exact name. Logged as this builder, from the Studio. Email, URL and phone text is preserved; visible suspect values are returned in warnings (code suspect\_format, recordId, fieldId, fieldName, message). A warning is a successful save, not a reason to retry. Invalid numeric values, ratings and ISO dates fail with validation\_failed/422. A failing create batch saves no rows. Requires the `records:write` scope. Operation `apps.records.update`. MCP tool `apps_records_update`. CLI, once published: `gtable apps records update`. ## Path parameters | Name | Type | Required | Description | | ---------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | | `recordId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ------------- | ------ | -------- | -------------------- | | `fields` | object | yes | | | `baseVersion` | string | no | Up to 64 characters. | --- # Delete a record, as the builder Source: https://gtable.app/docs/builder-api/data/records-delete `DELETE https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}/records/{recordId}` Removes the row, its links and (out of sight) its comments. The op log keeps all three, so changesets.revert puts the record back whole, same id, same number, its links to records that still exist, its comments, the id of the bundle to pass it comes back as `changesetId`. Requires the `records:write` scope. Operation `apps.records.delete`. MCP tool `apps_records_delete`. CLI, once published: `gtable apps records delete`. ## Path parameters | Name | Type | Required | Description | | ---------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | | `recordId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # Upload a file for an attachment field, as the builder Source: https://gtable.app/docs/builder-api/data/files-upload `POST https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}/files` The builder plane's half of `files.upload`. Takes the BYTES as `multipart/form-data` under `file`, with `recordId` saying which record they will belong to, and answers with the descriptor to write into an attachment field. Attaching it is an ordinary record write, so the file arrives through the same op log and the same undo as any other value. Requires the `records:write` scope. Operation `apps.files.upload`. Not an MCP tool: a file is bytes and a tool call is JSON; upload with REST or the CLI. CLI, once published: `gtable apps files upload`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body Sent as `multipart/form-data`. | Field | Type | Required | Description | | ---------- | ------ | -------- | ---------------------------------------------------------------------------------- | | `file` | string | yes | The bytes. | | `recordId` | string | yes | The record the file will be attached to, so an upload never attached can be swept. | --- # Duplicate a record, as the builder Source: https://gtable.app/docs/builder-api/data/records-duplicate `POST https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}/records/{recordId}/duplicate` A new record with the same values, inserted after the original. Comments and history stay with the original. Requires the `records:write` scope. Operation `apps.records.duplicate`. MCP tool `apps_records_duplicate`. CLI, once published: `gtable apps records duplicate`. ## Path parameters | Name | Type | Required | Description | | ---------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | | `recordId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # Delete several records, as the builder Source: https://gtable.app/docs/builder-api/data/records-delete-many `POST https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}/records/delete` Deletes up to 500 records in one request. A row that refuses is reported in `failed` and does not stop the others. The batch is ONE entry in the op log, so `changesetId` undoes all of it at once. Requires the `records:write` scope. Operation `apps.records.deleteMany`. MCP tool `apps_records_deleteMany`. CLI, once published: `gtable apps records delete-many`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ----------- | --------- | -------- | --------------- | | `recordIds` | string\[] | yes | 1 to 500 items. | --- # A record's history, as the builder Source: https://gtable.app/docs/builder-api/data/records-history `GET https://studio.gtable.app/v1/apps/{appId}/records/{recordId}/history` Every change to the record, one entry per field that moved, newest first. `tableId` names the table it is in. Requires the `records:read` scope. Operation `apps.records.history`. MCP tool `apps_records_history`. CLI, once published: `gtable apps records history`. ## Path parameters | Name | Type | Required | Description | | ---------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `recordId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | --------- | ------- | -------- | ------------- | | `limit` | integer | no | Default `50`. | | `tableId` | string | yes | | --- # A record's comments, as the builder Source: https://gtable.app/docs/builder-api/data/comments-list `GET https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}/records/{recordId}/comments` The notes users have left on this record, oldest first. Requires the `records:read` scope. Operation `apps.comments.list`. MCP tool `apps_comments_list`. CLI, once published: `gtable apps comments list`. ## Path parameters | Name | Type | Required | Description | | ---------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | | `recordId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | ------- | ------- | -------- | ------------- | | `limit` | integer | no | Default `50`. | --- # Comment on a record, as the builder Source: https://gtable.app/docs/builder-api/data/comments-create `POST https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}/records/{recordId}/comments` Adds a comment under this builder's own identity. Mentions may only name users who can see the record. Requires the `records:write` scope. Operation `apps.comments.create`. MCP tool `apps_comments_create`. CLI, once published: `gtable apps comments create`. ## Path parameters | Name | Type | Required | Description | | ---------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | | `recordId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ---------- | --------- | -------- | ---------------------- | | `text` | string | yes | Up to 4000 characters. | | `mentions` | string\[] | no | Up to 20 items. | --- # Who may be named on this record Source: https://gtable.app/docs/builder-api/data/comments-mentionable `GET https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}/records/{recordId}/comments/mentionable` The app users who can currently see this record, the only users an @ may name. Requires the `records:read` scope. Operation `apps.comments.mentionable`. MCP tool `apps_comments_mentionable`. CLI, once published: `gtable apps comments mentionable`. ## Path parameters | Name | Type | Required | Description | | ---------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | | `recordId` | string | yes | | --- # Edit your own comment, as the builder Source: https://gtable.app/docs/builder-api/data/comments-update `PATCH https://studio.gtable.app/v1/apps/{appId}/comments/{commentId}` Only the author may edit a comment, and the builder is the author of the ones they wrote. Requires the `records:write` scope. Operation `apps.comments.update`. MCP tool `apps_comments_update`. CLI, once published: `gtable apps comments update`. ## Path parameters | Name | Type | Required | Description | | ----------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `commentId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ---------- | --------- | -------- | ---------------------- | | `text` | string | yes | Up to 4000 characters. | | `mentions` | string\[] | no | Up to 20 items. | --- # Delete your own comment, as the builder Source: https://gtable.app/docs/builder-api/data/comments-delete `DELETE https://studio.gtable.app/v1/apps/{appId}/comments/{commentId}` Removes a comment this builder wrote. Only the author may delete a comment, whichever plane they stand on. Requires the `records:write` scope. Operation `apps.comments.delete`. MCP tool `apps_comments_delete`. CLI, once published: `gtable apps comments delete`. ## Path parameters | Name | Type | Required | Description | | ----------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `commentId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # Read with SQL, as the builder Source: https://gtable.app/docs/builder-api/data/sql-query `POST https://studio.gtable.app/v1/apps/{appId}/sql/query` One SELECT over one table of the app, every row. The dialect: SELECT \ FROM \
\[WHERE …] \[GROUP BY …] \[ORDER BY …] \[LIMIT n OFFSET m]; UPDATE \
SET col = expr \[WHERE …]; DELETE FROM \
\[WHERE …]. One table per statement; field names or ids (double-quote names with spaces); 'text', numbers, TRUE, FALSE, NULL, CURRENT\_DATE; AND OR NOT, = \<> \< \<= > >=, \[NOT] LIKE, \[NOT] IN (…), IS \[NOT] NULL, \[NOT] BETWEEN, CASE WHEN cond THEN v … \[ELSE v] END and CASE expr WHEN value THEN v … \[ELSE v] END; a select takes its choice's label (any case) or id; functions LENGTH LOWER UPPER TRIM ABS ROUND COALESCE SUBSTR DATE STRFTIME IFNULL INSTR REPLACE; link columns: col HAS ANY ('id', …), col HAS ALL (…), col IS EMPTY. Your text is never executed: it is compiled against your own permissions, and a column you may not read is simply an unknown column. Requires the `records:read` scope. Operation `apps.sql.query`. MCP tool `apps_sql_query`. CLI, once published: `gtable apps sql query`. ## Path parameters | Name | Type | Required | Description | | ------- | ------ | -------- | ----------- | | `appId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ----- | ------ | -------- | ----------------------- | | `sql` | string | yes | Up to 20000 characters. | --- # Change many rows with SQL, as the builder Source: https://gtable.app/docs/builder-api/data/sql-execute `POST https://studio.gtable.app/v1/apps/{appId}/sql/execute` One UPDATE or DELETE; each row is written through the ordinary path, and the statement is one changeset (changesetId), undone by one revert. Up to 500 rows per statement; without a WHERE it needs confirm: true; dryRun counts first. The dialect: SELECT \ FROM \
\[WHERE …] \[GROUP BY …] \[ORDER BY …] \[LIMIT n OFFSET m]; UPDATE \
SET col = expr \[WHERE …]; DELETE FROM \
\[WHERE …]. One table per statement; field names or ids (double-quote names with spaces); 'text', numbers, TRUE, FALSE, NULL, CURRENT\_DATE; AND OR NOT, = \<> \< \<= > >=, \[NOT] LIKE, \[NOT] IN (…), IS \[NOT] NULL, \[NOT] BETWEEN, CASE WHEN cond THEN v … \[ELSE v] END and CASE expr WHEN value THEN v … \[ELSE v] END; a select takes its choice's label (any case) or id; functions LENGTH LOWER UPPER TRIM ABS ROUND COALESCE SUBSTR DATE STRFTIME IFNULL INSTR REPLACE; link columns: col HAS ANY ('id', …), col HAS ALL (…), col IS EMPTY. Your text is never executed: it is compiled against your own permissions, and a column you may not read is simply an unknown column. Requires the `records:write` scope. Operation `apps.sql.execute`. MCP tool `apps_sql_execute`. CLI, once published: `gtable apps sql execute`. ## Path parameters | Name | Type | Required | Description | | ------- | ------ | -------- | ----------- | | `appId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | --------- | ------- | -------- | ----------------------- | | `sql` | string | yes | Up to 20000 characters. | | `confirm` | boolean | no | | | `dryRun` | boolean | no | | --- # Turn a sentence into a filter, as the builder Source: https://gtable.app/docs/builder-api/data/filters-suggest `POST https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}/filters/suggest` A small model call given the table's fields, example values and the current filter; returns the complete filter to apply, or a refusal in plain words. Costs the organisation credits. Requires the `records:read` scope. Operation `apps.filters.suggest`. MCP tool `apps_filters_suggest`. CLI, once published: `gtable apps filters suggest`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ---------- | ----------- | -------- | --------------------- | | `sentence` | string | yes | Up to 600 characters. | | `current` | FilterGroup | no | | --- # Your saved views of a table, as the builder Source: https://gtable.app/docs/builder-api/data/views-list `GET https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}/views` The views YOU saved of this table, in your order. Nobody else's are here, and nobody else sees yours: a view is one person's arrangement, on this plane as on the app's. Empty is ordinary. Requires the `records:read` scope. Operation `apps.views.list`. MCP tool `apps_views_list`. CLI, once published: `gtable apps views list`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | --- # Save a view of a table, as the builder Source: https://gtable.app/docs/builder-api/data/views-create `POST https://studio.gtable.app/v1/apps/{appId}/tables/{tableId}/views` A view visible to you alone. `config` fills in defaults for whatever is left out: an empty filter, no sorts, nothing hidden. Pass its id as `viewId` to apps.records.list to read through it. Requires the `records:write` scope. Operation `apps.views.create`. MCP tool `apps_views_create`. CLI, once published: `gtable apps views create`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `tableId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | -------- | ------ | -------- | --------------------- | | `name` | string | yes | Up to 120 characters. | | `config` | object | no | | --- # Read one of your saved views, as the builder Source: https://gtable.app/docs/builder-api/data/views-get `GET https://studio.gtable.app/v1/apps/{appId}/views/{viewId}` One of your own views. A view that is not yours is refused exactly like one that does not exist. Requires the `records:read` scope. Operation `apps.views.get`. MCP tool `apps_views_get`. CLI, once published: `gtable apps views get`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `viewId` | string | yes | | --- # Change one of your saved views, as the builder Source: https://gtable.app/docs/builder-api/data/views-update `PATCH https://studio.gtable.app/v1/apps/{appId}/views/{viewId}` Only a view that is yours. `config` merges shallowly: a key you send replaces that key, anything you leave out keeps its saved value. Requires the `records:write` scope. Operation `apps.views.update`. MCP tool `apps_views_update`. CLI, once published: `gtable apps views update`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `viewId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | -------- | ------ | -------- | --------------------- | | `name` | string | no | Up to 120 characters. | | `config` | object | no | | --- # Delete one of your saved views, as the builder Source: https://gtable.app/docs/builder-api/data/views-delete `DELETE https://studio.gtable.app/v1/apps/{appId}/views/{viewId}` Only a view that is yours. The table itself is untouched: a view is an arrangement of rows, never the rows. Requires the `records:write` scope. Operation `apps.views.delete`. MCP tool `apps_views_delete`. CLI, once published: `gtable apps views delete`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `viewId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # List the user groups Source: https://gtable.app/docs/builder-api/groups/list `GET https://studio.gtable.app/v1/apps/{appId}/groups` Every group in this app, how many people are in it, and one sentence per table saying what it may see and do. Start here: a group is where all of this app's permissions are decided, and no rule crosses from one group to another. Requires the `schema:read` scope. Operation `groups.list`. MCP tool `groups_list`. CLI, once published: `gtable groups list`. ## Path parameters | Name | Type | Required | Description | | ------- | ------ | -------- | ----------- | | `appId` | string | yes | | --- # Read one group's permissions Source: https://gtable.app/docs/builder-api/groups/get `GET https://studio.gtable.app/v1/apps/{appId}/groups/{groupId}` One group's complete permissions, table by table: which rows it sees, which fields it reads, which of those it may change, and on which rows it may add, edit and delete. Each table also comes with the same sentence the Studio shows, so a tool and a screen never describe a group differently. Requires the `schema:read` scope. Operation `groups.get`. MCP tool `groups_get`. CLI, once published: `gtable groups get`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `groupId` | string | yes | | --- # Replace one group's permissions Source: https://gtable.app/docs/builder-api/groups/set-permissions `PUT https://studio.gtable.app/v1/apps/{appId}/groups/{groupId}/permissions` Writes what this ONE group may do, leaving every other group untouched: permissions are per group and never cross, so there is no ordering to get right and nothing to shadow. Send every table the group should reach; a table you leave out becomes inaccessible to it. Simulate first if you are unsure who is affected. Send the `revision` groups.get gave you as `baseRevision`, and a save made after somebody else changed this group is refused with 409 rather than erasing their change. Requires the `schema:write` scope. Operation `groups.setPermissions`. MCP tool `groups_setPermissions`. CLI, once published: `gtable groups set-permissions`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `groupId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | -------------- | -------------- | -------- | ----------- | | `tables` | "\*" or object | yes | | | `baseRevision` | string | no | | --- # Read the whole permission document Source: https://gtable.app/docs/builder-api/rules/get `GET https://studio.gtable.app/v1/apps/{appId}/rules` Every group and every group's permissions in one object. Deny by default, and what a user may do is the union of their groups, so adding a group can only add. Prefer groups.get when you are working on one group. `revision` identifies this version of the document; send it back as `baseRevision` when you replace it. Requires the `schema:read` scope. Operation `rules.get`. MCP tool `rules_get`. CLI, once published: `gtable rules get`. ## Path parameters | Name | Type | Required | Description | | ------- | ------ | -------- | ----------- | | `appId` | string | yes | | --- # Replace the whole permission document Source: https://gtable.app/docs/builder-api/rules/set `PUT https://studio.gtable.app/v1/apps/{appId}/rules` Replaces every group and every permission at once. This is the call for adding or removing a GROUP; to change what one group may do, prefer groups.setPermissions, which cannot touch the others by accident. Send the `revision` rules.get gave you as `baseRevision`, and a save made after somebody else changed the document is refused with 409 rather than erasing their change. Requires the `schema:write` scope. Operation `rules.set`. MCP tool `rules_set`. CLI, once published: `gtable rules set`. ## Path parameters | Name | Type | Required | Description | | ------- | ------ | -------- | ----------- | | `appId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | -------------- | --------- | -------- | ---------------- | | `version` | 2 | yes | | | `groups` | object\[] | yes | 1 to 100 items. | | `permissions` | object\[] | yes | Up to 100 items. | | `baseRevision` | string | no | | --- # Ask what someone would see Source: https://gtable.app/docs/builder-api/rules/simulate `POST https://studio.gtable.app/v1/apps/{appId}/rules/simulate` Answers 'what would this user see' without granting anything: the rows and fields a set of groups gets on a table, and the same answer per group so you can tell which group is doing the granting. Use this before changing rules, not after. Requires the `schema:read` scope. Operation `rules.simulate`. MCP tool `rules_simulate`. CLI, once published: `gtable rules simulate`. ## Path parameters | Name | Type | Required | Description | | ------- | ------ | -------- | ----------- | | `appId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | --------- | ------------------------------------------ | -------- | ----------- | | `groups` | string\[] | yes | | | `tableId` | string | yes | | | `op` | "read" or "create" or "update" or "delete" | yes | | --- # List an app's pages Source: https://gtable.app/docs/builder-api/pages/list `GET https://studio.gtable.app/v1/apps/{appId}/pages` Every page of the app: the builder's, with their mode and the groups they are shown to, and the pages app users built of their own. Requires the `pages:read` scope. Operation `pages.list`. MCP tool `pages_list`. CLI, once published: `gtable pages list`. ## Path parameters | Name | Type | Required | Description | | ------- | ------ | -------- | ----------- | | `appId` | string | yes | | --- # Add a page Source: https://gtable.app/docs/builder-api/pages/create `POST https://studio.gtable.app/v1/apps/{appId}/pages` Creates the page's definition: name, mode, the groups that see it, optionally the table it is about. It has no code yet, save a version with `pages.code.set`, or ask the assistant to draft one. Requires the `pages:write` scope. Operation `pages.create`. MCP tool `pages_create`. CLI, once published: `gtable pages create`. ## Path parameters | Name | Type | Required | Description | | ------- | ------ | -------- | ----------- | | `appId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ----------- | ----------------------- | -------- | --------------------- | | `name` | string | yes | Up to 120 characters. | | `kind` | "declarative" or "code" | no | Default `"code"`. | | `mode` | "locked" or "template" | no | Default `"locked"`. | | `tableId` | string | no | Up to 64 characters. | | `visibleTo` | "\*" or string\[] | no | Default `"*"`. | --- # Rename a page, change its mode, or change who sees it Source: https://gtable.app/docs/builder-api/pages/update `PATCH https://studio.gtable.app/v1/apps/{appId}/pages/{pageId}` Any of name, mode, tableId and visibleTo. A page has one mode for everyone: it cannot be locked for one group and a Template for another. Requires the `pages:write` scope. Operation `pages.update`. MCP tool `pages_update`. CLI, once published: `gtable pages update`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `pageId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ----------- | ----------------------- | -------- | --------------------- | | `name` | string | no | Up to 120 characters. | | `kind` | "declarative" or "code" | no | | | `mode` | "locked" or "template" | no | | | `tableId` | string | no | Up to 64 characters. | | `visibleTo` | "\*" or string\[] | no | | --- # Delete a page Source: https://gtable.app/docs/builder-api/pages/delete `DELETE https://studio.gtable.app/v1/apps/{appId}/pages/{pageId}` The page, every version of its code, and every copy an app user took of it. Requires the `pages:write` scope. Operation `pages.delete`. MCP tool `pages_delete`. CLI, once published: `gtable pages delete`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `pageId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # Read a page's code Source: https://gtable.app/docs/builder-api/pages/code-get `GET https://studio.gtable.app/v1/apps/{appId}/pages/{pageId}/code` The current version unless `version` names an earlier one. Null when nothing has been saved yet. Requires the `pages:read` scope. Operation `pages.code.get`. MCP tool `pages_code_get`. CLI, once published: `gtable pages code get`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `pageId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | --------- | ------- | -------- | ----------- | | `version` | integer | no | | --- # Save a new version of a page's code Source: https://gtable.app/docs/builder-api/pages/code-set `PUT https://studio.gtable.app/v1/apps/{appId}/pages/{pageId}/code` Never overwrites: every save is a new version, and the page points at the latest. A note says why, for the history. Send `baseVersion` (the page's `version` when you read it) to be refused with 409 if somebody saved since, instead of burying their version under yours. Requires the `pages:write` scope. Operation `pages.code.set`. MCP tool `pages_code_set`. CLI, once published: `gtable pages code set`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `pageId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ------------- | ------------------------------------------- | -------- | ------------------------ | | `source` | string | yes | Up to 400000 characters. | | `note` | string | no | Up to 200 characters. | | `via` | "assistant" or "hand" or "api" or "restore" | no | | | `baseVersion` | integer | no | At least 0. | --- # A page's version history, newest first Source: https://gtable.app/docs/builder-api/pages/versions `GET https://studio.gtable.app/v1/apps/{appId}/pages/{pageId}/versions` Who saved each version and when, with the note they left. The code itself is read by version with pages.code.get. Requires the `pages:read` scope. Operation `pages.versions`. MCP tool `pages_versions`. CLI, once published: `gtable pages versions`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `pageId` | string | yes | | --- # Record what a version did when it ran Source: https://gtable.app/docs/builder-api/pages/report `POST https://studio.gtable.app/v1/apps/{appId}/pages/{pageId}/report` Written by the preview, never by a page and never by an agent: which tables the page asked for, how many rows came back, what was refused, and what it drew. It is what lets `page_check` answer "this page shows an empty table over 140 rows" instead of "it compiled". Requires the `pages:write` scope. Operation `pages.report`. MCP tool `pages_report`. CLI, once published: `gtable pages report`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `pageId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | --------- | ------- | -------- | ----------- | | `version` | integer | yes | At least 1. | | `report` | object | yes | | --- # Publish a version of a page Source: https://gtable.app/docs/builder-api/pages/publish `POST https://studio.gtable.app/v1/apps/{appId}/pages/{pageId}/publish` Points the page at a version that has already been built, the latest by default, or `version` to go back to an earlier one. Every save builds, so publishing compiles nothing: it cannot fail on the code and it cannot show people something different from the preview. A version that did not build is refused with the reason. `files` is the escape hatch for a caller that brings its own build. Requires the `pages:write` scope. Operation `pages.publish`. MCP tool `pages_publish`. CLI, once published: `gtable pages publish`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `pageId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | --------- | --------- | -------- | -------------- | | `version` | integer | no | At least 1. | | `files` | object\[] | no | 1 to 50 items. | --- # What was changed, and what can be undone Source: https://gtable.app/docs/builder-api/changesets/list `GET https://studio.gtable.app/v1/apps/{appId}/changesets` Recent write bundles, newest first. A bundle is one atomic group of operations by one user at one moment, which is what somebody means by "that thing I just did", and each says whether it has since been undone, and with `revertible`, whether it can be. Requires the `records:read` scope. Operation `changesets.list`. MCP tool `changesets_list`. CLI, once published: `gtable changesets list`. ## Path parameters | Name | Type | Required | Description | | ------- | ------ | -------- | ----------- | | `appId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | ------- | ------- | -------- | ------------- | | `limit` | integer | no | Default `50`. | --- # Undo a changeset Source: https://gtable.app/docs/builder-api/changesets/revert `POST https://studio.gtable.app/v1/apps/{appId}/changesets/{changesetId}/revert` Replays a bundle's operations backwards, through the same permission compiler as any other write, so undoing a delete re-creates the row only if you could have created it. An update puts back ONLY the fields it changed, so somebody else's edit to another field of the same record survives; if they changed one of the SAME fields since, this refuses and says whose, and `confirm: true` overwrites theirs anyway. The revert is itself recorded, so reverting a revert is just undo again. Only a bundle of record creates, edits and deletes can be undone, and the list marks each one `revertible`: a comment, a saved view, a page or a change to the app's SHAPE is refused up front with the reason, because putting a definition back would run its migration in reverse, and a column that returns is a column whose contents went. A deleted field or table, and a column a type change converted, is put back from the trash instead (trash.restore), values and all. Every row it can put back is undone in ONE transaction, and a row deleted since is listed in `skipped` rather than failing the rest. Requires the `records:write` scope. Operation `changesets.revert`. MCP tool `changesets_revert`. CLI, once published: `gtable changesets revert`. ## Path parameters | Name | Type | Required | Description | | ------------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `changesetId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | --------- | ------- | -------- | ----------- | | `confirm` | boolean | no | | --- # What was deleted, and can still be put back Source: https://gtable.app/docs/builder-api/trash/list `GET https://studio.gtable.app/v1/apps/{appId}/trash` The app's trash: every deleted table and field, and every column a type change converted, with how many records' values it holds and when it expires (thirty days after the change). Restore one with trash.restore. Requires the `schema:read` scope. Operation `trash.list`. MCP tool `trash_list`. CLI, once published: `gtable trash list`. ## Path parameters | Name | Type | Required | Description | | ------- | ------ | -------- | ----------- | | `appId` | string | yes | | --- # Put a deleted table or field back Source: https://gtable.app/docs/builder-api/trash/restore `POST https://studio.gtable.app/v1/apps/{appId}/trash/{trashId}/restore` Recreates what was deleted with the SAME ids, a table with its rows and its links, a field with its values, a converted column with the values it held before, and the grants that named it. Records created since keep theirs; a value is only written back to a record that still exists, and a link only where both records do. Refused when something now uses the id (restore the table before its fields). Requires the `schema:write` scope. Operation `trash.restore`. MCP tool `trash_restore`. CLI, once published: `gtable trash restore`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `trashId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # A suite's directory Source: https://gtable.app/docs/builder-api/users/list `GET https://studio.gtable.app/v1/users` Everyone who can sign in at this suite's apps, and which apps they are in. One user holds one account per suite and can be a member of many of its apps, so start here rather than inviting the same colleague again for each app. `apps` carries the apps themselves with the groups the user holds in each, because "how many" is the one fact about a user nobody can act on. Requires the `members:read` scope. Operation `users.list`. MCP tool `users_list`. CLI, once published: `gtable users list`. ## Query parameters | Name | Type | Required | Description | | --------- | ------- | -------- | --------------------- | | `limit` | integer | no | Default `50`. | | `cursor` | string | no | Up to 512 characters. | | `suiteId` | string | yes | Up to 64 characters. | | `search` | string | no | Up to 200 characters. | --- # Add someone to a suite's directory Source: https://gtable.app/docs/builder-api/users/invite `POST https://studio.gtable.app/v1/users` Creates a directory entry. This alone gives access to nothing: put them in an app with `members.add` afterwards. Being in the directory and having access to an app are separate on purpose, an assistant onboarding a colleague should not have to decide what they may see in the CRM. Requires the `members:write` scope. Operation `users.invite`. MCP tool `users_invite`. CLI, once published: `gtable users invite`. ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | --------- | --------------------- | -------- | --------------------- | | `suiteId` | string | yes | Up to 64 characters. | | `email` | string | yes | | | `name` | string | no | Up to 120 characters. | | `kind` | "builder" or "member" | no | Default `"member"`. | --- # Rename or suspend someone Source: https://gtable.app/docs/builder-api/users/update `PATCH https://studio.gtable.app/v1/users/{userId}` Suspending keeps the user in the directory and in their apps, but refuses their next sign-in and every request from their credentials. Reinstating is the same call with status active. Requires the `members:write` scope. Operation `users.update`. MCP tool `users_update`. CLI, once published: `gtable users update`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `userId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | -------- | ------------------------------------ | -------- | ----------- | | `name` | string or null | no | | | `status` | "active" or "invited" or "suspended" | no | | --- # Remove someone from a suite Source: https://gtable.app/docs/builder-api/users/remove `DELETE https://studio.gtable.app/v1/users/{userId}` Takes them out of the directory and out of every app in this suite. Their sign-in stops working immediately. The records they created stay, those belong to the app, not to them. Requires the `members:write` scope. Operation `users.remove`. MCP tool `users_remove`. CLI, once published: `gtable users remove`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `userId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # Get someone's personal set-password link Source: https://gtable.app/docs/builder-api/users/password-link `POST https://studio.gtable.app/v1/users/{userId}/password-link` A one-time link that lets this person, and only this person, set their password at the suite's address. Being in the directory proves nothing, the platform sends no email, so this is how a person without Google gets in: you hand them the link. It works once, for seven days, and making a new one retires the last. Refused for someone who can already sign in. The URL is returned once and never stored in clear. Requires the `members:write` scope. Operation `users.passwordLink`. Not an MCP tool: a set-password link is a credential for another person and never passes through a model. CLI, once published: `gtable users password-link`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `userId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # List members Source: https://gtable.app/docs/builder-api/members/list `GET https://studio.gtable.app/v1/apps/{appId}/members` Everyone invited into this app and which groups they are in. Requires the `members:read` scope. Operation `members.list`. MCP tool `members_list`. CLI, once published: `gtable members list`. ## Path parameters | Name | Type | Required | Description | | ------- | ------ | -------- | ----------- | | `appId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | -------- | ------- | -------- | --------------------- | | `limit` | integer | no | Default `50`. | | `cursor` | string | no | Up to 512 characters. | | `search` | string | no | Up to 200 characters. | --- # Put someone in an app Source: https://gtable.app/docs/builder-api/members/add `POST https://studio.gtable.app/v1/apps/{appId}/members` Gives a user from the directory access to this app, in one or more groups. Their access is the union of what those groups grant, so adding a group can only add. Pass an email that is not in the directory yet and they are added to it first. Requires the `members:write` scope. Operation `members.add`. MCP tool `members_add`. CLI, once published: `gtable members add`. ## Path parameters | Name | Type | Required | Description | | ------- | ------ | -------- | ----------- | | `appId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | -------- | --------- | -------- | -------------------- | | `userId` | string | no | Up to 64 characters. | | `email` | string | no | | | `groups` | string\[] | yes | | --- # Change someone's groups in an app Source: https://gtable.app/docs/builder-api/members/update `PATCH https://studio.gtable.app/v1/apps/{appId}/members/{userId}` Replaces the whole group list for this user in this app. Their access is recomputed as the union of the new list; nothing else about them changes, in this app or any other. Requires the `members:write` scope. Operation `members.update`. MCP tool `members_update`. CLI, once published: `gtable members update`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `userId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | -------- | --------- | -------- | ----------- | | `groups` | string\[] | yes | | --- # Remove someone from an app Source: https://gtable.app/docs/builder-api/members/remove `DELETE https://studio.gtable.app/v1/apps/{appId}/members/{userId}` Takes away access to this app only. They stay in the directory and keep every other app they are in, and the records they created stay, those belong to the app. Requires the `members:write` scope. Operation `members.remove`. MCP tool `members_remove`. CLI, once published: `gtable members remove`. ## Path parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `appId` | string | yes | | | `userId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # List invite links Source: https://gtable.app/docs/builder-api/invites/list `GET https://studio.gtable.app/v1/suites/{suiteId}/invites` Every invite link of this suite, across its apps, with the app and groups it grants, how many times it was used, and whether it is still live. The URL itself is never returned after creation. Requires the `members:read` scope. Operation `invites.list`. MCP tool `invites_list`. CLI, once published: `gtable invites list`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `suiteId` | string | yes | | ## Query parameters | Name | Type | Required | Description | | -------- | ------- | -------- | --------------------- | | `limit` | integer | no | Default `50`. | | `cursor` | string | no | Up to 512 characters. | --- # Create an invite link Source: https://gtable.app/docs/builder-api/invites/create `POST https://studio.gtable.app/v1/suites/{suiteId}/invites` Mints a link that adds whoever opens it to one app of this suite, in exactly these groups, after they sign in at the suite's address. The link is the invitation: it works even when the suite is invitation-only. It can bring a NEW person into the directory; it can never be used to set a password for an address already there, which needs that person's own link (`users.passwordLink`). Single use unless you pass `maxUses`, or `null` for unlimited. The URL is returned once and never stored in clear; copy it now. Requires the `members:write` scope. Operation `invites.create`. MCP tool `invites_create`. CLI, once published: `gtable invites create`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `suiteId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | --------------- | --------------- | -------- | ------------------------------------- | | `appId` | string | yes | Up to 64 characters. | | `groups` | string\[] | yes | | | `label` | string | no | Up to 120 characters. | | `expiresInDays` | integer | no | Default `14`. At least 1. At most 90. | | `maxUses` | integer or null | no | | --- # Revoke an invite link Source: https://gtable.app/docs/builder-api/invites/revoke `DELETE https://studio.gtable.app/v1/suites/{suiteId}/invites/{inviteId}` Stops a link working from now on. Users who already joined through it keep their membership; revoking a link is not removing members. Requires the `members:write` scope. Operation `invites.revoke`. MCP tool `invites_revoke`. CLI, once published: `gtable invites revoke`. ## Path parameters | Name | Type | Required | Description | | ---------- | ------ | -------- | ----------- | | `suiteId` | string | yes | | | `inviteId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # The hostnames a suite answers on Source: https://gtable.app/docs/builder-api/domains/list `GET https://studio.gtable.app/v1/suites/{suiteId}/domains` Every hostname attached to this suite, active, pending its DNS proof, provisioning its certificate, or released, with when it was verified. A pending one carries the records to publish; a provisioning one carries its certificate's progress. Its apps are reached underneath each active one. Requires the `account:read` scope. Operation `domains.list`. MCP tool `domains_list`. CLI, once published: `gtable domains list`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `suiteId` | string | yes | | --- # Attach a hostname to a suite Source: https://gtable.app/docs/builder-api/domains/attach `POST https://studio.gtable.app/v1/suites/{suiteId}/domains` Routes a hostname to this suite: its people sign in there, and each of its apps answers underneath at its own slug, `/crm`, `/hr`. A suite is one front door, so one address serves every app in it. The suite's own platform subdomain is active at once. Any other hostname is a domain of your own, a paid option, and subdomains only (`app.example.com`, not `example.com`), and starts `pending`: the answer carries a TXT record that proves you own it and a CNAME that sends traffic here. Publish both, then call `domains.verify`: once the TXT shows, the hostname is `provisioning` while a certificate is issued, and `active` when it serves. A hostname another organisation answers on is refused. Requires the `schema:write` scope. Operation `domains.attach`. MCP tool `domains_attach`. CLI, once published: `gtable domains attach`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `suiteId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ---------- | ------ | -------- | --------------------- | | `hostname` | string | yes | Up to 253 characters. | --- # Check a hostname's DNS and certificate, and activate it Source: https://gtable.app/docs/builder-api/domains/verify `POST https://studio.gtable.app/v1/suites/{suiteId}/domains/{hostname}/verify` Moves a hostname one step forward and says where it is. `pending`: looks up the TXT record `domains.attach` asked for. When it shows, the hostname becomes `provisioning`, Cloudflare is validating it and issuing a certificate, which needs the CNAME to point here and takes minutes, and `certificate` carries both states and anything Cloudflare complained about. `active`: it serves, and starts routing to this suite. Safe to call as often as you like while DNS propagates. Requires the `schema:write` scope. Operation `domains.verify`. MCP tool `domains_verify`. CLI, once published: `gtable domains verify`. ## Path parameters | Name | Type | Required | Description | | ---------- | ------ | -------- | ----------- | | `suiteId` | string | yes | | | `hostname` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # Detach a hostname Source: https://gtable.app/docs/builder-api/domains/detach `DELETE https://studio.gtable.app/v1/suites/{suiteId}/domains/{hostname}` The hostname stops routing to this suite at once, for every app under it; people signed in there are signed out by the next request. Records, users and members are untouched. Requires the `schema:write` scope. Operation `domains.detach`. MCP tool `domains_detach`. CLI, once published: `gtable domains detach`. ## Path parameters | Name | Type | Required | Description | | ---------- | ------ | -------- | ----------- | | `suiteId` | string | yes | | | `hostname` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # Your builder keys and connected agents Source: https://gtable.app/docs/builder-api/keys/list `GET https://studio.gtable.app/v1/keys` Every credential acting as you on the builder plane: keys you made and AI clients you connected to the Studio. Never the secrets. Any valid credential for this API: no scope is needed. Operation `keys.list`. MCP tool `keys_list`. CLI, once published: `gtable keys list`. --- # Create a builder API key Source: https://gtable.app/docs/builder-api/keys/create `POST https://studio.gtable.app/v1/keys` Mints a key that acts as you, the builder, in ONE organisation, the one you name, narrowed to the scopes you choose. Only from a signed-in browser: a key or a connected agent cannot mint another. Expires in 90 days unless you choose otherwise; `expiresInDays: null` is a key that lives until revoked. Returned once; never stored in clear. For CI, and for the CLI when there is no browser. Any valid credential for this API: no scope is needed. Operation `keys.create`. Not an MCP tool: keys are made by a person in a signed-in browser. CLI, once published: `gtable keys create`. ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | ## Request body | Field | Type | Required | Description | | ---------------- | --------------- | -------- | --------------------- | | `organizationId` | string | yes | | | `name` | string | yes | Up to 120 characters. | | `scopes` | string\[] | yes | | | `expiresInDays` | integer or null | no | | --- # Revoke one of your builder credentials Source: https://gtable.app/docs/builder-api/keys/revoke `DELETE https://studio.gtable.app/v1/keys/{credentialId}` Stops a key or a connected agent working, immediately. Only your own, and only from a signed-in browser. Any valid credential for this API: no scope is needed. Operation `keys.revoke`. Not an MCP tool: keys are revoked by a person in a signed-in browser. CLI, once published: `gtable keys revoke`. ## Path parameters | Name | Type | Required | Description | | -------------- | ------ | -------- | ----------- | | `credentialId` | string | yes | | ## Headers | Name | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Idempotency-Key` | string | no | Any unique string. Sending the same key with the same request again returns the first response (marked `Idempotency-Replayed: true`) instead of running it twice. Reusing it for a different request is refused with 409. Kept for 24 hours. Up to 255 characters. | --- # List the skills Source: https://gtable.app/docs/builder-api/skills/list `GET https://studio.gtable.app/v1/skills` The manual, as a list of ids and one-liners. Each skill is detailed instructions for one area, permissions, an app's database, its interface, written for whoever is holding these tools. Check this list before doing anything non-trivial, then read the one you need with skills.get rather than guessing what it would say. Any valid credential for this API: no scope is needed. Operation `skills.list`. MCP tool `skills_list`. CLI, once published: `gtable skills list`. --- # Read one skill Source: https://gtable.app/docs/builder-api/skills/get `GET https://studio.gtable.app/v1/skills/{skillId}` The full text of one skill, as markdown: how the tools in that area fit together, the mistakes that are easy to make, and what never to do. Cheap to read and much cheaper than getting a permission change wrong. Any valid credential for this API: no scope is needed. Operation `skills.get`. MCP tool `skills_get`. CLI, once published: `gtable skills get`. ## Path parameters | Name | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `skillId` | string | yes | | --- # The models an assistant menu may offer Source: https://gtable.app/docs/builder-api/models/list `GET https://studio.gtable.app/v1/models` The platform's model catalogue: each model's id, name, what it is good at and how fast, clever and expensive it is. The same list for everybody; an organisation's own menu and a suite's are chosen from it. Any valid credential for this API: no scope is needed. Operation `models.list`. MCP tool `models_list`. CLI, once published: `gtable models list`. --- # This organisation's referral code and what it has earned Source: https://gtable.app/docs/builder-api/referrals/get `GET https://studio.gtable.app/v1/organizations/{organizationId}/referrals` The code a sign-up link carries, the referrals it brought (status and date, never who), and the credits promised for them. Rewards stay pending until billing is live, and while sign-ups are closed a code cannot bring anybody in. Admins, and builders on every suite. Requires the `account:read` scope. Operation `referrals.get`. MCP tool `referrals_get`. CLI, once published: `gtable referrals get`. ## Path parameters | Name | Type | Required | Description | | ---------------- | ------ | -------- | ----------- | | `organizationId` | string | yes | | --- # The gtable CLI Source: https://gtable.app/docs/cli/overview > **Warning: Coming soon** > > The CLI is not published yet, so there is nothing to install today. Until it is, use the > [REST API](/docs/api/overview) from a script, or [connect an agent](/docs/agents/setup). This > page shows what it will look like, so you can plan around it. ## What it will look like Every operation of both APIs is a command, named after it: `records.list` is `gtable records list`. Path parameters come first, in order; everything else is a flag, or `--body` with the whole request as JSON. A new endpoint is a new command the day it ships, with its help text already correct. ```bash gtable records list tbl_8fj3kq2wmv5d --view-id viw_e4ks9mz2bh7w gtable records get tbl_8fj3kq2wmv5d rec_4nqw8e2hzk7s gtable records create tbl_8fj3kq2wmv5d --records '[{"Name":"New record","Amount":1000}]' gtable files upload tbl_8fj3kq2wmv5d --file ./contract.pdf --record-id rec_4nqw8e2hzk7s gtable rules simulate app_5mg8rw2kx4tn --groups grp_v6nh3ta8rk2e --table-id tbl_8fj3kq2wmv5d --op read ``` Each page of the [App API](/docs/app-api/records/list) and [Builder API](/docs/builder-api/apps/list) references names its command. ## Signing in Three ways, one per situation: ```bash gtable auth login # the Studio, in your browser gtable auth login --target https://your-suite.gtable.app/your-app # one app gtable auth login --device # a machine with no browser ``` In CI, skip the login: set `GTABLE_TOKEN` to one of your keys and `GTABLE_URL` to the Studio or the app it is for. On a desktop, tokens are kept in the system keychain. ## Made for scripts and agents - **Output is JSON whenever it is not going to a terminal**, so nobody has to remember `--json`. - **The exit code says what kind of failure it was**: 2 usage, 3 authentication, 4 not found, 5 rate limited, 6 conflict, 7 plan. The API's error envelope goes to stderr. - **Deleting asks for the name**, as the Studio does: `gtable tables delete app_5mg8rw2kx4tn tbl_x2c7nw4hq9fk --confirm "Old deals"`. - `gtable mcp` will bridge a local stdio client to the MCP server, and `gtable doctor` will say whether a problem is your setup, the network or gtable. ## More than MCP, by design Two things will be possible from the CLI and not from MCP: uploading files, because a terminal can send bytes and a model cannot, and walking every page of a list, because a script can loop. Everything else is at parity by construction. --- # Changelog Source: https://gtable.app/docs/changelog ## 2026-10-06: The developer documentation, rebuilt These pages were rebuilt around gtable's two planes. The App API and Builder API references are now generated from the platform's own OpenAPI documents, one page per operation, and [Set up your agent](/docs/agents/setup) is generated from the same source as the setup prompt in the product, so the two cannot disagree. For agents: every page is available as Markdown (append `.md`), the whole site as [llms.txt](/docs/llms.txt) and [llms-full.txt](/docs/llms-full.txt), and the documentation has its own MCP server at `https://gtable.app/docs/mcp`. ## 2026-10-06: A new name: gtable The product is now called gtable. New API keys start with `gt_`; keys issued before the rename start with `dva_` and keep working unchanged. The contract version header is `x-gtable-api-version`. ## 2026-10-05: Connect an agent with one prompt **Settings, Connections** in an app and the Studio's **Developer** page now lead with **Copy setup prompt**: paste it into your agent and it connects itself. Each suite serves the guide the agent follows at `/agent-setup.md`. A suite's MCP server also accepts clients that send an OAuth `resource`, which some were refused for before.