# API Reference This document provides a complete reference for all Visual Notebook API endpoints, including request schemas, response schemas, status codes, and usage examples. ## Base URL - Development: `http://localhost:8000` - Production: Same origin (backend serves the frontend) - All API endpoints are prefixed with `/api` - The health endpoint is at `/health` (no prefix) ## Authentication The API uses cookie-based session management. The `session_id` cookie is set automatically after the first CSV upload and sent with all subsequent requests. No explicit authentication is required. ## Common Response Patterns ### Error Responses All error responses follow the RFC 7807 pattern with a `detail` field: ```json { "detail": "Error description" } ``` ### Session Cookie The first endpoint that creates a session (`POST /api/upload/csv`) sets an `httponly` cookie: ``` Set-Cookie: session_id=abc123def456; HttpOnly; Max-Age=604800; Path=/; SameSite=lax ``` --- ## Endpoints ### POST /api/upload/csv Upload a CSV file, register it in DuckDB, and create a new session. **Request**: `multipart/form-data` | Field | Type | Required | Description | |-------|------|----------|-------------| | `file` | File | Yes | CSV file to upload (must have `.csv` extension) | **Response**: `200 OK` ```json { "dataset_id": "a3b2c1d4", "table_name": "ds_a3b2c1d4", "row_count": 15000, "columns": [ { "name": "order_id", "dtype": "INTEGER", "column_type": "numeric", "null_count": 0, "unique_count": 15000, "min": 1, "max": 15000, "mean": 7500.5, "std": 4330.1, "q25": 3750.75, "q50": 7500.5, "q75": 11250.25, "count": 15000 }, { "name": "status", "dtype": "VARCHAR", "column_type": "categorical", "null_count": 5, "unique_count": 3, "min": null, "max": null, "mean": null, "std": null, "q25": null, "q50": null, "q75": null, "count": 14995 } ], "session_id": "a1b2c3d4e5f6" } ``` **Error Responses**: | Status | Condition | |--------|-----------| | 400 | File does not have `.csv` extension | | 413 | File exceeds `MAX_UPLOAD_SIZE` | --- ### GET /api/schema Retrieve the schema and statistics for the currently loaded dataset. **Request**: No parameters. **Response**: `200 OK` ```json { "table_name": "ds_a3b2c1d4", "row_count": 15000, "columns": [ { "name": "order_id", "dtype": "INTEGER", "column_type": "numeric", "null_count": 0, "unique_count": 15000, "min": 1, "max": 15000, "mean": 7500.5, "std": 4330.1, "q25": 3750.75, "q50": 7500.5, "q75": 11250.25, "count": 15000 } ] } ``` **Error Responses**: | Status | Condition | |--------|-----------| | 400 | No dataset loaded in the current session | --- ### GET /api/rows Retrieve paginated rows from the current dataset. **Query Parameters**: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `offset` | integer | 0 | Row offset (0-based) | | `limit` | integer | 50 | Maximum number of rows to return | **Response**: `200 OK` ```json { "rows": [ {"order_id": 1, "status": "shipped", "amount": 99.99}, {"order_id": 2, "status": "pending", "amount": 45.50} ], "columns": ["order_id", "status", "amount"], "offset": 0, "limit": 50, "total": 15000 } ``` **Error Responses**: | Status | Condition | |--------|-----------| | 400 | No dataset loaded | --- ### GET /api/ops List all available data operations. **Request**: No parameters. **Response**: `200 OK` ```json { "operations": [ { "key": "filter", "label": "Filter", "description": "Filter rows by condition", "param_schema": { "type": "object", "required": ["column", "operator"], "properties": { "column": {"type": "string"}, "operator": {"type": "string", "enum": ["eq", "neq", "gt", "lt", "gte", "lte", "contains", "is_null", "is_not_null"]}, "value": {"type": ["string", "number"]} } } }, { "key": "sort", "label": "Sort", "description": "Sort by column", "param_schema": { "type": "object", "required": ["column"], "properties": { "column": {"type": "string"}, "descending": {"type": "boolean", "default": false} } } }, { "key": "select", "label": "Select Columns", "description": "Keep only selected columns", "param_schema": { "type": "object", "required": ["columns"], "properties": { "columns": {"type": "array", "items": {"type": "string"}} } } }, { "key": "rename", "label": "Rename Columns", "description": "Rename columns using a mapping", "param_schema": { "type": "object", "required": ["mapping"], "properties": { "mapping": {"type": "object", "additionalProperties": {"type": "string"}} } } }, { "key": "groupby", "label": "Group By", "description": "Aggregate data by groups", "param_schema": { "type": "object", "required": ["group_by", "aggregations"], "properties": { "group_by": {"type": "array", "items": {"type": "string"}}, "aggregations": {"type": "object", "additionalProperties": {"type": "array", "items": {"type": "string"}}} } } }, { "key": "fill_nulls", "label": "Fill Nulls", "description": "Fill null values using a strategy", "param_schema": { "type": "object", "properties": { "strategy": {"type": "string", "enum": ["forward", "backward", "mean", "zero", "constant"], "default": "forward"}, "columns": {"type": "array", "items": {"type": "string"}}, "value": {"type": ["string", "number"]} } } }, { "key": "clip", "label": "Clip Values", "description": "Clip values to a range", "param_schema": { "type": "object", "required": ["column"], "properties": { "column": {"type": "string"}, "lower": {"type": "number"}, "upper": {"type": "number"} } } } ] } ``` --- ### POST /api/ops/{op_key} Apply a data operation to the current dataset. **Path Parameters**: | Parameter | Type | Description | |-----------|------|-------------| | `op_key` | string | Operation key (filter, sort, select, rename, groupby, fill_nulls, clip) | **Request Body**: ```json { "op_key": "filter", "params": { "column": "amount", "operator": "gt", "value": 100 } } ``` **Response**: `200 OK` ```json { "state_id": "e5f6a7b8", "columns_schema": [ { "name": "order_id", "dtype": "INTEGER", "column_type": "numeric", "null_count": 0, "unique_count": 5200, "min": 1, "max": 15000, "mean": 7800.3, "std": 4200.5, "q25": 3900.0, "q50": 7800.0, "q75": 11700.0, "count": 5200 } ], "row_count": 5200, "sample_rows": [ {"order_id": 5, "status": "shipped", "amount": 150.00}, {"order_id": 12, "status": "pending", "amount": 220.50} ] } ``` **Error Responses**: | Status | Condition | |--------|-----------| | 400 | No dataset loaded | | 404 | Operation key not found | | 422 | Parameter validation failed | | 500 | Operation execution failed | **Operation Parameter Examples**: Filter: ```json {"column": "status", "operator": "eq", "value": "shipped"} {"column": "amount", "operator": "gte", "value": 50} {"column": "notes", "operator": "contains", "value": "urgent"} {"column": "email", "operator": "is_null"} ``` Sort: ```json {"column": "amount", "descending": true} ``` Select: ```json {"columns": ["order_id", "amount", "status"]} ``` Rename: ```json {"mapping": {"order_id": "id", "amount": "total"}} ``` GroupBy: ```json {"group_by": ["status"], "aggregations": {"amount": ["mean", "sum", "count"]}} ``` Fill Nulls: ```json {"strategy": "forward"} {"strategy": "zero"} {"strategy": "constant", "value": "N/A", "columns": ["status"]} ``` Clip: ```json {"column": "amount", "lower": 0, "upper": 1000} ``` --- ### POST /api/history/undo Undo the last operation, reverting to the previous state. **Request**: No body. **Response**: `200 OK` ```json { "state_id": "a3b2c1d4", "operation": "upload", "columns_schema": [ { "name": "order_id", "dtype": "INTEGER", "column_type": "numeric", "null_count": 0, "unique_count": 15000, "min": 1, "max": 15000, "mean": 7500.5, "std": 4330.1, "q25": 3750.75, "q50": 7500.5, "q75": 11250.25, "count": 15000 } ], "row_count": 15000 } ``` **Error Responses**: | Status | Condition | |--------|-----------| | 400 | Cannot undo (at the beginning of history) | --- ### POST /api/history/redo Redo the next operation, advancing to the subsequent state. **Request**: No body. **Response**: `200 OK` Same schema as undo response. **Error Responses**: | Status | Condition | |--------|-----------| | 400 | Cannot redo (at the end of history) | --- ### POST /api/history/branch Branch from a specific historical state, resetting the current pointer to that state. **Request Body**: ```json { "state_id": "a3b2c1d4" } ``` Note: The `state_id` is passed as a query parameter in the current implementation. **Response**: `200 OK` ```json { "state_id": "a3b2c1d4", "operation": "upload" } ``` **Error Responses**: | Status | Condition | |--------|-----------| | 404 | State ID not found in history | --- ### GET /api/history Retrieve the full history tree for the current session. **Request**: No parameters. **Response**: `200 OK` ```json { "nodes": [ { "id": "a3b2c1d4", "operation": "upload", "params": {"filename": "orders.csv"}, "parent_id": null, "children": ["e5f6a7b8"], "is_current": false }, { "id": "e5f6a7b8", "operation": "filter", "params": {"column": "amount", "operator": "gt", "value": 100}, "parent_id": "a3b2c1d4", "children": [], "is_current": true } ], "current_id": "e5f6a7b8", "order": ["a3b2c1d4", "e5f6a7b8"] } ``` --- ### POST /api/viz/chart Compute a chart specification for the current dataset. **Request Body**: ```json { "chart_type": "histogram", "params": { "column": "amount", "bins": 20 } } ``` **Chart Type Parameters**: | Chart Type | Required Params | Optional Params | |-------------|----------------|-----------------| | `histogram` | `column` | `bins` (default: 20) | | `scatter` | `x_col`, `y_col` | `color_col` | | `bar_topn` | `category_col` | `value_col`, `n` (default: 15), `sort_desc` (default: true) | | `timeline` | `date_col`, `value_col` | `agg` (default: "sum") | | `heatmap` | `x_col`, `y_col`, `value_col` | `agg` (default: "mean") | | `map` | `lat_col`, `lon_col` | `label_col` | **Response**: `200 OK` For ECharts-based charts (histogram, scatter, bar_topn, timeline, heatmap): ```json { "chart_type": "histogram", "spec": { "animation": true, "animationDuration": 600, "grid": {"left": 60, "right": 30, "top": 60, "bottom": 50, "containLabel": true}, "title": {"text": "Distribution of amount", "left": "center"}, "tooltip": {"trigger": "item", "backgroundColor": "rgba(255,255,255,0.95)"}, "xAxis": {"type": "category", "data": ["0-50", "50-100", "100-150"]}, "yAxis": {"type": "value", "name": "Count"}, "series": [{"type": "bar", "data": [3200, 5100, 4500]}] } } ``` For map charts: ```json { "chart_type": "map", "spec": { "type": "map", "points": [{"lat": 40.7, "lon": -74.0, "label": "NYC"}], "bounds": {"minLat": 25.0, "maxLat": 48.0, "minLon": -125.0, "maxLon": -67.0}, "center": {"lat": 36.5, "lon": -96.0}, "pointCount": 1500 } } ``` **Error Responses**: | Status | Condition | |--------|-----------| | 400 | No dataset loaded, or chart requires DuckDB backend | | 422 | Missing required parameter | | 500 | Chart computation failed | --- ### GET /api/export/csv Export the current dataset as a CSV file download. **Request**: No parameters. **Response**: `200 OK` with `Content-Type: text/csv` The response is a file download with the table name as the filename. **Error Responses**: | Status | Condition | |--------|-----------| | 400 | No dataset loaded | --- ### POST /api/checkpoint Save a checkpoint of the current session state, including the history tree and data. **Request**: No body. **Response**: `200 OK` ```json { "path": "checkpoints/a1b2c3d4e5f6/checkpoint_20240101_120000.json", "timestamp": "20240101_120000", "state_id": "e5f6a7b8" } ``` --- ### GET /api/checkpoints List all saved checkpoints for the current session. **Request**: No parameters. **Response**: `200 OK` ```json [ { "path": "checkpoints/a1b2c3d4e5f6/checkpoint_20240101_120000.json", "timestamp": "20240101_120000", "session_id": "a1b2c3d4e5f6" }, { "path": "checkpoints/a1b2c3d4e5f6/checkpoint_20240101_090000.json", "timestamp": "20240101_090000", "session_id": "a1b2c3d4e5f6" } ] ``` --- ### GET /health Health check endpoint for infrastructure monitoring. **Request**: No parameters. **Response**: `200 OK` ```json { "status": "ok", "version": "0.1.0" } ``` --- ## Status Code Summary | Code | Meaning | When Used | |------|---------|-----------| | 200 | Success | All successful responses | | 400 | Bad Request | No dataset loaded, invalid state transition | | 404 | Not Found | Unknown operation key or history state | | 413 | Payload Too Large | File exceeds MAX_UPLOAD_SIZE | | 422 | Unprocessable Entity | Parameter validation failure | | 500 | Internal Server Error | Operation or chart computation failure |