GeoQuery / docs /backend /API_ENDPOINTS.md
GerardCB's picture
Deploy to Spaces (Final Clean)
4851501
# 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)