API reference

Every endpoint, with examples

The Studio is a thin shell over a typed HTTP API. Whatever SelectStar does in the browser, you can do from a script, from CI, or from your own agent. Below is every public endpoint with parameters, returns, and copy-paste examples.

Connection — SQL Mode

2 endpoints in this section.

POST /api/connect

endpoint
POST /api/connect Body: { connectionString: string } Returns: { sessionId, schema, canWrite, suggestedQuestions }

Opens a connection to a live database (SQLite, PostgreSQL, or MySQL-ready), pings it, introspects the schema, and creates a session. Returns the schema snapshot, write-privilege flag, and suggested starter questions based on the real tables. Per-table COUNT queries have a 3-second statement_timeout so a single slow table doesn't block introspection.

Parameters

connectionString · string
A SQLite file path, 'demo' for the bundled DB, or a postgresql:// / mysql:// URL.

Returns

Session object with schema, canWrite flag, and suggested questions.

Example

const res = await fetch("/api/connect", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ connectionString: "demo" }),
});
const { sessionId, schema, suggestedQuestions } = await res.json();

POST /api/refresh-schema

endpoint
POST /api/refresh-schema Body: { sessionId: string } Returns: { schema }

Re-runs introspection on the connected database. Useful after a DDL change in Zen mode. Returns the fresh schema snapshot.

Parameters

sessionId · string
The session to refresh.

Returns

Updated schema snapshot.

Example

await fetch("/api/refresh-schema", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ sessionId }),
});

Classic Mode — Upload & Edit

4 endpoints in this section.

POST /api/classic/upload

endpoint
POST /api/classic/upload Content-Type: multipart/form-data files: one or more .csv / .tsv / .xlsx / .xlsm / .xlsb / .ods Returns: { sessionId, tables, suggestedQuestions }

Accepts multipart/form-data with one or more files. Each file is parsed — XLSX files produce one table per sheet — and all tables are registered in the same in-memory SQLite workspace under a fresh sessionId. The agent can then JOIN across tables from different files. Allowed extensions: .csv, .tsv, .txt, .xlsx, .xlsm, .xlsb, .ods. Max 25 MB per file.

Parameters

files · File[]
One or more files under any field name (file, files, etc.). Max 25 MB each.

Returns

Session object with tables (one per sheet), dialect ('xlsx' or 'csv'), and suggested questions.

Example

const form = new FormData();
form.append("files", fileInput.files[0]);  // sales_q1.xlsx
form.append("files", fileInput.files[1]);  // sales_q2.xlsx

const res = await fetch("/api/classic/upload", {
  method: "POST",
  body: form,  // don't set Content-Type — browser sets boundary
});
const { sessionId, tables } = await res.json();

GET /api/classic/data

endpoint
GET /api/classic/data?sessionId=...&table=...&limit=...&offset=... Returns: { columns, rows, totalRows } | { tables: TableProfile[] }

Reads rows from a Classic Mode table. If no table is specified, returns the list of tables in the workspace with their profiles (column types, row counts, null percentages). Used by the spreadsheet grid to render cells.

Parameters

sessionId · string
The Classic Mode session.
table · string?
Table name. Omit to list all tables.
limit · number?
Row limit (default 100).
offset · number?
Row offset for pagination.

Returns

Either a table list (no table param) or { columns, rows, totalRows }.

Example

const res = await fetch(
  `/api/classic/data?sessionId=${sessionId}&table=sales_q1&limit=50`
);
const { columns, rows, totalRows } = await res.json();

POST /api/classic/data

endpoint
POST /api/classic/data Body: { sessionId, table, op, ... } Returns: { ok, rows? }

Direct cell-edit API for the spreadsheet grid. Supports six operations: setCell, addRow, deleteRow, renameColumn, addColumn, deleteColumn. Every edit writes through to the in-memory SQLite table, so SQL queries issued by the chat agent see edits live — the spreadsheet and the in-memory tables are the same object.

Parameters

