Files
geointel/docs/API_CONTRACTS.md
T
Codex 6ea3586a3e
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled
Initial GeoIntel V1 foundation
2026-06-16 23:36:32 +02:00

973 lines
26 KiB
Markdown

# API Contracts v1
This document freezes the first API shape. Codex may add implementation details but must not rename these routes without updating this file and the frontend API client.
## API principles
- Base path: `/api/v1`.
- JSON by default.
- GeoJSON accepted for geometries where possible.
- Long processing tasks return a job or analysis run record instead of blocking.
- Error responses use the shared `ApiError` schema.
## Shared schemas
### ApiError
```json
{
"error": "string",
"message": "human readable message",
"details": {},
"request_id": "optional string"
}
```
### GeoJsonGeometry
Any valid GeoJSON geometry object. V1 primarily expects `Polygon` and `MultiPolygon` for areas.
### BoundingBox
```json
{
"min_x": 0.0,
"min_y": 0.0,
"max_x": 0.0,
"max_y": 0.0,
"crs": "EPSG:4326"
}
```
## Health
### GET `/health`
Returns service status.
```json
{
"status": "ok",
"service": "geointel-backend",
"version": "0.1.0"
}
```
### GET `/api/v1/system/capabilities`
Returns enabled feature flags and tool availability.
```json
{
"postgis": true,
"rasterio": true,
"geopandas": true,
"yolo": false,
"sam": false,
"grb": "planned",
"sentinel": "planned",
"providers": [
{
"provider_name": "grb",
"display_name": "GRB",
"authority_level": "authoritative",
"supported_layers": ["buildings", "roads", "parcels"],
"supported_geometry_types": ["Polygon", "MultiPolygon", "LineString", "MultiLineString"],
"supported_query_modes": ["area"],
"fetch_signature": "POST /api/v1/external/grb/fetch",
"configured": false,
"status": "not_configured",
"limitation_message": "GRB live WFS/download integration is not configured in Sprint 7B.",
"attribution": "Digitaal Vlaanderen - Basiskaart Vlaanderen (GRB)",
"license_note": "Use must follow Digitaal Vlaanderen open data and attribution terms.",
"not_configured_reason": "Provider integration is not configured yet"
}
]
}
```
## Projects
### GET `/api/v1/projects`
Returns all projects.
### POST `/api/v1/projects`
Request:
```json
{
"name": "Geel building detection demo",
"description": "Detect buildings and validate against GRB",
"region": "Kempen"
}
```
Response: `ProjectRead`.
### GET `/api/v1/projects/{project_id}`
Returns one project with summary counts.
### PATCH `/api/v1/projects/{project_id}`
Updates name/description/region.
### DELETE `/api/v1/projects/{project_id}`
Soft-delete in V1 preferred. Hard-delete only if storage cleanup is also implemented.
## Areas
### GET `/api/v1/projects/{project_id}/areas`
Returns areas for a project.
### POST `/api/v1/projects/{project_id}/areas`
Request:
```json
{
"name": "Geel Centrum AOI",
"geometry": {"type": "Polygon", "coordinates": []},
"crs": "EPSG:4326"
}
```
Backend responsibilities:
- Validate geometry.
- Repair trivial polygon issues if safe.
- Store geometry in PostGIS.
- Calculate area in square meters using projected CRS.
- Store bbox.
## Datasets
### POST `/api/v1/projects/{project_id}/datasets/upload`
Multipart upload.
Fields:
- `file`: dataset file.
- `dataset_type`: `vector`, `geojson` (legacy), `raster`.
- `source`: free text, e.g. `user_upload`, `grb`, `osm`.
- `dataset_role`: `source`, `derived`, or `reference` (default `source`).
- `source_name`: optional source identity, e.g. `manual`, `grb`, `osm`; reference uploads default to `manual` when omitted.
- `reference_layer_name`: optional reference layer label, e.g. `buildings`; only retained for reference datasets.
- `area_id`: optional.
Response: `DatasetRead` with extracted metadata if supported.
Vector uploads remain stored as original files and are also persisted into `vector_features` as queryable PostGIS state.
### GET `/api/v1/projects/{project_id}/datasets`
List datasets.
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}`
Return metadata.
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/metadata/refresh`
Re-extract metadata.
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/inspect`
Return a wrapped vector inspection payload with metadata, storage summary and feature summary.
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/summary`
Return vector summary data only.
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/metadata`
Return raster metadata profile for supported raster uploads.
If raster processing is unavailable:
```text
code: RASTER_PROCESSING_UNAVAILABLE
message: Raster processing unavailable. Install rasterio and GDAL-compatible drivers to enable raster metadata extraction.
```
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/inspect`
Return raster inspect wrapper payload.
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/stats`
Return raster band statistics payload.
If raster processing dependencies are unavailable:
- code: `RASTER_PROCESSING_UNAVAILABLE`
- message: dependency-specific unavailable message.
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/preview`
Preview readiness for raster layers.
If preview dependencies are unavailable:
- code: `RASTER_PROCESSING_UNAVAILABLE`
- message: `Raster preview unavailable...`
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/clip`
Clip raster by selected area. Returns a `202`-style accepted job payload through the job wrapper (`jobs` create/read flow).
If raster processing dependencies are unavailable:
- code: `RASTER_PROCESSING_UNAVAILABLE`
- message: `Raster processing unavailable. Install rasterio and GDAL-compatible drivers to enable raster processing operations.`
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/reproject`
Reproject raster dataset to another CRS.
Input:
- `target_crs` (default: `EPSG:31370`)
- `resampling` (`nearest`, `bilinear`, `cubic`; default `nearest`)
- `output_name`
Returns a job payload with derived dataset id in `result.output_dataset_id`.
Failure modes:
- code: `INVALID_PARAMETERS` for bad CRS or resampling
- code: `INVALID_DATASET_CRS` when source raster CRS is missing
- code: `RASTER_PROCESSING_UNAVAILABLE` when rasterio is unavailable
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/indices/ndvi`
Compute NDVI from raster band pairs.
Input:
- `nir_band` (positive integer, 1-based)
- `red_band` (positive integer, 1-based)
- `output_name` (optional)
Returns a job payload with derived dataset id in `result.output_dataset_id`.
Failure modes:
- code: `INVALID_PARAMETERS` for non-positive/non-integer band indices
- code: `INVALID_PARAMETERS` for band index outside source band count
- code: `INVALID_DATASET_TYPE` when source is not raster
- code: `RASTER_PROCESSING_UNAVAILABLE` when rasterio or numpy is unavailable
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/indices/ndwi`
Compute NDWI from raster band pairs.
Input:
- `nir_band` (positive integer, 1-based)
- `green_band` (positive integer, 1-based)
- `output_name` (optional)
Returns a job payload with derived dataset id in `result.output_dataset_id`.
Failure modes:
- code: `INVALID_PARAMETERS` for non-positive/non-integer band indices
- code: `INVALID_PARAMETERS` for band index outside source band count
- code: `INVALID_DATASET_TYPE` when source is not raster
- code: `RASTER_PROCESSING_UNAVAILABLE` when rasterio or numpy is unavailable
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/indices/ndbi`
Compute NDBI from raster band pairs.
Input:
- `nir_band` (positive integer, 1-based)
- `swir_band` (positive integer, 1-based)
- `output_name` (optional)
Returns a job payload with derived dataset id in `result.output_dataset_id`.
Failure modes:
- code: `INVALID_PARAMETERS` for non-positive/non-integer band indices
- code: `INVALID_PARAMETERS` for band index outside source band count
- code: `INVALID_DATASET_TYPE` when source is not raster
- code: `RASTER_PROCESSING_UNAVAILABLE` when rasterio or numpy is unavailable
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/raster/tile`
Generate raster tiles and a manifest for downstream processing. Returns a job payload with `tile_set_id` and manifest metadata.
If raster processing dependencies are unavailable:
- code: `RASTER_PROCESSING_UNAVAILABLE`
- message: `Raster processing unavailable. Install rasterio and GDAL-compatible drivers to enable raster processing operations.`
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/clip`
Clip vector dataset to selected area.
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/buffer`
Apply buffer distance to vector features.
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/intersect`
Intersect source vector dataset with another vector dataset.
### POST `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/stats`
Return vector stats (feature counts and geometry summary).
### GET `/api/v1/projects/{project_id}/datasets/{dataset_id}/vector/bbox`
Return vector bounds and feature count.
## Jobs
### POST `/api/v1/projects/{project_id}/jobs`
Create a job.
### GET `/api/v1/projects/{project_id}/jobs`
List jobs.
### GET `/api/v1/projects/{project_id}/jobs/{job_id}`
Read job detail.
### GET `/api/v1/projects/{project_id}/jobs/{job_id}/status`
Read simplified job status payload.
## Provider registry
### GET `/api/v1/external/providers`
Returns all configured provider capability descriptors.
### GET `/api/v1/external/providers/capabilities`
Compatibility alias for listing provider capability descriptors.
### GET `/api/v1/external/providers/{provider_name}`
Returns one provider capability descriptor.
### GET `/api/v1/external/providers/{provider_name}/layers`
Returns the supported provider layers.
### GET `/api/v1/external/providers/{provider_name}/status`
Returns configured/status/limitation fields.
### POST `/api/v1/external/providers/{provider_name}/import`
Defines the future provider import contract. Sprint 7B does not perform live imports or write datasets.
Request:
```json
{
"project_id": "uuid-or-local-id",
"area_id": "optional uuid-or-local-id",
"layers": ["buildings"],
"dataset_role": "optional source|reference"
}
```
GRB/OSM response:
```json
{
"provider_name": "grb",
"status": "not_configured",
"message": "No live GRB import is configured in Sprint 7B.",
"requested_layers": ["buildings"],
"dataset_id": null,
"dataset_role": "reference",
"source_name": "grb"
}
```
Manual and fixture providers point callers to existing upload/fixture flows. No provider writes directly to `vector_features`; all future provider output must flow through `DatasetService` and `VectorFeatureService`.
## External data fetchers
### POST `/api/v1/external/osm/fetch`
Request:
```json
{
"project_id": "uuid",
"area_id": "uuid",
"layers": ["buildings", "roads", "water", "green"]
}
```
### POST `/api/v1/external/grb/fetch`
Request:
```json
{
"project_id": "uuid",
"area_id": "uuid",
"layers": ["buildings"]
}
```
V1 may initially implement this as a service interface with a clear `not_configured` response until the exact WFS endpoint is wired.
Sprint 7B provider contract responses expose capabilities only. Providers must report:
```json
{
"provider_name": "osm",
"display_name": "OpenStreetMap",
"authority_level": "contextual",
"supported_layers": ["buildings", "roads", "water", "landuse"],
"supported_geometry_types": ["Polygon", "MultiPolygon", "LineString", "MultiLineString"],
"supported_query_modes": ["area"],
"configured": false,
"status": "not_configured",
"limitation_message": "OSM live Overpass/download integration is not configured in Sprint 7B.",
"attribution": "OpenStreetMap contributors",
"license_note": "OpenStreetMap data is available under ODbL; attribution is required."
}
```
No GRB WFS, OSM Overpass or provider downloads are implemented in Sprint 7B.
## Demo workflow
### POST `/api/v1/demo/workflow`
Seeds an explicit offline demo workflow from local fixture files. This endpoint
does not fetch live GRB/OSM data and does not run AI inference. It creates or
returns:
- one demo project
- one demo AOI
- one fixture reference building dataset
- one fixture candidate/predicted building dataset
- one persisted QA/QC result with metric rows
The endpoint is idempotent for the named demo project.
Response:
```json
{
"project_id": "uuid",
"area_id": "uuid",
"reference_dataset_id": "uuid",
"candidate_dataset_id": "uuid",
"quality_check_id": "uuid",
"metric_count": 6,
"status": "ready",
"message": "Demo workflow seeded from explicit local fixtures.",
"created": true
}
```
## Analysis
## Detection Lab
Sprint 8 implements Detection Lab foundation only. YOLO/PyTorch real inference is not enabled, no model is downloaded, and fixture detections require explicit fixture mode.
### GET `/api/v1/detection/models`
Returns object-detection model capability descriptors.
```json
{
"models": [
{
"model_id": "yolo-placeholder",
"display_name": "YOLO detector placeholder",
"framework": "ultralytics/pytorch",
"task_type": "object_detection",
"supported_classes": ["building", "road", "water", "landuse"],
"configured": false,
"status": "not_configured",
"limitation_message": "YOLO/PyTorch inference is not configured in Sprint 8; no model is downloaded or executed.",
"version": null
},
{
"model_id": "yolo-configured",
"display_name": "Configured YOLO detector",
"framework": "ultralytics/pytorch",
"task_type": "object_detection",
"supported_classes": ["building", "road", "water", "landuse"],
"configured": false,
"status": "not_configured",
"limitation_message": "YOLO is disabled. Set YOLO_ENABLED=true and YOLO_MODEL_PATH to a local model file to enable inference.",
"version": null
}
]
}
```
### POST `/api/v1/detection/run`
Creates a detection job and detection analysis run. If the requested model is unavailable, the job and analysis run are marked `failed` with `DETECTION_MODEL_UNAVAILABLE` or `DETECTION_DEPENDENCY_UNAVAILABLE`.
Request:
```json
{
"project_id": "uuid",
"dataset_id": "uuid",
"model_id": "yolo-placeholder",
"confidence_threshold": 0.5,
"class_filter": ["building"],
"tile_manifest_path": null,
"parameters_json": {}
}
```
Sprint 8B configured YOLO mode uses `model_id: "yolo-configured"`. It requires:
- `YOLO_ENABLED=true`
- `YOLO_MODEL_PATH` pointing to an existing local model file
- backend optional AI dependencies installed with `geointel-backend[ai]`
- `tile_manifest_path` pointing to an existing raster tile manifest generated by the raster tile operation
GeoIntel does not download model weights automatically. Configured YOLO runs read existing tile files from the manifest, convert YOLO pixel-space boxes to EPSG:4326 detection polygons and persist detections as first-class records.
Unavailable model response:
```json
{
"analysis_run_id": "uuid",
"job_id": "uuid",
"project_id": "uuid",
"dataset_id": "uuid",
"model_id": "yolo-placeholder",
"status": "failed",
"detection_count": 0,
"error_code": "DETECTION_MODEL_UNAVAILABLE",
"message": "YOLO/PyTorch inference is not configured in Sprint 8; no model is downloaded or executed."
}
```
Validation errors:
- `INVALID_DATASET_TYPE` when the dataset is not raster.
- `DETECTION_MODEL_NOT_FOUND` when the model id is unknown.
- `FIXTURE_MODE_REQUIRED` when `manual-fixture-detector` is requested without `parameters_json.fixture_mode=true`.
- `DETECTION_TILE_MANIFEST_REQUIRED` when `yolo-configured` is requested without `tile_manifest_path`.
- `DETECTION_TILE_MANIFEST_NOT_FOUND` when the provided manifest path does not exist.
- `DETECTION_TILE_MANIFEST_INVALID` when the manifest cannot be parsed or lacks tile metadata.
- `DETECTION_TILE_LIMIT_EXCEEDED` when the manifest exceeds `YOLO_MAX_TILES`.
- `DETECTION_DEPENDENCY_UNAVAILABLE` when YOLO dependencies are not installed.
- `DETECTION_MODEL_LOAD_FAILED` when the local model file exists but cannot be loaded.
Fixture detector mode is test/demo-only. It persists only explicit `parameters_json.fixture_detections` entries and is never invoked automatically.
### GET `/api/v1/detection/runs/{analysis_run_id}`
Returns one detection analysis run.
### GET `/api/v1/detection/runs`
Returns detection analysis runs, optionally filtered by `project_id` and `dataset_id`.
### GET `/api/v1/detection/runs/{analysis_run_id}/detections`
Returns persisted detections for a detection analysis run. Optional filters:
- `dataset_id`
- `class_name`
- `min_confidence`
### GET `/api/v1/detection/datasets/{dataset_id}/detections`
Returns persisted detections for a raster dataset. Optional filters:
- `analysis_run_id`
- `class_name`
- `min_confidence`
### GET `/api/v1/detection/detections/{detection_id}`
Returns one persisted detection.
### GET `/api/v1/detection/runs/{analysis_run_id}/geojson`
Returns persisted detections for a run as a GeoJSON FeatureCollection. Geometry comes from persisted PostGIS detection geometry in EPSG:4326.
Each feature includes:
- `detection_id`
- `class_name`
- `confidence`
- `model_name`
- `model_version`
- `analysis_run_id`
- `dataset_id`
- `job_id`
- `source_tile_path`
- `bbox_json`
### GET `/api/v1/detection/datasets/{dataset_id}/geojson`
Returns persisted detections for a dataset as a GeoJSON FeatureCollection. Optional filters match the detection list endpoint.
### POST `/api/v1/detection/runs/{analysis_run_id}/qa/reference`
Compares persisted detection geometries from an analysis run against persisted `vector_features` from a reference vector dataset.
Request:
```json
{
"reference_dataset_id": "uuid",
"iou_threshold": 0.5,
"class_name": "building",
"min_confidence": 0.5
}
```
Response persists a `quality_check` and `metrics` rows through the existing QA/QC persistence architecture and returns:
- `precision`
- `recall`
- `f1_score`
- `mean_iou`
- `false_positives`
- `false_negatives`
- `quality_check_id`
If the reference dataset has no persisted vector features, the endpoint returns `REFERENCE_FEATURES_NOT_FOUND`. It does not calculate fake QA metrics.
### POST `/api/v1/analysis/building-stats`
Input: area + vector building layer.
### POST `/api/v1/analysis/object-detection`
Request:
```json
{
"project_id": "uuid",
"area_id": "uuid",
"dataset_id": "uuid",
"model_id": "optional uuid",
"classes": ["building"],
"confidence_threshold": 0.35,
"tile_size": 640,
"overlap": 64
}
```
Response: `AnalysisRunRead`.
### POST `/api/v1/analysis/segmentation`
Same pattern as object detection, but output includes masks and polygonized geometries.
## Segmentation Lab
Sprint 9 implements Segmentation Lab foundation only. Real SAM and YOLO-seg inference are not enabled, no model is downloaded, and fixture segmentations require explicit fixture mode.
### GET `/api/v1/segmentation/models`
Returns segmentation model capability descriptors:
- `segmentation-placeholder`: `not_configured`
- `fixture-segmenter`: configured for explicit test/demo fixtures only
- `yolo-seg-configured`: `not_configured`
- `sam-configured`: `not_configured`
### POST `/api/v1/segmentation/run`
Creates a segmentation job and segmentation analysis run. If the requested model is unavailable, the job and analysis run are marked `failed` with `SEGMENTATION_MODEL_UNAVAILABLE`.
Request:
```json
{
"project_id": "uuid",
"dataset_id": "uuid",
"model_id": "segmentation-placeholder",
"confidence_threshold": 0.5,
"class_filter": ["vegetation"],
"tile_manifest_path": null,
"parameters_json": {}
}
```
Fixture segmenter mode is test/demo-only. It persists only explicit `parameters_json.fixture_segmentations` entries when `parameters_json.fixture_mode=true`; it is never invoked automatically and does not represent production inference.
Validation errors:
- `INVALID_DATASET_TYPE` when the dataset is not raster.
- `SEGMENTATION_MODEL_NOT_FOUND` when the model id is unknown.
- `FIXTURE_MODE_REQUIRED` when `fixture-segmenter` is requested without `parameters_json.fixture_mode=true`.
- `INVALID_FIXTURE_SEGMENTATIONS` when fixture payloads are not a list.
- `INVALID_FIXTURE_GEOMETRY` when fixture geometry is empty, invalid or not Polygon/MultiPolygon.
### GET `/api/v1/segmentation/runs`
Returns segmentation analysis runs, optionally filtered by `project_id` and `dataset_id`.
### GET `/api/v1/segmentation/runs/{analysis_run_id}`
Returns one segmentation analysis run.
### GET `/api/v1/segmentation/runs/{analysis_run_id}/segmentations`
Returns persisted segmentation records for a segmentation analysis run. Optional filters:
- `dataset_id`
- `class_name`
- `min_confidence`
### GET `/api/v1/segmentation/datasets/{dataset_id}/segmentations`
Returns persisted segmentation records for a raster dataset. Optional filters:
- `analysis_run_id`
- `class_name`
- `min_confidence`
### GET `/api/v1/segmentation/segmentations/{segmentation_id}`
Returns one persisted segmentation record.
### GET `/api/v1/segmentation/runs/{analysis_run_id}/geojson`
Returns persisted segmentations for a run as a GeoJSON FeatureCollection. Geometry comes from persisted PostGIS segmentation geometry in EPSG:4326.
Each feature includes:
- `segmentation_id`
- `class_name`
- `confidence`
- `area_m2`
- `model_name`
- `model_version`
- `analysis_run_id`
- `dataset_id`
- `job_id`
- `source_tile_path`
- `tile_index`
- `mask_path`
- `bbox_json`
- `provenance_json`
### GET `/api/v1/segmentation/datasets/{dataset_id}/geojson`
Returns persisted segmentations for a dataset as a GeoJSON FeatureCollection. Optional filters match the segmentation list endpoint.
### POST `/api/v1/segmentation/runs/{analysis_run_id}/qa/reference`
Compares persisted segmentation geometries from an analysis run against persisted `vector_features` from a reference vector dataset.
Request:
```json
{
"reference_dataset_id": "uuid",
"iou_threshold": 0.5,
"class_name": "vegetation",
"min_confidence": 0.5
}
```
Response persists a `quality_check` and `metrics` rows through the existing QA/QC persistence architecture and returns precision, recall, F1, mean IoU and false positive/negative counts.
If the segmentation run has no persisted geometries, the endpoint returns `SEGMENTATIONS_NOT_FOUND`. If the reference dataset has no persisted vector features, it returns `REFERENCE_FEATURES_NOT_FOUND`. It does not calculate fake QA metrics.
### POST `/api/v1/analysis/change-detection`
Request contains source analysis or datasets A/B and method.
## QA/QC
### POST `/api/v1/qa/detections-vs-reference`
Request:
```json
{
"candidate_dataset_id": "uuid",
"reference_dataset_id": "uuid",
"iou_threshold": 0.5,
"area_id": "optional uuid"
}
```
Response is wrapped in the job envelope. On success, `result_json` includes precision, recall, F1, mean IoU, false positives, false negatives and `quality_check_id`.
Sprint 7A persists the QA/QC result as:
- `jobs`: execution state.
- `quality_checks`: domain result.
- `metrics`: individual measurements.
Future Detection and Segmentation flows may add an `analysis_run_id` path without replacing persisted quality checks.
### GET `/api/v1/projects/{project_id}/quality-checks`
Lists persisted QA/QC quality checks for a project with metric rows.
Response:
```json
{
"items": [
{
"id": "uuid",
"project_id": "uuid",
"job_id": "uuid-or-null",
"analysis_run_id": "uuid-or-null",
"candidate_dataset_id": "uuid-or-null",
"reference_dataset_id": "uuid",
"check_type": "demo_candidate_vs_reference",
"status": "ok",
"score": 0.5,
"parameters_json": {},
"findings_json": {},
"metrics": [
{
"metric_key": "precision",
"metric_value": 0.5
}
]
}
],
"total": 1,
"limit": 50,
"offset": 0
}
```
## Exports
### POST `/api/v1/exports/geojson`
Export detections, segmentations or vector layer to GeoJSON.
Dataset vector export request:
```json
{
"export_kind": "dataset",
"dataset_id": "uuid",
"name": "optional-basename"
}
```
Detection run export request:
```json
{
"export_kind": "detection_run",
"analysis_run_id": "uuid",
"name": "optional-basename"
}
```
Segmentation run export request:
```json
{
"export_kind": "segmentation_run",
"analysis_run_id": "uuid",
"name": "optional-basename"
}
```
Response persists an `exports` row and writes a deterministic JSON artifact:
```json
{
"export_id": "uuid",
"path": "storage/exports/{project_id}/datasets/{target}/{name}.geojson",
"status": "ready",
"export_type": "dataset_geojson",
"metadata_json": {
"source": "dataset",
"feature_count": 0
}
}
```
Vector dataset exports use the stored dataset GeoJSON. Detection and
segmentation exports use persisted first-class geometry records and the
existing Detection/Segmentation GeoJSON conversion services. Raster datasets
are rejected for dataset GeoJSON export.
### POST `/api/v1/exports/metadata`
Exports project metadata JSON for projects, datasets, persisted QA/QC summary
rows and existing export history.
```json
{
"project_id": "uuid",
"name": "optional-basename"
}
```
### GET `/api/v1/exports/projects/{project_id}/exports`
Lists persisted export records for a project.
### GET `/api/v1/exports/{export_id}`
Returns one persisted export record.
### GET `/api/v1/exports/{export_id}/content`
Returns the stored JSON artifact content through the standard API envelope.
### GET `/api/v1/exports/{export_id}/download`
Downloads the stored JSON/GeoJSON export artifact as a raw file response with
`application/json` content type and a `Content-Disposition` attachment
filename. This endpoint intentionally does not use the JSON envelope because
it is a browser/file-download path; callers that need canonical API JSON should
use `/content`.
### POST `/api/v1/exports/yolo`
Export annotations/detections to YOLO format.
### POST `/api/v1/exports/report`
Creates a lightweight HTML project report artifact from persisted project,
dataset, QA/QC summary and export history state. This does not create a PDF
and does not introduce a report designer.
```json
{
"project_id": "uuid",
"name": "optional-basename"
}
```
Response persists an `exports` row with `export_type:
project_report_html`. Download the report through:
```text
GET /api/v1/exports/{export_id}/download
```
PDF/report-designer functionality can be added after core GeoAI workflows work.