HTTP API
Everything the frontend calls, all same-origin under /api/. Getting those requests to the Python service is the deployment's job: a reverse proxy, or a platform's routing rules; in development next.config.ts proxies them to BACKEND_ORIGIN.
Core
| Method | Path | What it does |
|---|---|---|
| GET | /api/health | Resolved provider and model, and where workspaces are kept. Start here when something is wrong. |
| POST | /api/upload | Upload a CSV or Excel file. |
| POST | /api/query | Ask a question about the data. The main one.· costs a model call |
| POST | /api/initialize-data | Load rows into a workspace without a file upload. |
Model-backed
| Method | Path | What it does |
|---|---|---|
| POST | /api/classify-command | Classify a spreadsheet command. Returns only a validated classification object.· costs a model call |
| POST | /api/orchestrate | Decompose a compound request into steps.· costs a model call |
| POST | /api/formula | Write a spreadsheet formula from a description. Returns the formula; nothing is applied to the sheet.· costs a model call |
The model picker
Writable only where control_allowed() says this is not a public deployment, which is what EDI_ALLOW_MODEL_SWITCHING=0 turns off.
| Method | Path | What it does |
|---|---|---|
| GET | /api/models | What this machine can reach: pulled Ollama models, a signed-in Claude CLI, any provider whose key is already set. Pass refresh=true to probe again rather than reuse the last answer. |
| POST | /api/models/select | Choose the provider and model. Saved next to the workspaces, and it outranks EDI_LLM_PROVIDER. |
| POST | /api/models/key | Store a provider key. It reaches the backend once, going in, and is never returned. |
| DELETE | /api/models/key/{provider} | Forget a stored key. |
| POST | /api/models/reset | Drop the saved choice and fall back to the environment. |
Workspaces and chats
| Method | Path | What it does |
|---|---|---|
| POST | /api/workspace | Create a workspace. |
| POST | /api/workspaces | Summarise the workspaces whose ids you send. A POST for a read, because the browser supplies the list. |
| GET | /api/workspace/{id} | Everything the sheet needs to restore itself. |
| PUT | /api/workspace/{id} | Save data, sheet state, chat messages, or the name. Only the fields sent are written. |
| DELETE | /api/workspace/{id} | Delete a workspace. Its chats go with it, by cascade. |
| GET | /api/workspace/{id}/chats | Chat threads, newest first. |
| POST | /api/workspace/{id}/chats | Start a thread. |
| GET | /api/chats/{id} | One thread. |
| PUT | /api/chats/{id} | Save messages or rename. |
Sheet operations
| Method | Path | What it does |
|---|---|---|
| POST | /api/workspace/{id}/analyze-insights | Profile the data. Mostly computed; asks the model only for the written summary.· costs a model call |
| POST | /api/workspace/{id}/smart-format | Detect column types and derive formats. Pure Python, no model call. |
| POST | /api/workspace/{id}/quick-data-entry | Generate rows to fill a sheet.· costs a model call |
| POST | /api/workspace/{id}/pivot | Cross-tabulate the sheet. Returns the whole table as a grid, header row first. Pure pandas, no model call. |
| POST | /api/cancel-operation | Cancel an in-flight operation. |
| POST | /api/reset-state | Clear server-side state. |
Health
GET /api/health is the one to reach for when something is not working. It reports what was actually resolved rather than what you meant to configure:
{
"status": "healthy",
"llm": "available",
"llm_config": {
"provider": "ollama",
"model": "qwen2.5-coder:7b",
"configured": true,
"detail": null
},
"store": { "backend": "sqlite", "location": ".edi-data" }
}llm_config.detail carries the reason when a model could not be built: a missing key, an unnamed model, an uninstalled provider package.
Errors
Refusals come back as JSON with a detail string written for the person who will read it. 413 means the parsed sheet is more data than the grid can hold, which EDI_MAX_DATA_MB sets and Self-hosting explains -- the upload itself is not capped; 503 means no model is configured; 502 means the model was reached but returned something unusable; 403 means the model picker was switched off with EDI_ALLOW_MODEL_SWITCHING=0.
/openapi.json, with interactive docs at /docs, and both work when you run the backend directly. Behind a proxy that forwards only /api/* they reach the frontend instead, where /docs now redirects to these pages, which is a more confusing answer than a 404. Forward them too if you want them public, and consider whether you do, since they describe every endpoint to anyone who asks.How it works covers what happens behind /api/query.