sessionId · string
The Classic Mode session.
table · string
Table to edit.
op · "setCell" | "addRow" | "deleteRow" | "renameColumn" | "addColumn" | "deleteColumn"
The edit operation.
rowIndex · number?
Row index (setCell, deleteRow).
column · string?
Column name (setCell, renameColumn, addColumn, deleteColumn).
value · any?
New value (setCell, addRow).
oldName · string?
Old column name (renameColumn).
newName · string?
New column name (renameColumn).
name · string?
Column name (addColumn, deleteColumn).
dtype · string?
Column dtype (addColumn).

Returns

{ ok: true, rows?: updated rows }.

Example

// Edit a cell
await fetch("/api/classic/data", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    sessionId, table: "sales_q1", op: "setCell",
    rowIndex: 4, column: "revenue", value: 12500,
  }),
});

// Add a column
await fetch("/api/classic/data", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    sessionId, table: "sales_q1", op: "addColumn",
    name: "region", dtype: "text",
  }),
});

GET /api/classic/download

endpoint
GET /api/classic/download?sessionId=...&table=...&format=... Returns: binary file (CSV or XLSX)

Exports an edited Classic Mode table back out as a CSV or XLSX file. Use this after you've edited cells, added columns, or removed rows in the spreadsheet grid — the exported file reflects every edit.

Parameters

sessionId · string
The Classic Mode session.
table · string
Table to export.
format · "csv" | "xlsx"
Export format (default csv).

Returns

Binary file download (Content-Type: text/csv or application/vnd.openxmlformats-officedocument.spreadsheetml.sheet).

Example

const res = await fetch(
  `/api/classic/download?sessionId=${sessionId}&table=sales_q1&format=xlsx`
);
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "sales_q1_edited.xlsx";
a.click();

Agent Turn

2 endpoints in this section.

POST /api/chat

endpoint
POST /api/chat Body: { sessionId, message, zenMode? } Returns: SSE stream

Runs one agent turn. Returns a text/event-stream of step / sql / canvas / token / reply_done / done events. The orchestrator runs the Router, then routes to the appropriate agents (in parallel where possible), then streams the Synthesis reply.

Parameters

sessionId · string
The session to run the turn in (SQL or Classic Mode).
message · string
The user's natural-language question.
zenMode · boolean?
Override the session's Zen mode for this turn.

Returns

SSE stream: step (agent started), sql (query text), canvas (artifact), token (synthesis streaming), reply_done, done.

Example

const res = await fetch("/api/chat", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    sessionId,
    message: "Chart top 8 products by sales",
  }),
});

const reader = res.body.getReader();
// Read SSE events: step, sql, canvas, token, reply_done, done

POST /api/confirm-write

endpoint
POST /api/confirm-write Body: { sessionId, pendingId, action: "confirm" | "rollback" | "cancel" } Returns: { result, rowCount? }

Resolves a pending Zen-mode write. 'confirm' executes the write, 'rollback' executes inside a transaction that is immediately rolled back (so you see the affected row count without committing), 'cancel' discards the pending write. Every resolution is audit-logged.

Parameters

sessionId · string
The session owning the pending write.
pendingId · string
The id of the pending write (10-minute TTL).
action · "confirm" | "rollback" | "cancel"
How to resolve the pending write.

Returns

Result object with rowCount (for confirm/rollback) and audit log entry.

Example

await fetch("/api/confirm-write", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    sessionId, pendingId,
    action: "rollback",  // dry-run: see row count without committing
  }),
});

Sessions & Audit

6 endpoints in this section.

GET /api/sessions

endpoint
GET /api/sessions Returns: { sessions: Session[] }

Lists recent sessions. Each session is one database connection (SQL Mode) or one file upload workspace (Classic Mode) with its own chat history, canvas objects, and audit log.

Returns

Array of session summaries.

Example

const { sessions } = await fetch("/api/sessions").then(r => r.json());

GET /api/sessions/:id

endpoint
GET /api/sessions/:id Returns: { session, messages, canvas, audit }

Loads a full session: metadata, chat messages, canvas object history, and the audit log. Used when resuming a session.

Parameters

id · string
The session id.

Returns

Full session state including messages, canvas, and audit log.

Example

const data = await fetch(`/api/sessions/${id}`).then(r => r.json());

PATCH /api/sessions/:id

