API & MCP
Querying data
These are the endpoints you'll use most: they turn any dataset into queryable data over HTTP. Everything here is read-only, capped at 1,000 rows per request, and authenticated with a bearer key. The {table_name} in every path is the dataset's physical gp_… name, which you get from listing datasets.
Query rows — POST /tables/{table_name}#
The REST facade is the simplest way to read a dataset: pick columns, filter, sort and paginate with a JSON body. No SQL required.
cURL
curl -X POST "https://<your-gopie-host>/v1/api/tables/gp_DzuutVJef9rSG" \
-H "Authorization: Bearer gp_pub_…" \
-H "Content-Type: application/json" \
-d '{
"columns": ["product", "revenue"],
"filter": { "filter[category]": "Electronics", "filter[revenue]gte": "500" },
"sort": "-revenue",
"limit": 50,
"page": 1
}'JavaScript
const res = await fetch(
"https://<your-gopie-host>/v1/api/tables/gp_DzuutVJef9rSG",
{
method: "POST",
headers: {
Authorization: "Bearer gp_pub_…",
"Content-Type": "application/json"
},
body: JSON.stringify({
columns: ["product", "revenue"],
filter: { "filter[category]": "Electronics", "filter[revenue]gte": "500" },
sort: "-revenue",
limit: 50,
page: 1
})
}
);
const { data, count } = await res.json();Python
import requests
res = requests.post(
"https://<your-gopie-host>/v1/api/tables/gp_DzuutVJef9rSG",
headers={"Authorization": "Bearer gp_pub_…"},
json={
"columns": ["product", "revenue"],
"filter": {"filter[category]": "Electronics", "filter[revenue]gte": "500"},
"sort": "-revenue",
"limit": 50,
"page": 1,
},
)
rows = res.json()["data"]Every field is optional — an empty body {} returns the first page of all columns.
| Field | Meaning |
|---|---|
columns | Array of columns to return; omit for all |
filter | Map of conditions, AND-ed together — see below |
sort | Comma-separated columns; - prefix for descending (-revenue,product) |
limit | Rows per page, capped at 1,000 |
page | 1-based page number |
snapshot_version | Read the dataset as of a past version |
Filter syntax#
This is the one non-obvious part. Filter keys encode the column and operator as filter[column]op — a plain {"category": "Electronics"} key is silently ignored:
| Key form | Condition |
|---|---|
filter[col] | equals |
filter[col]neq | not equal |
filter[col]gt / filter[col]gte | greater than / or equal |
filter[col]lt / filter[col]lte | less than / or equal |
Comparison operators (gt, lte, …) work on numeric values only; text values filter by equality. There is no like, in, or between — reach for SQL when you need those. An unrecognised operator is a 400:
{ "code": 400, "error": "invalid filter operator", "message": "Invalid rest params" }Response#
{
"data": [ { "product": "27-inch Monitor", "revenue": 2490 } ],
"count": 42,
"executionTime": 23
}count is the total matching rows (not the page size) — use it with limit and page to walk the whole result. If the count couldn't be computed it comes back as -1; the data is still valid.
Run SQL — POST /sql#
When the filter grammar isn't enough, send arbitrary read-only SQL. Authorisation is derived by parsing the table references out of your query, so a key can only run SQL over datasets it can already reach.
curl -X POST "https://<your-gopie-host>/v1/api/sql" \
-H "Authorization: Bearer gp_pub_…" \
-H "Content-Type: application/json" \
-d '{"query": "SELECT category, SUM(revenue) AS total FROM gp_DzuutVJef9rSG GROUP BY 1 ORDER BY 2 DESC"}'{
"columns": ["category", "total"],
"count": 3,
"data": [ { "category": "Electronics", "total": 148156.86 } ],
"executionTime": 75
}| Body field | Meaning |
|---|---|
query | The SQL (required) |
limit | Rows to return, capped at 1,000 |
offset | Rows to skip |
snapshot_version / snapshot_timestamp | Time travel to a past version |
Only SELECT, WITH, DESCRIBE and SUMMARIZE are allowed — the same read-only rules as the SQL playground. Anything that writes, and any attempt to send more than one statement, is rejected:
{ "code": 400, "error": "multiple sql statements are not allowed", "message": "Invalid SQL query" }NoteNotice the response shapes differ:
/sqlincludes acolumnsarray,/tablesdoes not. Both includecountandexecutionTime.
Generate SQL from a question — POST /nl2sql#
Turn a plain-English question into SQL without running it — useful for building query UIs or letting users preview what a question resolves to.
curl -X POST "https://<your-gopie-host>/v1/api/nl2sql" \
-H "Authorization: Bearer gp_pub_…" \
-H "Content-Type: application/json" \
-d '{"query": "total revenue by category", "table": "gp_DzuutVJef9rSG"}'{ "sql": "SELECT \"category\", SUM(\"revenue\") AS \"total_revenue\"\nFROM \"gp_DzuutVJef9rSG\"\nGROUP BY \"category\"\nORDER BY \"total_revenue\" DESC" }Provide exactly one of table (a single table name) or tables (an array, for multi-table questions). If the question can't be answered from the schema, you get a 200 explaining why instead of SQL:
{ "sql": "", "non_sql_response": true, "message": "The data doesn't contain information about customer support tickets." }This is a single-shot translation with no conversation memory. For a full analytical conversation — schema search, self-correcting SQL, grounded prose — use the Chat API.
Inspect structure — schemas & summaries#
GET /schemas/{table_name} returns the columns and their types:
{ "schema": [ { "column_name": "revenue", "column_type": "DOUBLE" } ] }GET /summary/{table_name} returns per-column statistics — min, max, average, quartiles, distinct and null counts, plus any column descriptions:
{ "summary": [
{ "column_name": "revenue", "column_type": "DOUBLE",
"min": 0, "max": 9999, "avg": 123.4, "approx_unique": 812,
"null_percentage": 0, "description": "Line-item revenue in USD" }
] }By default the summary is served from a fast cached copy. Add ?source=olap to force a live recompute over the whole table — accurate but expensive on large datasets, so use it deliberately. The full statistical summary uses SUMMARIZE.
Get an OpenAPI spec — GET /openapi/{table_name}#
Returns an OpenAPI 3.0 document generated live from the dataset's actual schema — its columns become typed response properties. Feed it to Postman, a client generator, or any tool that speaks OpenAPI to scaffold a typed client for that specific dataset.