Skip to content

Commit cb5cb32

Browse files
committed
update docs
1 parent a608dd2 commit cb5cb32

16 files changed

Lines changed: 3143 additions & 0 deletions

docs/api-reference.md

Lines changed: 386 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,386 @@
1+
# REST API Reference
2+
3+
The tabletalk web server (`tabletalk serve`) exposes a REST API consumed by the web UI. All endpoints are also available for programmatic use.
4+
5+
**Base URL:** `http://localhost:5000` (default)
6+
7+
---
8+
9+
## Health
10+
11+
### `GET /health`
12+
13+
Check server readiness.
14+
15+
**Response 200 — ready:**
16+
```json
17+
{
18+
"status": "ok"
19+
}
20+
```
21+
22+
**Response 503 — degraded:**
23+
```json
24+
{
25+
"status": "degraded",
26+
"issues": ["No manifests found — run tabletalk apply"]
27+
}
28+
```
29+
30+
---
31+
32+
## Configuration
33+
34+
### `GET /config`
35+
36+
Return the active LLM provider and model name.
37+
38+
**Response:**
39+
```json
40+
{
41+
"provider": "ollama",
42+
"model": "qwen2.5-coder:7b"
43+
}
44+
```
45+
46+
---
47+
48+
## Manifests
49+
50+
### `GET /manifests`
51+
52+
List all compiled manifests in the project's `manifest/` directory.
53+
54+
**Response:**
55+
```json
56+
{
57+
"manifests": ["customers.txt", "inventory.txt", "marketing.txt", "sales.txt"]
58+
}
59+
```
60+
61+
### `POST /select_manifest`
62+
63+
Load a manifest and reset the conversation context.
64+
65+
**Request:**
66+
```json
67+
{
68+
"manifest": "sales.txt"
69+
}
70+
```
71+
72+
**Response:**
73+
```json
74+
{
75+
"manifest": "sales.txt",
76+
"details": "DATA_SOURCE: duckdb - ...\nCONTEXT: sales ...\n..."
77+
}
78+
```
79+
80+
The `details` field contains the full manifest text in compact schema notation. Parse it with the web UI's `parseManifest()` function or use it as-is as LLM context.
81+
82+
---
83+
84+
## Chat (streaming)
85+
86+
### `POST /chat/stream`
87+
88+
Main query endpoint. Streams SQL generation, execution results, and explanation as Server-Sent Events.
89+
90+
**Request:**
91+
```json
92+
{
93+
"question": "What is total revenue by month?",
94+
"manifest": "sales.txt",
95+
"auto_execute": true,
96+
"explain": true,
97+
"suggest": true
98+
}
99+
```
100+
101+
| Field | Type | Required | Default | Description |
102+
|-------|------|----------|---------|-------------|
103+
| `question` | string | Yes || Natural language question |
104+
| `manifest` | string | Yes || Manifest filename (e.g. `sales.txt`) |
105+
| `auto_execute` | boolean | No | `false` | Execute generated SQL |
106+
| `explain` | boolean | No | `false` | Stream plain-English explanation after execution |
107+
| `suggest` | boolean | No | `false` | Return 3 suggested follow-up questions |
108+
109+
**Response:** `text/event-stream`
110+
111+
Each line is a Server-Sent Event in the format:
112+
113+
```
114+
data: {"type": "...", ...}
115+
```
116+
117+
**Event types:**
118+
119+
| Type | Payload | Description |
120+
|------|---------|-------------|
121+
| `sql_chunk` | `{"type":"sql_chunk","content":"SELECT"}` | Incremental SQL token |
122+
| `sql_done` | `{"type":"sql_done","sql":"SELECT ..."}` | Full generated SQL |
123+
| `results` | `{"type":"results","columns":["month","revenue"],"rows":[["2024-01",...]],"count":12}` | Query results |
124+
| `execute_error` | `{"type":"execute_error","error":"...","sql":"..."}` | SQL execution error |
125+
| `explain_chunk` | `{"type":"explain_chunk","content":"Revenue grew..."}` | Incremental explanation token |
126+
| `explain_done` | `{"type":"explain_done"}` | Explanation complete |
127+
| `suggestions` | `{"type":"suggestions","questions":["...", "...", "..."]}` | Follow-up suggestions |
128+
| `error` | `{"type":"error","error":"Select a manifest first"}` | Non-fatal error |
129+
| `done` | `{"type":"done"}` | Stream complete |
130+
131+
**Example stream:**
132+
133+
```
134+
data: {"type": "sql_chunk", "content": "SELECT"}
135+
data: {"type": "sql_chunk", "content": " DATE_TRUNC"}
136+
data: {"type": "sql_chunk", "content": "('month', created_at)"}
137+
...
138+
data: {"type": "sql_done", "sql": "SELECT DATE_TRUNC('month', created_at) AS month, SUM(total_amount) AS revenue FROM orders GROUP BY 1 ORDER BY 1"}
139+
data: {"type": "results", "columns": ["month", "revenue"], "rows": [["2024-01-01", "15234.50"], ...], "count": 6}
140+
data: {"type": "explain_chunk", "content": "Revenue peaked in March"}
141+
...
142+
data: {"type": "explain_done"}
143+
data: {"type": "suggestions", "questions": ["Break that down by product", "Show month-over-month growth", "Which customers drove January revenue?"]}
144+
data: {"type": "done"}
145+
```
146+
147+
### `POST /fix/stream`
148+
149+
Fix a failing SQL query. Same SSE format as `/chat/stream` but generates corrected SQL given an error.
150+
151+
**Request:**
152+
```json
153+
{
154+
"sql": "SELECT * FROM order WHERE id = 1",
155+
"error": "relation \"order\" does not exist",
156+
"manifest": "sales.txt"
157+
}
158+
```
159+
160+
**Response:** `text/event-stream` — same event types as `/chat/stream`.
161+
162+
---
163+
164+
## Execution
165+
166+
### `POST /execute`
167+
168+
Execute SQL and return results (non-streaming).
169+
170+
**Request:**
171+
```json
172+
{
173+
"sql": "SELECT COUNT(*) AS total FROM orders",
174+
"manifest": "sales.txt"
175+
}
176+
```
177+
178+
**Response:**
179+
```json
180+
{
181+
"columns": ["total"],
182+
"rows": [[42]],
183+
"count": 1
184+
}
185+
```
186+
187+
**Error response:**
188+
```json
189+
{
190+
"error": "column \"total_ammount\" does not exist"
191+
}
192+
```
193+
194+
---
195+
196+
## Suggestions
197+
198+
### `POST /suggest`
199+
200+
Generate 3 follow-up question suggestions for a manifest.
201+
202+
**Request:**
203+
```json
204+
{
205+
"manifest": "sales.txt"
206+
}
207+
```
208+
209+
**Response:**
210+
```json
211+
{
212+
"questions": [
213+
"What is total revenue by month?",
214+
"Which products drive the most revenue?",
215+
"Show average order value by customer city"
216+
]
217+
}
218+
```
219+
220+
---
221+
222+
## Session
223+
224+
### `POST /reset`
225+
226+
Clear the current conversation context.
227+
228+
**Request:** empty body or `{}`
229+
230+
**Response:**
231+
```json
232+
{
233+
"status": "ok"
234+
}
235+
```
236+
237+
---
238+
239+
## History
240+
241+
### `GET /history`
242+
243+
Retrieve recent query history.
244+
245+
**Query parameters:**
246+
247+
| Parameter | Default | Description |
248+
|-----------|---------|-------------|
249+
| `limit` | `20` | Maximum number of entries to return |
250+
251+
**Response:**
252+
```json
253+
{
254+
"history": [
255+
{
256+
"question": "What is total revenue this month?",
257+
"sql": "SELECT SUM(total_amount) FROM orders WHERE ...",
258+
"manifest": "sales.txt",
259+
"timestamp": "2024-03-15T14:32:01.123456"
260+
}
261+
]
262+
}
263+
```
264+
265+
---
266+
267+
## Favorites
268+
269+
### `GET /favorites`
270+
271+
List all saved queries.
272+
273+
**Response:**
274+
```json
275+
{
276+
"favorites": [
277+
{
278+
"name": "Monthly revenue",
279+
"question": "What is total revenue by month?",
280+
"sql": "SELECT DATE_TRUNC('month', ...) ...",
281+
"manifest": "sales.txt",
282+
"created_at": "2024-03-15T14:32:01.123456"
283+
}
284+
]
285+
}
286+
```
287+
288+
### `POST /favorites`
289+
290+
Save a query as a favorite.
291+
292+
**Request:**
293+
```json
294+
{
295+
"name": "Monthly revenue",
296+
"question": "What is total revenue by month?",
297+
"sql": "SELECT DATE_TRUNC('month', created_at) ...",
298+
"manifest": "sales.txt"
299+
}
300+
```
301+
302+
**Response:**
303+
```json
304+
{
305+
"status": "ok"
306+
}
307+
```
308+
309+
### `DELETE /favorites/<name>`
310+
311+
Delete a saved favorite by name.
312+
313+
```
314+
DELETE /favorites/Monthly%20revenue
315+
```
316+
317+
**Response:**
318+
```json
319+
{
320+
"status": "ok"
321+
}
322+
```
323+
324+
---
325+
326+
## Consuming the streaming API
327+
328+
### Python (httpx)
329+
330+
```python
331+
import httpx
332+
import json
333+
334+
with httpx.stream("POST", "http://localhost:5000/chat/stream", json={
335+
"question": "What is total revenue by month?",
336+
"manifest": "sales.txt",
337+
"auto_execute": True,
338+
"explain": True,
339+
}) as r:
340+
for line in r.iter_lines():
341+
if line.startswith("data: "):
342+
event = json.loads(line[6:])
343+
if event["type"] == "sql_chunk":
344+
print(event["content"], end="", flush=True)
345+
elif event["type"] == "sql_done":
346+
print(f"\n\nSQL: {event['sql']}")
347+
elif event["type"] == "results":
348+
print(f"Results: {event['count']} rows")
349+
elif event["type"] == "done":
350+
break
351+
```
352+
353+
### curl
354+
355+
```bash
356+
curl -sN -X POST http://localhost:5000/chat/stream \
357+
-H "Content-Type: application/json" \
358+
-d '{"question":"total revenue by month","manifest":"sales.txt","auto_execute":true}'
359+
```
360+
361+
### JavaScript (browser)
362+
363+
```javascript
364+
const res = await fetch('/chat/stream', {
365+
method: 'POST',
366+
headers: { 'Content-Type': 'application/json' },
367+
body: JSON.stringify({ question: 'total revenue by month', manifest: 'sales.txt' }),
368+
});
369+
370+
const reader = res.body.getReader();
371+
const dec = new TextDecoder();
372+
let buf = '';
373+
374+
while (true) {
375+
const { done, value } = await reader.read();
376+
if (done) break;
377+
buf += dec.decode(value, { stream: true });
378+
for (const line of buf.split('\n')) {
379+
if (line.startsWith('data: ')) {
380+
const event = JSON.parse(line.slice(6));
381+
if (event.type === 'sql_chunk') process.stdout.write(event.content);
382+
}
383+
}
384+
buf = '';
385+
}
386+
```

0 commit comments

Comments
 (0)