# API Endpoints Reference Complete reference for GeoQuery's FastAPI endpoints. --- ## Base URL **Development**: `http://localhost:8000` **Production**: Configure via environment --- ## Endpoints ### 1. Chat Query (SSE Streaming) Main endpoint for natural language queries with streaming responses. ```http POST /api/chat Content-Type: application/json ``` **Request Body**: ```json { "message": "Show me hospitals in Panama City", "history": [ {"role": "user", "content": "previous query"}, {"role": "assistant", "content": "previous response"} ] } ``` **Response**: Server-Sent Events (SSE) stream **Event Types**: 1. **`status`** - Processing status updates ```json {"status": "🔍 Identifying relevant tables..."} ``` 2. **`intent`** - Detected query intent ```json {"intent": "MAP_REQUEST"} ``` 3. **`chunk`** - Streaming content (text or thought) ```json {"type": "thought", "content": "Planning spatial query..."} {"type": "text", "content": "I found 45 hospitals..."} ``` 4. **`result`** - Final result with data ```json { "response": "Full explanation text", "sql_query": "SELECT name, geom FROM ...", "geojson": {...}, "chart_data": {...}, "raw_data": [...], "data_citations": [...] } ``` **curl Example**: ```bash curl -X POST http://localhost:8000/api/chat \ -H "Content-Type: application/json" \ -d '{"message": "Show me provinces", "history": []}' \ --no-buffer ``` **JavaScript Example**: ```javascript const response = await fetch('http://localhost:8000/api/chat', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ message: "Show me hospitals", history: [] }) }); const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const {value, done} = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const data = JSON.parse(line.slice(6)); console.log(data); } } } ``` --- ### 2. Get Data Catalog Returns metadata for all available datasets. ```http GET /api/catalog ``` **Response**: ```json { "panama_healthsites_geojson": { "description": "Healthcare facilities including hospitals...", "categories": ["health", "infrastructure"], "tags": ["hospitals", "clinics"], "row_count": 986, "has_geometry": true }, "pan_admin1": { "description": "Panama provinces...", "categories": ["administrative"], "row_count": 10, "has_geometry": true } } ``` **curl Example**: ```bash curl http://localhost:8000/api/catalog ``` --- ### 3. Get Database Schema Returns current database schema with loaded tables. ```http GET /api/schema ``` **Response**: ```json { "tables": [ { "name": "panama_healthsites_geojson", "columns": ["osm_id", "name", "amenity", "geom"], "row_count": 986, "geometry_type": "Point" }, { "name": "pan_admin1", "columns": ["adm1_name", "adm1_pcode", "area_sqkm", "geom"], "row_count": 10, "geometry_type": "Polygon" } ], "loaded_count": 2, "total_datasets": 108 } ``` **curl Example**: ```bash curl http://localhost:8000/api/schema ``` --- ## Response Formats ### GeoJSON Structure Map data returned in standard GeoJSON format with custom properties: ```json { "type": "FeatureCollection", "properties": { "layer_id": "abc123", "layer_name": "Hospitals in David", "style": { "color": "#E63946", "fillColor": "#E63946", "opacity": 0.8, "fillOpacity": 0.4 }, "pointMarker": { "icon": "🏥", "style": "icon", "color": "#E63946", "size": 32 }, "choropleth": { "enabled": false } }, "features": [ { "type": "Feature", "geometry": { "type": "Point", "coordinates": [-79.5, 8.98] }, "properties": { "name": "Hospital Santo Tomás", "amenity": "hospital" } } ] } ``` **Properties Explanation**: - `layer_id`: Unique identifier - `layer_name`: Display name - `style`: Default polygon/line styling - `pointMarker`: Point rendering configuration - `style: "icon"` → Use emoji icon - `style: "circle"` → Use simple circle - `choropleth`: Choropleth configuration (if enabled) ### Chart Data Structure ```json { "type": "bar", "title": "Districts by Province", "data": { "labels": ["Panamá", "Chiriquí", "Veraguas"], "datasets": [{ "label": "District Count", "data": [8, 13, 12], "backgroundColor": "#6366f1" }] } } ``` **Chart Types**: - `bar` - Bar chart - `pie` - Pie chart - `line` - Line chart ### Data Citations ```json [ "Administrative boundary data from HDX/INEC, 2021", "Healthcare facilities from OpenStreetMap via Healthsites.io" ] ``` --- ## Error Responses ### Standard Error Format ```json { "detail": "Error message", "error_type": "DataNotFound", "timestamp": "2026-01-10T12:00:00Z" } ``` **HTTP Status Codes**: - `400 Bad Request` - Invalid query format - `404 Not Found` - Endpoint not found - `500 Internal Server Error` - Server error - `503 Service Unavailable` - LLM API unavailable ### Data Unavailable Response When requested data doesn't exist: ```json { "response": "I couldn't find data for crime statistics in the current database...", "sql_query": "-- ERROR: DATA_UNAVAILABLE\\n-- Requested: crime statistics\\n-- Available: admin boundaries, hospitals", "geojson": null, "data_citations": [] } ``` --- ## CORS Configuration Current CORS settings (in `backend/main.py`): ```python app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:3000"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) ``` **For Production**: Update `allow_origins` to your frontend domain. --- ## Rate Limiting **Current**: No rate limiting implemented **Recommended** (future): ```python from slowapi import Limiter from slowapi.util import get_remote_address limiter = Limiter(key_func=get_remote_address) @app.post("/api/chat") @limiter.limit("10/minute") async def chat(request: Request): ... ``` --- ## Authentication **Current**: No authentication required **For Production**: Add API key or JWT authentication: ```python from fastapi import Security, HTTPException from fastapi.security import APIKeyHeader API_KEY = os.getenv("API_KEY") api_key_header = APIKeyHeader(name="X-API-Key") def verify_api_key(api_key: str = Security(api_key_header)): if api_key != API_KEY: raise HTTPException(status_code=403, detail="Invalid API key") return api_key ``` --- ## WebSocket Support **Current**: Not implemented (using SSE instead) **Rationale**: SSE sufficient for one-way server → client streaming. WebSockets add complexity without benefit for this use case. --- ## API Versioning **Current**: No versioning **Future Consideration**: ``` /api/v1/chat /api/v2/chat ``` --- ## OpenAPI Documentation FastAPI auto-generates interactive API docs: - **Swagger UI**: http://localhost:8000/docs - **ReDoc**: http://localhost:8000/redoc - **OpenAPI JSON**: http://localhost:8000/openapi.json --- ## Client Libraries ### Python Client Example ```python import requests import json def query_geoquery(message, history=[]): response = requests.post( "http://localhost:8000/api/chat", json={"message": message, "history": history}, stream=True ) for line in response.iter_lines(): if line.startswith(b'data: '): data = json.loads(line[6:]) if data.get("event") == "chunk": print(data["data"]["content"], end="") elif data.get("event") == "result": return data["data"] result = query_geoquery("Show me hospitals") print(result["sql_query"]) ``` ### JavaScript/TypeScript Client See `frontend/src/lib/api.ts` for full implementation. --- ## Performance Considerations ### Response Times Typical query pipeline: 1. Intent detection: ~500ms 2. Semantic search: <10ms 3. SQL generation: ~1s 4. Query execution: 100ms - 5s (depends on data size) 5. Explanation: ~1s (streaming) **Total**: 2-8 seconds for most queries ### Optimization Tips 1. **Pre-load Common Tables**: Load frequently used datasets at startup 2. **Query Limits**: Add `LIMIT` clauses for large result sets 3. **Spatial Indexes**: DuckDB auto-indexes geometry columns 4. **Caching**: Consider Redis for repeated queries --- ## Next Steps - **Core Services**: [CORE_SERVICES.md](CORE_SERVICES.md) - **Data Flow**: [../DATA_FLOW.md](../DATA_FLOW.md) - **Frontend Components**: [../frontend/COMPONENTS.md](../frontend/COMPONENTS.md)