API Reference
FastGIS exposes a REST API consumed by the browser frontend and the mobile app. All endpoints require an X-API-Key header.
Authentication
X-API-Key: <your-token>
Tokens are generated in the user profile page or bootstrapped via the ADMIN_API_KEY environment variable.
:::tip Interactive docs
FastAPI auto-generates live API docs at /docs (Swagger UI) and /redoc (ReDoc) on any running server. These are the authoritative source for exact request/response shapes.
:::
Environments
All routes are under /grass/env.
| Method | Path | Description |
|---|---|---|
GET | /grass/env | List all environments for the authenticated user |
POST | /grass/env | Create a new GRASS environment |
GET | /grass/env/{env_id} | Get environment metadata |
DELETE | /grass/env/{env_id} | Delete an environment |
GET | /grass/env/{env_id}/layers | List raster and vector layers in the environment |
Create environment
POST /grass/env
Content-Type: application/json
{
"name": "my-project",
"epsg": 32632,
"db_driver": "pg"
}
db_driver is optional (sqlite or pg); defaults to GRASS_DB_DEFAULT_DRIVER.
GRASS module execution
Modules are executed via individual route modules per domain (raster, vector, imagery, …). The SSE task stream pattern is shared:
POST /grass/env/{env_id}/<module-path>
→ returns { "task_id": "..." }
GET /grass/tasks/{task_id}/stream
Accept: text/event-stream
→ streams stdout lines until done
See /docs on the running server for the full module schema per endpoint.
Profile tool
POST /grass/env/{env_id}/profile
Content-Type: application/json
{
"map": "elevation",
"coordinates": [[lon1, lat1], [lon2, lat2]],
"resolution": 100
}
Returns sampled profile values plus statistics.
Histogram tool
POST /grass/env/{env_id}/histogram
Content-Type: application/json
{
"map": "elevation",
"nsteps": 255,
"region": { "west": 10.0, "south": 59.0, "east": 11.0, "north": 60.0 }
}
Analysis artifacts
Save profile/histogram results as named artifacts.
| Method | Path | Description |
|---|---|---|
GET | /grass/artifacts | List all saved artifacts for the authenticated user |
POST | /grass/artifacts | Save a new artifact |
GET | /grass/artifacts/{artifact_id} | Load an artifact |
DELETE | /grass/artifacts/{artifact_id} | Delete an artifact |
Terrain
| Method | Path | Description |
|---|---|---|
POST | /grass/env/{env_id}/terrain/build | Build a quantized-mesh terrain dataset |
GET | /grass/terrain | List built terrain datasets |
DELETE | /grass/terrain/{name} | Delete a terrain dataset |
Build terrain
POST /grass/env/{env_id}/terrain/build
Content-Type: application/json
{
"map": "elevation",
"name": "my_terrain",
"max_depth": 14,
"overwrite": false
}
Streams SSE build progress:
{"stage": "export", "msg": "Exporting …", "done": false}
{"stage": "reproject", "msg": "Reprojecting …", "done": false}
{"stage": "terrain", "msg": "Generating …", "done": false}
{"stage": "done", "msg": "Ready", "done": true, "terrain_url": "/terrain/my_terrain"}
Vector editing
| Method | Path | Description |
|---|---|---|
POST | /grass/env/{env_id}/vedit | Commit new features |
DELETE | /grass/env/{env_id}/vedit/{map}/feature | Delete features by category |
PATCH | /grass/env/{env_id}/vedit/{map}/feature/move | Translate features |
PATCH | /grass/env/{env_id}/vedit/{map}/feature/replace | Replace feature geometry |
PATCH | /grass/env/{env_id}/vedit/{map}/feature/{cat}/attributes | Update feature attributes |
POST | /grass/env/{env_id}/vedit/{map}/attributes/repair | Repair missing attribute rows |
Commit new features
POST /grass/env/{env_id}/vedit
Content-Type: application/json
{
"map": "my_points",
"features": [
{ "type": "point", "coordinates": [[lon, lat]], "cat": 1, "attributes": {"name": "A"} },
{ "type": "line", "coordinates": [[x1,y1],[x2,y2]], "cat": 2 },
{ "type": "polygon", "coordinates": [[x1,y1],[x2,y2],[x3,y3]], "cat": 3 }
],
"overwrite": false
}
Coordinates must be in the map's native GRASS CRS (not WGS84). attributes per feature is optional; new columns are created automatically.
Update attributes
PATCH /grass/env/{env_id}/vedit/{map}/feature/{cat}/attributes
Content-Type: application/json
{ "attributes": { "name": "Station A", "depth": 42.5 } }
Routing (Valhalla)
All routing endpoints are under /grass/routing/.
| Method | Path | Description |
|---|---|---|
GET | /grass/routing/status | Valhalla health + tile info |
POST | /grass/routing/route | Turn-by-turn directions |
POST | /grass/routing/isochrone | Reachability contours |
POST | /grass/routing/matrix | Time/distance table |
POST | /grass/routing/circular | Loop route (start = end) |
GET | /grass/routing/speed-cameras | Speed cameras in bbox |
GET | /grass/routing/poi-categories | Supported POI category IDs |
GET | /grass/routing/nearby | POI within radius |
GET | /grass/routing/routes | List saved routes |
POST | /grass/routing/routes | Save a route |
GET | /grass/routing/routes/{id} | Load a route |
DELETE | /grass/routing/routes/{id} | Delete a route |
GET | /grass/routing/routes/{id}/export/gpx | Export GPX |
GET | /grass/routing/routes/{id}/export/geojson | Export GeoJSON |
POST | /grass/routing/location | Publish position to a group |
GET | /grass/routing/location/group/{group_id} | Get group member positions |
DELETE | /grass/routing/location/{group_id}/{user_id} | Leave group |
Route
POST /grass/routing/route
Content-Type: application/json
{
"locations": [{"lon": 10.7, "lat": 59.9}, {"lon": 14.2, "lat": 66.3}],
"costing": "motorcycle",
"units": "km",
"alternates": 0
}
Loop route
POST /grass/routing/circular
Content-Type: application/json
{
"lat": 59.9, "lon": 10.7,
"distance_km": 150,
"direction": "N",
"costing": "motorcycle",
"avg_speed_kmh": 50,
"num_waypoints": 6,
"waypoint_percentile": 0.70,
"through_waypoints": true,
"use_heading": true
}
Response includes trip, waypoints, isochrone, actual_distance_km, actual_duration_s.
Curvature (curvy-road discovery)
| Method | Path | Description |
|---|---|---|
GET | /grass/curvature/db-status | Check whether roads.db exists and its row count |
GET | /grass/curvature/road-classes | List OSM highway classes in the DB |
POST | /grass/curvature/analyze-precomputed | Query pre-ingested curvature DB by bbox/filters |
GET | /grass/curvature/nearby | Find curvy roads near a lat/lon (server-side, uses DB or live Overpass) |
Query nearby curvy roads
GET /grass/curvature/nearby?lat=59.9&lon=10.7&radius_km=50&preset=twisty&limit=50
Parameters:
| Field | Type | Default | Description |
|---|---|---|---|
lat, lon | float | — | Search centre |
radius_km | float | 50 | Search radius |
preset | string | twisty | Scoring preset (twisty, scenic, balanced) |
limit | int | 50 | Maximum roads returned |
min_curvature | float | — | Override minimum curvature score |
min_sinuosity | float | — | Override minimum sinuosity |
road_classes | string | — | Comma-separated OSM highway classes |
exclude_loops | bool | true | Exclude loop roads |
Returns GeoJSON FeatureCollection with LineString road segments and Point name markers.
Offline data tiles
1°×1° SQLite tile files for offline POI search and curvature — downloaded by the mobile app.
| Method | Path | Description |
|---|---|---|
GET | /grass/tiles/catalog | List all available tiles with per-tile availability and file size |
GET | /grass/tiles/poi/{tile_id} | Download a POI SQLite tile (e.g. tile_id=47_8) |
GET | /grass/tiles/curvature/{tile_id} | Download a curvature SQLite tile |
Tile catalog response
{
"tiles": [
{
"id": "47_8",
"label": "47°N 8°E",
"bbox": { "west": 8, "south": 47, "east": 9, "north": 48 },
"poi": { "available": true, "size_bytes": 2400000 },
"curvature": { "available": true, "size_bytes": 890000 }
},
{
"id": "48_9",
"label": "48°N 9°E",
"bbox": { "west": 9, "south": 48, "east": 10, "north": 49 },
"poi": { "available": true, "size_bytes": 1100000 },
"curvature": { "available": false, "size_bytes": null }
}
]
}
Tile IDs encode the SW corner: {lat}_{lon}, e.g. 47_8 = SW corner 47°N 8°E.
Data packs (server admin)
Manage region configuration and trigger background data rebuild jobs.
| Method | Path | Description |
|---|---|---|
GET | /grass/data-packs/status | Region list with per-region pack availability |
POST | /grass/data-packs/regions/enable | Enable / disable regions in regions.yml |
POST | /grass/data-packs/rebuild | Start a background rebuild job |
GET | /grass/data-packs/rebuild/{job_id} | Poll job status and log tail |
Enable regions
POST /grass/data-packs/regions/enable
Content-Type: application/json
{
"enable": ["norway", "sweden", "austria"],
"disable": ["denmark"]
}
Trigger rebuild
POST /grass/data-packs/rebuild
Content-Type: application/json
{
"regions": ["norway", "sweden", "austria", "italy", "spain", "poland", "czech-republic"],
"pack_types": ["curvature"],
"force": true,
"curvature_threshold": "both"
}
| Field | Type | Default | Description |
|---|---|---|---|
regions | string[] | — | Region names to rebuild. Empty list [] uses all enabled regions (for tiles step). |
pack_types | string[] | ["poi","curvature"] | Steps to run: curvature, poi, tiles. Run curvature before tiles. |
force | bool | false | Re-build even if output already exists. |
curvature_threshold | "300" | "1000" | "both" | "both" | Which KMZ score-band(s) to download and ingest. "both" produces the most complete DB and is the recommended default. |
pack_types order: curvature first, then tiles (tiles depend on the curvature DB being ready).
Poll job status
GET /grass/data-packs/rebuild/{job_id}
{
"job_id": "a1b2c3d4",
"status": "done",
"pack_types": ["curvature"],
"started_at": 1715000000.0,
"finished_at": 1715003600.0,
"errors": [],
"logs": ["[09:00:01] $ python3 scripts/fetch_region_data.py …", "…"]
}
status values: running, done, error.
Field sessions (mobile)
Server-side registry for mobile field sessions, backed by Redis.
| Method | Path | Description |
|---|---|---|
POST | /grass/env/{env_id}/field-sessions | Register a field session (upsert) |
GET | /grass/env/{env_id}/field-sessions | List registered field sessions for an environment |
DELETE | /grass/env/{env_id}/field-sessions/{track_layer} | Delete a field session registration |
Published projects
| Method | Path | Description |
|---|---|---|
GET | /grass/published | List published project snapshots for the authenticated user |
GET | /grass/published/{project_id} | Load a published project |
DELETE | /grass/published/{project_id} | Delete a published project |
Location sharing
POST /grass/routing/location
Content-Type: application/json
{
"group_id": "trip-2025",
"user_id": "alice",
"lat": 59.9,
"lon": 10.7,
"heading": 45.0,
"speed": 13.8
}
Positions expire after 5 minutes of inactivity.