endpoint
PATCH /api/sessions/:id Body: { zenMode?, name? } Returns: { session }

Updates a session — typically toggling Zen mode or renaming. Zen mode can only be enabled if the connected role has write privileges.

Parameters

zenMode · boolean?
Toggle write-permission gating.
name · string?
Rename the session.

Returns

Updated session object.

Example

await fetch(`/api/sessions/${id}`, {
  method: "PATCH",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ zenMode: true }),
});

DELETE /api/sessions/:id

endpoint
DELETE /api/sessions/:id Returns: { ok }

Closes the database connection (or clears the Classic Mode workspace) and deletes the session, its messages, canvas objects, and audit log.

Parameters

id · string
The session id to delete.

Returns

{ ok: true } on success.

Example

await fetch(`/api/sessions/${id}`, { method: "DELETE" });

GET /api/audit

endpoint
GET /api/audit?sessionId=... Returns: AuditEntry[]

Returns the write-audit trail for a session. Every write resolution — executed, rolled-back, cancelled, or failed — is logged with the SQL text, timestamp, and row count.

Parameters

sessionId · string
The session to fetch the audit log for.

Returns

Array of audit entries with SQL, action, timestamp, and rowCount.

Example

const audit = await fetch(
  `/api/audit?sessionId=${sessionId}`
).then(r => r.json());

GET /api/suggest

endpoint
GET /api/suggest?sessionId=... Returns: string[]

Returns suggested starter questions based on the real schema of the connected database or uploaded files. Generated once at connection time and cached.

Parameters

sessionId · string
The session to suggest questions for.

Returns

Array of suggested natural-language questions.

Example

const suggestions = await fetch(
  `/api/suggest?sessionId=${sessionId}`
).then(r => r.json());
SSE events

The /api/chat event stream

The /api/chat endpoint returns a text/event-stream. Each event has a type and a typed payload. Parse them in order to render the agent turn live in your own UI.

src/lib/types.ts (excerpt)
// SSE event types from /api/chat
type StreamEvent =
  | { type: "step"; agent: "router" | "sql" | "eda" | "viz" | "ml" | "synthesis" }
  | { type: "sql"; sql: string; frameId?: string; rowCount?: number; ms?: number }
  | { type: "canvas"; object: CanvasObject }  // table | chart | eda | model | pending-write | error
  | { type: "token"; value: string }  // streamed synthesis reply
  | { type: "reply_done" }
  | { type: "done" }
  | { type: "error"; message: string };

// CanvasObject union — typed artifacts rendered in the canvas pane
type CanvasObject =
  | { kind: "table"; frameId: string; columns: Column[]; rows: Row[] }
  | { kind: "chart"; spec: VegaLiteSpec }  // declarative Vega-Lite JSON
  | { kind: "sql"; sql: string; rowCount?: number; ms?: number }
  | { kind: "eda"; profile: StatisticalProfile }
  | { kind: "model"; metrics: ModelMetrics; predictions: Row[] }
  | { kind: "pending-write"; pendingId: string; sql: string; impact: string }
  | { kind: "error"; message: string };
example — parsing the stream
// Read the SSE stream from /api/chat
const res = await fetch("/api/chat", {
  method: "POST",
  body: JSON.stringify({ sessionId, message: "Chart top 8 products by sales" }),
});

const reader = res.body!.getReader();
const decoder = new TextDecoder();
let buffer = "";

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });

  // Parse SSE events: "data: {...}\n\n"
  const events = buffer.split("\n\n");
  buffer = events.pop()!;  // keep the last partial chunk

  for (const event of events) {
    const data = event.replace(/^data: /, "").trim();
    if (!data) continue;
    const evt: StreamEvent = JSON.parse(data);

    switch (evt.type) {
      case "step":    console.log(`${evt.agent}`); break;
      case "sql":     console.log("SQL:", evt.sql); break;
      case "canvas":  renderCanvas(evt.object); break;
      case "token":   appendToReply(evt.value); break;
      case "done":    console.log("✓ turn complete"); break;
    }
  }
}

Build something with the API

The HTTP API is the same one the live app uses. Wire it into a Slack bot, a cron, or your own agent — the contract is stable.