The review vocabulary already separates a model error from a reference gap, because the product's position is that official footprints are not automatically perfect ground truth. Those verdicts were only counted. An operator who inspected forty false positives and established that twelve are buildings the reference simply lacks still saw a precision counting all forty against the model — a number they had personally disproved, on the panel where they disproved it. Applying the verdicts gives an adjudicated score reported next to the raw one, so nothing is quietly improved. Not being able to judge is not evidence in the model's favour, so uncertain and obscured verdicts keep counting, as does a decision from a later release that this runtime does not recognise. Because part of the evidence is usually still unreviewed, the honest form is an interval rather than a single corrected number: pessimistic assumes every unreviewed finding is a model error, optimistic assumes none is, and the headline equals the pessimistic reading so a partly reviewed check never presents as a settled one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
117 KiB
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
ApiErrorschema. - Every successful JSON endpoint has a concrete Pydantic response model and
uses the canonical
{"data": ...}envelope. Readiness runs an OpenAPI audit that rejects free-form dictionary responses and envelope drift. - The only successful non-envelope responses are
/health,/health/live,/health/ready, the four documented persisted-raster PNG endpoints and the export artifact download endpoint.
Shared schemas
ApiError
{
"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
{
"min_x": 0.0,
"min_y": 0.0,
"max_x": 0.0,
"max_y": 0.0,
"crs": "EPSG:4326"
}
Operator authentication and guest demo
Authentication remains an optional single-operator access gate, not multi-user
account management or tenant isolation. When GEOINTEL_AUTH_ENABLED=true,
every /api/v1/* request except the four authentication endpoints below
requires a valid signed geointel_session cookie. Missing, expired or modified
sessions return HTTP 401 with AUTHENTICATION_REQUIRED. Direct loopback calls
to the backend without proxy headers remain available to trusted in-container
operator tools; the backend is bound to loopback in the all-in-one runtime.
The runtime stores only a PBKDF2-SHA256 operator password hash and an
independent session-signing secret. The browser receives an HttpOnly,
SameSite=Strict, time-limited cookie. Five failed operator-login attempts for
one client/username combination within five minutes temporarily return HTTP
429 LOGIN_RATE_LIMITED.
Optional guest access is a configuration-gated demonstration mode. It creates
a shorter signed session with role guest, scopes that session to the
idempotently seeded demo project and blocks mutating operator routes. Project
listing is filtered to the bound demo project. The frontend exposes the same
exploration, assistant, model-selection, analysis, QA and export workspaces as
an operator. Model catalogs are globally readable; every run, result and export
request remains explicitly bound to the demo-project UUID. Project and area
management, uploads, source/runtime configuration, evidence review and other
administrative mutations remain unavailable. This is deliberately not
a substitute for user accounts, authorization or tenant isolation; expose it
only on a dedicated demo installation without private or operational data.
GET /api/v1/auth/session
Public session probe used by the frontend before it mounts the workbench.
When authentication is disabled, authenticated is true and
authentication_required is false so local development retains its existing
direct workflow. guest_access_enabled tells the landing page whether it may
show the guest action.
{
"data": {
"authentication_required": true,
"authenticated": false,
"username": null,
"expires_at": null,
"role": null,
"guest_access_enabled": true,
"guest_project_id": null
}
}
Authenticated operator sessions return role: "operator". Guest sessions
return role: "guest" and the UUID of their bound demo project in
guest_project_id.
POST /api/v1/auth/login
{
"username": "operator",
"password": "user-supplied secret"
}
Successful login sets the session cookie and returns the authenticated session
shape. Invalid credentials return HTTP 401 INVALID_CREDENTIALS; username
existence is not disclosed.
POST /api/v1/auth/guest
No request body is required. The endpoint is available only when both
GEOINTEL_AUTH_ENABLED=true and GEOINTEL_GUEST_ACCESS_ENABLED=true. It
idempotently prepares the canonical demo workflow, creates a short-lived guest
session bound to that project and returns the normal session shape.
Disabled guest access returns HTTP 403 GUEST_ACCESS_DISABLED. A guest request
for a different project returns HTTP 403 GUEST_PROJECT_SCOPE_REQUIRED; a
blocked mutation returns HTTP 403 GUEST_READ_ONLY. Unscoped read routes that
are not needed by the demo return HTTP 403 GUEST_ROUTE_NOT_AVAILABLE.
Guest reads are limited to the filtered project list, provider/model metadata,
the bound project tree and project-scoped detection, segmentation and export
results. An explicit set of POST selection, assistant, AI/QA and export routes
is available for that bound demo project. Unscoped analysis routes require the
same UUID as a project_id query parameter; cross-project values fail before
route execution. Coverage resolution additionally verifies the project_id in
the request body against the guest-session scope.
POST /api/v1/auth/logout
Clears the browser cookie and returns an unauthenticated session. Logout is idempotent and remains callable when the current cookie is missing or expired.
Health
GET /health/live
Returns process liveness only. It never queries PostgreSQL.
{
"status": "ok",
"service": "geointel-backend",
"version": "1.0.0",
"build_sha": null,
"build_time": null
}
GET /health
Backward-compatible alias for dependency readiness.
GET /health/ready
Returns dependency readiness. Both readiness routes return HTTP 503 when the
database, PostGIS, single migration head or writable storage check is
degraded. Docker uses /health/ready.
{
"status": "ok",
"service": "geointel-backend",
"version": "1.0.0",
"database": "ok",
"postgis": "ok:3.x",
"migration": "ok:202607160001",
"storage": "ok",
"checks": {
"database": "ok",
"postgis": "ok:3.x",
"migration": "ok:202607160001",
"storage": "ok"
}
}
GET /api/v1/system/capabilities
Returns enabled feature flags and tool availability in the canonical data envelope. PostGIS and configured YOLO state are derived at runtime.
{
"data": {
"postgis": true,
"rasterio": true,
"geopandas": true,
"yolo": true,
"yolo_status": "configured",
"sam": false,
"grb": "bounded",
"sentinel": "planned",
"version": "1.0.0",
"build_sha": null,
"providers": []
}
}
Projects
GET /api/v1/projects
Returns active projects by default with limit/offset pagination. The
optional status query accepts active, archived or all; deleted projects
are never returned. Optional exact name filtering supports stable lookup of
a canonical operational workspace without depending on its position among
newer operator or benchmark projects:
GET /api/v1/projects?name=Belgium%20and%20North%20Sea%20Workbench&limit=1
GET /api/v1/projects?status=archived&limit=50
POST /api/v1/projects
Request:
{
"name": "Geel building detection demo",
"description": "Detect buildings and validate against GRB",
"region": "Belgium and Belgian North Sea"
}
Response: ProjectRead.
GET /api/v1/projects/{project_id}
Returns one project with summary counts.
PATCH /api/v1/projects/{project_id}
Updates name, description, region or the ordinary lifecycle status. The status
can only be active or archived. Archiving keeps datasets, jobs, analyses,
quality checks and exports intact while removing the workspace from the
default active-project list.
DELETE /api/v1/projects/{project_id}
Marks the project as deleted. This route does not remove storage artifacts or
related persistence and is not the ordinary workspace-cleanup path. Operators
and the UI use PATCH with status: "archived" for reversible cleanup.
Areas
GET /api/v1/projects/{project_id}/areas
Returns areas for a project. Area responses include persisted AOI geometry as GeoJSON so the frontend can display the selected area in the map workbench.
{
"id": "uuid",
"project_id": "uuid",
"name": "Geel Centrum AOI",
"original_crs": "EPSG:4326",
"area_m2": 1234.5,
"created_at": "timestamp",
"geometry_type": null,
"geometry": {
"type": "MultiPolygon",
"coordinates": []
}
}
POST /api/v1/projects/{project_id}/areas
Request:
{
"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.
GET /api/v1/projects/{project_id}/areas/{area_id}
Returns one project area. The payload uses the same AreaRead shape as the
area list endpoint and includes persisted GeoJSON geometry for map display.
PATCH /api/v1/projects/{project_id}/areas/{area_id}
Updates the area name and/or geometry. Geometry updates follow the same validation, repair and metric-calculation rules as area creation.
GET /api/v1/projects/{project_id}/areas/municipalities
Searches the persisted authoritative NGI AdminVector municipality layer by
Dutch, French or German name and NIS code. query is optional and limit is
bounded to 50. Results contain names and NIS identity but no fabricated or
browser-fetched geometry.
POST /api/v1/projects/{project_id}/areas/municipalities/{niscode}/activate
Idempotently creates or returns a project Area from the exact persisted NGI municipality geometry. The resulting Area can be used by all existing bounded selection, acquisition, analysis and export contracts.
Resumable AOI operations
POST /api/v1/projects/{project_id}/aoi-operations
Creates one persisted parent operation and deterministic bounded partitions.
Exactly one of area_id or an EPSG:4326 bbox is required. Partitioning is
calculated in EPSG:31370 and clipped to the exact AOI; source side limits remain
server concerns. Optional coverage_zone clips the immutable AOI snapshot to
the persisted legal/regional scope before planning. The production worker
automatically claims queued children; clients poll the parent instead of
driving provider requests.
{
"area_id": "optional-uuid",
"operation_type": "acquire",
"provider_key": "grb",
"product_key": "buildings",
"max_partition_side_m": null,
"max_attempts": 3,
"parameters_json": {"force_refresh": false}
}
When max_partition_side_m is omitted, the backend derives the limit from the
governed provider registry. An explicit value can only make partitions smaller,
never relax the provider budget. The response reports parent status, progress from 0.0 to 1.0, per-status
partition counts and child partition evidence. Stable partition keys make
planning and completion idempotent.
GET /api/v1/projects/{project_id}/aoi-operations/{operation_id}
Returns the persisted operation, aggregate progress and every child state.
GET /api/v1/projects/{project_id}/aoi-operations
Lists the most recent parent operations for the project with their derived progress, status counts and child evidence.
POST /api/v1/projects/{project_id}/aoi-operations/{operation_id}/execute-next
Claims and executes one queued partition through the registered governed
provider. Registered executors cover grb, orthophoto, dhmv,
spw_terrain, official_vector, flood_hazard, thematic_raster, walous,
bathymetry_profiles and mdk_bathymetry; their existing configuration and
zone contracts remain authoritative. Every
execution creates a linked child Job. Transient provider failures are retried
within the stored attempt budget; validation failures fail immediately.
POST /api/v1/projects/{project_id}/aoi-operations/{operation_id}/partitions/claim
Atomically claims the next queued child using a locked, skip-locked database
selection. Returns data: null when no queued child remains.
PUT /api/v1/projects/{project_id}/aoi-operations/{operation_id}/partitions/{partition_id}/checkpoint
Persists provider-specific restart evidence for a running child.
POST /api/v1/projects/{project_id}/aoi-operations/{operation_id}/partitions/{partition_id}/complete
Marks a running child successful or explicitly skipped. Repeating completion for a terminal successful/skipped child is idempotent.
POST /api/v1/projects/{project_id}/aoi-operations/{operation_id}/partitions/{partition_id}/fail
Records an error and either returns the child to queued within its attempt
budget or terminalizes it as failed.
Datasets
POST /api/v1/projects/{project_id}/datasets/upload
Multipart upload.
Fields:
file: dataset file.dataset_type:vector,geojson(legacy),raster.source: descriptive caller text, e.g.user_upload. It is retained as a claim only and never establishes authority.dataset_role:source,derived, orreference(defaultsource).source_name: optional descriptive claim. Public uploads are always bound to the server-ownedmanualregistry entry, including when this field saysgrb,osmor another official name. Reference uploads also default tomanual.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, plus
source_registry_id, source_snapshot_id, exact data-contract key/version,
validation report/status, provenance/lineage status, quarantine status and an
idempotent ingest key. A malformed or doubtful artifact is retained as
status=quarantined; it is not silently discarded or made ready.
Vector uploads remain stored as original files and are also persisted into
vector_features as queryable PostGIS state only after their contract passes.
Non-EPSG:4326 vector coordinates are explicitly transformed before canonical
feature persistence; relabelling Lambert coordinates as EPSG:4326 is rejected.
GET /api/v1/source-registry
Lists server-owned source definitions. Optional query parameter
classification is one of authoritative, corroborative, contextual,
derived, or experimental. This endpoint reports policy and snapshot counts;
it does not assert that historical datasets carrying a matching text field are
trusted. It is operator-only because source snapshots can contain
operator-acquisition provenance. A demo session uses its project-scoped
dataset provenance endpoint instead.
GET /api/v1/source-registry/{source_key}
Returns one source definition and its immutable snapshots, including authority
scope, licence/restrictions, expected geometry/attributes, freshness and known
limitations. Unknown source keys return SOURCE_REGISTRY_ENTRY_NOT_FOUND.
GET /api/v1/projects/{project_id}/datasets/{dataset_id}/provenance
Returns the dataset's bound source/snapshot, contract report, lineage edges and quarantine records. A missing binding is visible as incomplete provenance; it is never backfilled from a display name or caller metadata.
GET /api/v1/projects/{project_id}/datasets/orthophoto/products
Return the governed Belgian orthophoto product allowlist in the canonical
envelope. It contains Digitaal Vlaanderen products plus wallonia_latest
(SPW) and brussels_latest (Paradigm UrbIS). Every product reports its key,
provider, coverage zone, display/observation label, temporal granularity,
native resolution, colour mode, catalogue URL, attribution, licence note,
limitations and whether configured-YOLO detection is allowed.
POST /api/v1/projects/{project_id}/datasets/orthophoto/acquire
Explicitly acquire a bounded orthophoto selection from a governed official
regional WMS product. Arbitrary WMS URLs and layer names are not accepted.
The regional products require the persisted project Areas Wallonia or
Brussels-Capital Region and reject a rectangle unless that Area covers at
least 99% of it.
{
"bbox": {"min_x": 5.10, "min_y": 51.17, "max_x": 5.11, "max_y": 51.18, "crs": "EPSG:4326"},
"area_id": "optional-project-area-uuid",
"product_key": "most_recent",
"resolution_m": 0.25,
"force_refresh": false
}
The canonical envelope contains a synchronous Job. Its output_dataset_id
identifies the raster Dataset; result_json contains provider, layer, pixel
dimensions, EPSG:4326/EPSG:31370 bounds, sampling resolution, attribution,
cache reuse and limitation text. Historical products also persist their
observation/validity period and a spatially scoped temporal-series key.
resolution_m is optional (0.1-2.0 m) and can never be finer than the
allowlisted product's native resolution. It exists for governed training and
review exports; ordinary workbench requests retain the configured 1 m default.
For rolling latest products, acquisition time is not represented as the
per-pixel observation date: provenance explicitly records
observation_time_precision=unknown_per_pixel.
GET /api/v1/projects/{project_id}/datasets/grb/products
Returns the fixed official GRB vector registry in the canonical envelope.
Only buildings (GBG), roads (Wegsegment), water
(WTZ/WLAS/WGR) and parcels (ADP) are exposed. Each product reports
its exact source collections, supported geometry types, attribution, licence
and semantic limitation.
POST /api/v1/projects/{project_id}/datasets/grb/acquire
Acquires one explicitly bounded GRB product from the allowlisted Digitaal Vlaanderen OGC API Features service behind the synchronous Job abstraction:
{
"bbox": {"min_x": 5.12, "min_y": 51.18, "max_x": 5.17, "max_y": 51.22, "crs": "EPSG:4326"},
"area_id": "optional-project-area-uuid",
"product_key": "buildings",
"force_refresh": false
}
The backend requires EPSG:4326, intersects the rectangle with the optional persisted Area, rejects sides below 10 m or above 20 km, follows only same-host allowlisted collection pagination and fails instead of truncating when page, transfer or feature limits are reached. Every official feature id, request URL, response checksum and artifact checksum remains provenance. Although GRB is stored natively in EPSG:31370, the service explicitly negotiates OGC CRS84 longitude/latitude GeoJSON for both bbox and output before performing the exact geometry intersection.
Output is an ordinary reference Dataset persisted through DatasetService,
DatasetVersion and VectorFeatureService; providers never write directly
to vector_features. Exact requests are reused for 24 hours unless
force_refresh=true. Selection metrics are footprint hectares for buildings,
line kilometres for roads, hectares plus supporting line kilometres for
water, and hectares for parcels. Water volume is unsupported because GRB
contains no depth or bathymetry.
GET /api/v1/projects/{project_id}/datasets/dhmv/products
Returns the fixed official DHMV II product registry in the canonical envelope.
The registry contains only dtm_1m (DHMVII_DTM_1m) and dsm_1m
(DHMVII_DSM_1m). Each item records native 1 m resolution, EPSG:31370, TAW,
the 2013-2015 acquisition period, attribution, catalogue and limitations.
POST /api/v1/projects/{project_id}/datasets/dhmv/acquire
Runs a bounded WCS 2.0.1 GetCoverage request behind the existing synchronous
Job abstraction. Arbitrary coverage identifiers are rejected.
{
"bbox": {"min_x": 5.0, "min_y": 51.1, "max_x": 5.2, "max_y": 51.3, "crs": "EPSG:4326"},
"area_id": "optional-project-area-uuid",
"product_key": "dtm_1m",
"resolution_m": 5.0,
"force_refresh": false
}
The service extracts the GeoTIFF from the official multipart response, clips
to the exact persisted Area when supplied, validates EPSG:31370, one band,
resolution, nodata and valid cells, then persists through DatasetService.
The default 5 m file is an analysis copy of the retained 1 m source product;
both resolutions and all request/response/output checksums remain provenance.
POST /api/v1/projects/{project_id}/datasets/{dataset_id}/raster/terrain/select
Accepts an EPSG:4326 rectangle and optional Area id. It reads only a governed,
ready DHMV Dataset and returns a canonical envelope with valid-cell coverage,
mean/min/max/P10/P90 height in m TAW, relief in metres and mean/P90/max slope
in degrees. Area geometry is an exact mask, not only a bounding box.
The response always lists water_depth_m and water_volume_m3 under
unsupported_metrics. Drainage is not calculated by this endpoint.
POST /api/v1/projects/{project_id}/datasets/raster/terrain/select
Runs the same exact terrain calculation over every persisted municipal DHMV
partition intersecting one bounded EPSG:4326 rectangle. The request adds the
governed product_key (dtm_1m or dsm_1m) to the ordinary selection bbox
and optional Area id. The backend mosaics only the intersecting windows in
EPSG:31370, enforces the existing 12-million-cell limit and calculates global
cell statistics. The canonical response includes dataset_ids and
partition_count; percentiles are calculated from the combined cells and are
not averages of municipal summaries.
GET /api/v1/projects/{project_id}/datasets/{dataset_id}/raster/terrain/image
Returns a browser-safe PNG colour relief for the persisted governed DHMV Dataset. This binary MapLibre source never accepts an arbitrary file path.
Safety contract:
- every side must measure between 128 m and 1,024 m in EPSG:31370;
- an optional
area_idmust belong to the project and cover at least 99% of the rectangle; - defaults are 1 m/pixel, a 32 MiB response limit and 24-hour exact-request reuse;
- WMS bytes are georeferenced to EPSG:31370 and persisted only through
DatasetService; no fetch runs on startup; - only
most_recent,wallonia_latestandbrussels_latestcan enter the configured-YOLO path; QA must use the matching regional reference source (GRB, PICC or UrbIS). Historical products remain visual evidence; - product periods such as
1979_1990remain explicitly multi-year and are not presented as exact annual observations.
GET /api/v1/projects/{project_id}/datasets/flood-hazard/products
Returns the fixed twelve-product VMM flood-depth registry in the canonical
envelope. Products combine pluviaal/fluviaal, current climate/climate
projection 2050 and T10/T100/T1000. Each product keeps the official WCS
coverage id, probability class, source unit centimetres, normalized unit
metres, publication metadata, attribution and limitation.
POST /api/v1/projects/{project_id}/datasets/flood-hazard/acquire
Acquires one bounded official VMM OGRK WCS 1.1 coverage behind the synchronous
Job abstraction. Arbitrary coverage identifiers and service URLs are rejected.
When product_key is omitted, the governed default is
pluviaal_current_t100; the default is itself present in the fixed product
registry.
{
"bbox": {"min_x": 5.0, "min_y": 51.1, "max_x": 5.2, "max_y": 51.3, "crs": "EPSG:4326"},
"area_id": "optional-project-area-uuid",
"product_key": "pluviaal_current_t100",
"resolution_m": 5.0,
"force_refresh": false
}
The service tiles municipality-size requests, validates EPSG:31370 and one
Float32 depth band, converts positive source centimetres to metres, clips to
the exact persisted Area and stores an ordinary raster Dataset and
DatasetVersion. Zero/null source cells become transparent nodata. The scenario
is not a temporal observation and receives no fabricated observed_at value.
POST /api/v1/projects/{project_id}/datasets/{dataset_id}/raster/flood-hazard/select
Returns mapped positive-depth area in hectares, share of the modelled area,
mean, P90 and maximum modeled local depth and
modelled_max_depth_area_integral_m3. Every result identifies mechanism,
climate context, probability class and return period. The integral sums local
modeled maximum depth times cell area; it is explicitly not concurrent flood
storage, permanent waterbody content, current water level or bathymetry. These
unsupported metrics remain listed in the response.
Three cell populations are reported separately, because conflating them turns missing data into a claim of safety:
selected_cell_count— cells inside the drawn selection;valid_cell_count/no_data_cell_count— the split between cells the VMM raster models and cells it does not;inundated_cell_count— modelled cells with a positive depth.
inundated_fraction and modelled_inundated_share_pct are shares of the
modelled cells, not of the drawn selection. inundated_fraction is null when
nothing was modelled at all. data_coverage_ratio, model_coverage_pct,
modelled_area_ha and selection_area_ha make the difference between the
drawn area and the analysed area explicit, and coverage_warning states it in
words. A selection reaching past the modelled extent previously reported a
diluted risk share for the whole rectangle.
Selections finer than one source cell. geometry_mask selects a cell when
its centre falls inside the geometry, so a rectangle smaller than a cell — or
one landing between four centres — selected nothing and the analysis returned
zeros indistinguishable from "nothing here". Every raster selection now falls
back to the cells the geometry touches and reports that in
cell_selection_warning (coverage_warning for flood hazard), because the
result then covers more ground than was drawn. This applies to terrain,
bathymetry, thematic raster and flood hazard, in both the single-dataset and
the partitioned paths.
POST /api/v1/projects/{project_id}/datasets/raster/flood-hazard/select
Runs exact bounded analysis over the persisted municipal VMM partitions for
one governed product_key. Only partitions intersecting the selection are
opened, the normalized metre grids are combined at their common 5 m analysis
resolution and the global area/depth metrics are calculated from the combined
cells. The canonical response includes every contributing Dataset id in
dataset_ids plus partition_count. The existing flood-volume and bathymetry
prohibitions are unchanged.
GET /api/v1/projects/{project_id}/datasets/{dataset_id}/raster/flood-hazard/image
Returns a constrained transparent PNG for a persisted governed VMM flood-depth Dataset. It never accepts an arbitrary path or coverage id and is used by the existing MapLibre image-overlay path.
GET /api/v1/projects/{project_id}/datasets/thematic-raster/products
Returns the fixed MercatorNet registry for space_occupation_2025,
open_space_2022, population_density_2019, node_value_2022 and
service_level_2022. Every item includes the governed WCS coverage id, native
resolution, source unit, observation year, legend, attribution and limitation.
POST /api/v1/projects/{project_id}/datasets/thematic-raster/acquire
Acquires one allowlisted official coverage behind the synchronous Job abstraction. The request accepts only an EPSG:4326 bbox, optional project Area, one registry product key and an explicit refresh flag:
{
"bbox": {"min_x": 5.03, "min_y": 51.15, "max_x": 5.25, "max_y": 51.33, "crs": "EPSG:4326"},
"area_id": "optional-project-area-uuid",
"product_key": "population_density_2019",
"force_refresh": false
}
The backend uses native 10 m or 100 m resolution, splits requests into bounded WCS 1.0 tiles, validates EPSG:31370 and documented source values, masks the exact Area and persists an ordinary raster Dataset and DatasetVersion. It does not accept arbitrary URLs, coverage ids, resolutions or expressions.
The Flanders map workbench orchestrates this existing endpoint only after an
explicit municipality or rectangle selection. It requests all five governed
products for the same bbox ∩ Area, reuses the checksum-bound Dataset for an
identical request and then calls the normal selection endpoint. The browser
never contacts WCS directly. A whole-Flanders raster request is intentionally
unavailable in the map flow because it exceeds the bounded 60 km/30 million
cell guardrail; users must choose a municipality or draw a smaller rectangle.
An Area is recorded as coverage_scope=municipality only when its canonical
name starts with Gemeente ; a regional Area remains bounded_selection.
POST /api/v1/projects/{project_id}/datasets/{dataset_id}/raster/thematic/select
Returns source-correct metrics for a bbox and optional exact Area mask:
- occupied/open hectares, share and valid raster area for binary products;
- estimated inhabitants plus mean/P90 inhabitants per hectare for the 2019 population raster;
- mean, P10, median and P90 source score for node value and service level.
The response names estimate status, aggregation method, source unit, observation year, attribution, unsupported metrics and product limitation. Current register population, live public-transport availability and causal interpretations are not produced.
GET /api/v1/projects/{project_id}/datasets/{dataset_id}/raster/thematic/image
Returns a constrained transparent PNG generated from the persisted governed raster. It accepts neither an arbitrary file path nor a provider URL and feeds the existing MapLibre image-overlay path.
GET /api/v1/projects/{project_id}/datasets/walous/products
Returns the fixed official WALOUS 2018/2020/2023 registry. Each product reports its
observation year, EPSG:3812 source contract, 1 m source semantics, configured
state, attribution, licence and documented edition accuracy. configured
becomes true only when the checksum-validated source GeoTIFF exists below
WALOUS_SOURCE_DIR; the endpoint never downloads an archive.
POST /api/v1/projects/{project_id}/datasets/walous/acquire
Reads a bounded window from one provisioned official 1 m WALOUS source,
applies nearest-neighbour resampling to the configured analysis resolution,
masks bbox intersect Area, validates the official non-contiguous class-code
set 1, 2, 3, 4, 5, 6, 7, 8, 9, 80, 90 and persists a normal raster Dataset
through DatasetService. URLs, paths, classes and resolutions are not
caller-controlled. The 2018 source retains official stacked two-digit values;
GeoIntel applies the published Classe vue mapping and explicitly groups the
2018-only greenhouse class 62 with artificial constructions. Source value
0 is treated only as implicit background/nodata. Equal spatial
requests for 2018, 2020 and 2023 share one temporal
series key. The exact official observation ranges are retained as
2018-01-01/2018-12-31, 2020-04-01/2020-04-24 and
2023-05-27/2023-06-25.
POST /api/v1/projects/{project_id}/datasets/{dataset_id}/raster/walous/select
Returns mapped hectares for total observed land cover, trees/forest, surface water, artificial cover, annual and permanent herbaceous cover and bare soil. Values are cell-area estimates from the persisted derived raster. The response explicitly rejects legal land use, ownership, tree count, timber volume and water volume as unsupported interpretations.
GET /api/v1/projects/{project_id}/datasets/{dataset_id}/raster/walous/image
Returns a transparent PNG using the governed 11-class colour table and only the persisted Dataset geometry. It never proxies the source archive.
The Map workbench acquires every configured comparable WALOUS observation for the same Walloon selection when current land cover is first requested. The latest edition drives the current result; the ordinary temporal comparison API then compares 2018, 2020 and 2023 semantic area metrics. The response retains the earlier 2018 methodology and crosswalk limitation; raster evolution does not claim individual object additions or removals.
GET /api/v1/projects/{project_id}/datasets/spw-terrain/products
Returns the governed official SPW 1 m MNT 2021-2022 product and its actual
runtime provisioning state. The source is EPSG:3812; its vertical reference is
DNG / EPSG:5710. configured becomes true only after the checksum-validated
GeoTIFF exists below SPW_TERRAIN_SOURCE_DIR.
POST /api/v1/projects/{project_id}/datasets/spw-terrain/acquire
Reads only bbox intersect Area from the operator-provisioned official MNT,
validates the one-band EPSG:3812/1 m source contract and plausible terrain
values, bilinearly resamples the bounded derivative to 1-10 m and persists it
through DatasetService. The request cannot choose a URL or file path. Source,
archive and derived checksums, acquisition range, CRS, DNG datum, exact bounds,
resolution and interpolation limitations remain provenance. The ordinary
persisted terrain selection and PNG endpoints then analyze/render this Dataset.
GET /api/v1/projects/{project_id}/datasets
List datasets.
GET /api/v1/projects/{project_id}/datasets/source-freshness
Returns one read-only, canonical source-governance report for all persisted
project datasets. Datasets are grouped by source_name (falling back to
source) and classified as a rolling snapshot, annual release, fixed edition,
scenario, historical archive or local artifact.
Each source item reports dataset/version counts, latest import and observation
evidence, latest source version, next review date where meaningful, historical
series availability and local integrity counts for missing DatasetVersions,
checksum mismatches, missing storage files and size mismatches. Status is one
of current, due, review_required or local.
This endpoint never contacts an external provider, downloads data, mutates a
Dataset or silently refreshes a publication. Fixed editions and scenarios are
not marked stale merely because their source date is old. Unknown sources are
review_required until an explicit publication policy is defined.
GET /api/v1/projects/{project_id}/datasets/source-catalog-probes
Performs an explicit, read-only release probe for the allowlisted GRB WFS,
most-recent orthophoto WMS, Statbel population DCAT catalog and ALZ
agricultural-use parcel publication page.
For OGC services, the endpoint reads bounded GetCapabilities responses,
confirms the expected layers and follows only HTTPS ISO 19139 GetRecordById
links on metadata.vlaanderen.be. For Statbel, it reads the bounded official
RDF/Turtle catalog and selects the latest uniquely identified Dutch
Bevolking per statistische sector release. It requires the official landing
page, CC BY 4.0 license and allowlisted distribution identities but never
requests a ZIP or XLSX file. For ALZ, it reads only the bounded official
HTML release page and accepts only exact
www.landbouwvlaanderen.be/bestanden/gis/agpa_<year>_<date>_public.zip link
identities. It does not request those archives. Query parameter refresh=true
bypasses the short in-memory response cache.
Each provider item returns service reachability, matched/missing layers, the
official metadata identifier, title, edition, publication/metadata dates,
local source_version and one comparison status: same, different,
not_comparable, no_local_data or unavailable. Provider status is
available, degraded, unavailable or disabled. A difference means only
that an operator should review provenance; it is not an update instruction.
ALZ comparisons use only the latest definitive third snapshot, normalized as
<campaign>-v3. A newer first or second snapshot is reported in the item title
and message as provisional but cannot mark a local definitive historical
edition as outdated. service_type=HTML uses definitive_archive and
current_snapshot as evidence markers in the existing expected/matched/missing
arrays; no parallel response shape is introduced.
Statbel comparisons use the four-digit population reference year. The 2025
release requires the new REDEGEO TXT/ZIP variant; its old-sector-layout file is
reported only as transition evidence. service_type=DCAT uses
population_txt_current, landing_page and cc_by_4_0 as evidence markers.
A newer statistical-sector geometry edition is not interpreted as a newer
population release.
Future Statbel execution remains outside the HTTP request cycle in
scripts/manage_statbel_population_release.py. It reuses this read-only
catalog response for plan, then separates filesystem-only stage, named
human review and checksum-confirmed apply. No additional public endpoint
is introduced. Apply delegates to the existing dataset upload contract and
creates a new immutable annual snapshot only after all evidence is unchanged.
Future definitive ALZ execution likewise remains outside the HTTP request
cycle in scripts/manage_alz_agriculture_release.py. It derives the exact
agpa_<campaign>_<publication-date>_public.zip identity from the existing
catalog response and accepts only YYYY-v3. stage is filesystem-only,
review is a named approval and apply requires exact plan/review hashes,
revalidates the current catalog and delegates to the existing Dataset upload
contract. No ALZ release endpoint, background task or provider URL parameter
is added; v1/v2 campaign snapshots remain non-importable.
Current-orthophoto release preflight also remains outside the HTTP request
cycle in scripts/orthophoto_release_preflight.py. It composes the existing
product-registry and source-catalog envelopes with allowlisted WMS
GetCapabilities, WCS DescribeCoverage and bounded Vliegdagcontour
GetFeatureInfo evidence for one 128-1,024 m EPSG:4326 selection. The result
reports product variant, official YYYY.NN edition, exact raster-domain
containment, sampled flight dates/years, local comparison state and
staging_permitted. It performs no pixel request, upload, Job, Dataset write
or legacy metadata rewrite. This operator script adds no public API contract.
Governed pixel promotion remains outside the HTTP request cycle in
scripts/manage_orthophoto_release.py. plan reruns that read-only preflight;
stage makes exactly one allowlisted bounded Ortho GetMap request and writes
only checksummed source/raster/preview evidence; review requires a named
approval; and apply requires the exact plan/review SHA-256 values. Apply
revalidates the remote identity and local comparison state, then delegates to
the existing POST .../datasets/upload contract with dataset_type=raster,
source_name=digitaal_vlaanderen_orthophoto, the official YYYY.NN
source_version and complete source/provenance metadata. No release endpoint,
provider URL parameter, Job type or alternate response envelope is added.
The endpoint accepts no arbitrary URL, feature query, area or layer. It does
not fetch vector features, raster pixels or models, create jobs/datasets, write
to PostGIS or trigger an import. The normal source-freshness endpoint remains
local-only and never invokes this probe implicitly.
GET /api/v1/projects/{project_id}/datasets/grb-refresh-plan
Builds a read-only refresh decision for the governed
kempen-transport-region GRB snapshot series. Query parameter
refresh_catalog=true explicitly bypasses the catalog cache. The response
maps the official dated edition to buildings, roads, water and parcels
and reports each latest local Dataset, source version, observation date,
feature count, artifact size and refresh state.
Layer state is current, update_available, not_loaded, review_required
or remote_unavailable. The summary reports how many new immutable Datasets
would be created and how many existing snapshots remain retained. The endpoint
does not fetch GRB features, stage files, create a Job, write to PostGIS or
start an operator process. Exact remote deltas remain unavailable until every
municipality partition has been staged and validated.
Regional execution uses scripts/manage_grb_refresh.py outside the request
cycle. stage requires the exact official ISO edition, invokes the existing
regional GRB operators with --fetch-only, validates all artifact/partition
checksums and emits a SHA-256-bound plan. apply requires that exact plan hash,
revalidates every staged byte and delegates persistence to DatasetService and
VectorFeatureService. It creates new temporal snapshots and never deletes or
overwrites an older Dataset.
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:
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...
GET /api/v1/projects/{project_id}/datasets/{dataset_id}/raster/image
Return a persisted orthophoto Dataset as a bounded browser-safe PNG. This is an explicit binary non-envelope endpoint used by the MapLibre image source. It accepts only ready datasets from the governed orthophoto provider and never reads arbitrary filesystem paths.
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; defaultnearest)output_name
Returns a job payload with derived dataset id in result.output_dataset_id.
Failure modes:
- code:
INVALID_PARAMETERSfor bad CRS or resampling - code:
INVALID_DATASET_CRSwhen source raster CRS is missing - code:
RASTER_PROCESSING_UNAVAILABLEwhen 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_PARAMETERSfor non-positive/non-integer band indices - code:
INVALID_PARAMETERSfor band index outside source band count - code:
INVALID_DATASET_TYPEwhen source is not raster - code:
RASTER_PROCESSING_UNAVAILABLEwhen 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_PARAMETERSfor non-positive/non-integer band indices - code:
INVALID_PARAMETERSfor band index outside source band count - code:
INVALID_DATASET_TYPEwhen source is not raster - code:
RASTER_PROCESSING_UNAVAILABLEwhen 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_PARAMETERSfor non-positive/non-integer band indices - code:
INVALID_PARAMETERSfor band index outside source band count - code:
INVALID_DATASET_TYPEwhen source is not raster - code:
RASTER_PROCESSING_UNAVAILABLEwhen 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.
GET /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.
Detection and segmentation result windows
GET /detection/runs/{id}/detections, /detection/datasets/{id}/detections
and their segmentation equivalents accept limit (default 2.000, 0 for
everything) and offset, and return total, limit, offset and
truncated. total always describes the complete population; items is one
page of it.
The /geojson siblings accept limit and report the window in a
geointel_result_window foreign member. Rows are ordered by confidence, so a
capped overlay draws the strongest detections rather than an arbitrary slice.
A regional run holds tens of thousands of detections, and these are the endpoints the results table and the map overlay call after every run; they previously returned all of them.
GET /api/v1/projects/{project_id}/quality-checks/{id}/evidence/geojson
Returns the reviewable geometry behind one quality check: the objects the model missed, the ones it found without a reference, and the confirmed matches.
The overlay is capped by limit (default 5.000, 0 returns everything). A
regional check emitted one feature per false positive, one per false negative
and two per match with no bound at all, so a run of 40k detections against
45k footprints produced well over a hundred thousand features in one response —
the endpoint the whole review workflow depends on stopped working exactly where
review matters most.
What to draw is decided before any geometry is fetched, so the database work is proportional to what is returned rather than to the size of the check. The budget is split between misses and false positives in proportion to their populations, with at least one of each: strict priority would mean a check with 50.000 misses and three false positives never showed one. Confirmations fill what is left, and a match is kept or dropped as a candidate/reference pair because half a match is not reviewable evidence.
total_feature_count and role_counts describe the complete population,
truncated says whether the cap applied, and unresolvable identifiers are
summarised in one warning rather than one per identifier.
Export provenance
Every exported GeoJSON carries a geointel_provenance foreign member on the
FeatureCollection. RFC 7946 requires parsers to ignore members they do not
know, so QGIS and ogr2ogr read the file normally.
It names the source, the dataset and its source_version/observed_at, the
selection the export was taken from, and — most importantly — whether the file
is complete. A capped selection export previously recorded truncated on the
export record only: the downloaded file looked whole, and an operator opening
250 of 1.400 buildings in QGIS had nothing to tell them so. Completeness is
derived from the counts as well as the flag, so a caller that forgets to pass
it cannot produce a file that claims to hold everything.
POST /api/v1/projects/{project_id}/datasets/{dataset_id}/vector/select
Selection metrics describe two different populations and now say so.
intersection_area and intersection_length clip each feature to the
selection, while the object count treats any feature touching the selection as
whole — which is what an operator expects from "objecten", but overstates the
count along every edge. The response therefore adds
fully_covered_feature_count, partially_covered_feature_count and
selection_edge_warning, and marks the count metric as an estimate whenever
the selection cuts features. Area and length metrics stay exact and do not
inherit that caveat.
Partitioned selection (/datasets/vector/partitions/select) de-duplicates on
source_feature_id across municipal partitions for the returned geometry as
well as for the count. A feature on a shared boundary was previously counted
once but drawn once per partition.
Read-only spatial selection over persisted vector_features.
Request:
{
"bbox": {
"min_x": 5.0,
"min_y": 51.0,
"max_x": 5.1,
"max_y": 51.1,
"crs": "EPSG:4326"
},
"area_id": "optional persisted area UUID",
"limit": 250
}
Response:
{
"data": {
"selection_bbox": {
"min_x": 5.0,
"min_y": 51.0,
"max_x": 5.1,
"max_y": 51.1,
"crs": "EPSG:4326"
},
"selection_area_id": "present when area_id was requested",
"feature_count": 2,
"total_feature_count": 2,
"limit": 250,
"truncated": false,
"geojson": {
"type": "FeatureCollection",
"features": []
},
"summary": {
"metric_label": "Wateroppervlakte",
"metric_value": 5.25,
"metric_unit": "ha",
"aggregation_method": "intersection_area",
"primary_metric_key": "water_area",
"feature_count": 23,
"is_estimate": false,
"warning": "Watervolume is niet berekenbaar zonder betrouwbare diepte- of bathymetrische gegevens.",
"metrics": [
{
"metric_key": "water_area",
"metric_label": "Wateroppervlakte",
"metric_value": 5.25,
"metric_unit": "ha",
"aggregation_method": "intersection_area",
"is_estimate": false
},
{
"metric_key": "watercourse_length",
"metric_label": "Lengte waterlopen",
"metric_value": 12.75,
"metric_unit": "km",
"aggregation_method": "intersection_length",
"is_estimate": false
},
{
"metric_key": "feature_count",
"metric_label": "Waterobjecten",
"metric_value": 23,
"metric_unit": "objecten",
"aggregation_method": "feature_count",
"is_estimate": false
}
]
}
}
}
Rules:
- Only vector/GeoJSON datasets are supported.
- Coordinates are EPSG:4326 longitude/latitude.
area_idis optional and must belong to the route project. When present, PostGIS filtering and configured aggregations usebbox ∩ Area. A bbox that encloses the complete Area therefore produces the exact full-work-area result, while a drawn rectangle that crosses a municipality boundary is clipped to that official boundary.- Results are generated from persisted PostGIS
vector_features, not from client-side map data. feature_countis the number of GeoJSON features returned in the bounded preview.total_feature_countis the exact number of persisted rows intersecting the requested bbox or persisted Area geometry.summarykeeps one backwards-compatible primary metric and exposes all relevant measurements inmetrics. Known themes use metric PostGIS calculations: building/forest/water/parcel surfaces in hectares, road and watercourse lengths in kilometres, population in inhabitants and intersecting feature counts as supporting evidence.- Governed datasets may declare additional
source_metadata.selection_metrics. Each metric may constrain one persisted feature-property to an explicit value allowlist before the same PostGIS aggregation runs. The BWK/Natura 2000 dataset uses this only for officialEVALclasses; it does not collapse mixed classes into a made-up score. - Area and length calculations transform geometry to Belgian Lambert 72 (
EPSG:31370); they are never calculated in geographic degrees. - Water volume is not inferred from 2D GRB geometry. It remains unavailable until a source provides reliable depth or bathymetry with compatible spatial coverage and provenance.
- The response is capped by
limitand returnstruncated=truewhentotal_feature_countexceeds the returned preview. limitis bounded to1..1000. Municipality-scale clients must page spatially by viewport instead of requesting an unbounded municipality FeatureCollection.- The Map workspace uses this existing endpoint for vector datasets above 5,000 features. It starts delivery at zoom level 14, debounces
moveendrequests and explicitly reportstruncated=trueas a request to zoom further in. This is a client delivery policy, not a second API or persistence path.
POST /api/v1/projects/{project_id}/datasets/vector/partitions/select
Combines 1..16 bounded vector acquisitions of one governed source product
into one read-only selection result. The request uses the same bbox, optional
area_id and limit contract as vector selection plus dataset_ids.
The backend rejects mixed projects, non-ready vector datasets and partitions
whose source_name or product_key differs. PostGIS calculates area and length
metrics across all persisted tile geometries. total_feature_count is
deduplicated by provider source_feature_id where available so a source object
crossing a tile edge is not presented as two objects. The response includes
partition_count, source_name and the exact dataset_ids used.
The Map workbench uses this route only for regional selections up to 50 by 50 kilometres. Provider calls remain individually bounded below 20 kilometres; larger overview selections do not fan out into unbounded high-resolution downloads.
POST /api/v1/projects/{project_id}/datasets/{dataset_id}/vector/select/derive
Persists a bbox selection as a new derived vector dataset and indexes the
selected output into vector_features.
Request:
{
"bbox": {
"min_x": 5.0,
"min_y": 51.0,
"max_x": 5.1,
"max_y": 51.1,
"crs": "EPSG:4326"
},
"limit": 250,
"output_name": "selected-buildings"
}
Response: DatasetRead in the canonical API envelope.
Rules:
- Only vector/GeoJSON datasets are supported.
- Coordinates are EPSG:4326 longitude/latitude.
- The new dataset uses
dataset_role="derived",source="operation:selection",source_name="map_selection"andderived_from_dataset_idpointing to the source dataset. - The persisted GeoJSON properties retain source provenance as
source_dataset_idandsource_vector_feature_id. - Empty selections return
VECTOR_OPERATION_EMPTY_RESULTand do not create a dataset.
GET /api/v1/projects/{project_id}/datasets/{dataset_id}/content
Returns stored vector dataset content through the canonical API envelope. Vector content is returned as GeoJSON/JSON payload data. Raster content is not served through this endpoint.
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
The legacy Sprint 7 provider registry remains the exact
grb|osm|manual|fixture import abstraction. National and maritime source
authority is exposed separately so adding official source families cannot
silently change that import contract.
GET /api/v1/external/coverage/catalog
Returns the normalized Belgium and Belgian North Sea theme vocabulary, legal coverage zones, allowed availability states and audited source contracts. Every source includes authority, native layers, geometry types, acquisition mode, source URL, attribution, licence and limitation text.
Allowed result states are exactly:
operational: one matchingreadyDataset or the spatial union of governed partition Datasets covers the complete selection;partial: the governed integration exists but matching project data is absent or incomplete;not_configured: an audited source has no operational adapter;unsupported: no audited contract supports the theme in that zone.
POST /api/v1/external/coverage/resolve
Resolves a user-drawn EPSG:4326 bbox against persisted national and maritime Areas. Cross-region and land/sea selections remain split by zone.
{
"project_id": "uuid",
"bbox": {
"minx": 2.65,
"miny": 51.05,
"maxx": 2.85,
"maxy": 51.20
},
"themes": ["buildings", "surface_water", "bathymetry"]
}
The response returns intersected_zones, outside_supported_scope, one item
per zone/theme combination, matching source names and IDs of actually
materialized Datasets. Every materialized item also carries evidence for
authority, source version, observation/publication time, CRS, resolution,
coverage bbox, attribution, licence and checksum where persisted. An empty themes list requests the complete normalized
vocabulary. Unknown themes fail with COVERAGE_THEME_UNSUPPORTED.
The Map workbench uses this response before a rectangle analysis. It queries
matching persisted Datasets and may call the already documented bounded
acquisition endpoints for applicable partial source contracts. Those calls
run through a client queue with at most three concurrent acquisitions. An
unsupported or not_configured theme is not rendered as a failed
measurement; an attempted provider failure is reported explicitly.
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
Compatibility contract for provider-specific import flows. It never writes datasets itself.
Request:
{
"project_id": "uuid-or-local-id",
"area_id": "optional uuid-or-local-id",
"layers": ["buildings"],
"dataset_role": "optional source|reference"
}
GRB response:
{
"provider_name": "grb",
"status": "bounded_request_required",
"message": "Use the governed project GRB acquisition endpoint with an EPSG:4326 bounding box and one supported layer.",
"requested_layers": ["buildings"],
"dataset_id": null,
"dataset_role": "reference",
"source_name": "grb"
}
OSM still returns not_configured. Manual and fixture providers point callers
to existing upload/fixture flows. No provider writes directly to
vector_features; the bounded GRB integration and all future provider output
flow through DatasetService and VectorFeatureService.
External data fetchers
POST /api/v1/external/osm/fetch
Request:
{
"project_id": "uuid",
"area_id": "uuid",
"layers": ["buildings", "roads", "water", "green"]
}
POST /api/v1/external/grb/fetch
Request:
{
"project_id": "uuid",
"area_id": "uuid",
"layers": ["buildings"]
}
This legacy compatibility endpoint never fetches. It returns
bounded_request_required and directs callers to
POST /api/v1/projects/{project_id}/datasets/grb/acquire, where bbox, Area,
product allowlist, limits and persistence are enforceable.
Sprint 7B provider contract responses expose capabilities only. Providers must report:
{
"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 unbounded GRB WFS download or OSM Overpass integration is implemented. Bounded GRB OGC API Features acquisition is documented under Datasets.
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:
{
"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.
Guided browser orchestration
The current frontend offers one guided building-analysis action, but does not add a parallel backend workflow endpoint. It deliberately composes the canonical contracts in this order:
- optional explicit
POST /api/v1/projects/{project_id}/datasets/uploadfor a georeferenced GeoTIFF; POST /api/v1/projects/{project_id}/datasets/{dataset_id}/raster/tilewith 512 px tiles and 64 px overlap;GET /api/v1/detection/yolo/preflightwith the returned manifest and selected local model asset;POST /api/v1/detection/runonly after successful preflight;- persisted run, Detection list and Detection GeoJSON reads;
- optional persisted reference QA through the existing detection QA endpoint.
The strict POST /api/v1/detection/run contract still requires tile_manifest_path for configured YOLO. The frontend does not create fake tiles, bypass tile limits, fetch external imagery or download model weights.
GET /api/v1/detection/models
Returns object-detection model capability descriptors.
Each descriptor exposes machine-readable training_scope, validation_scope,
validated_regions, nationally_validated and operator_review_required
fields. The configured local YOLO capability remains
nationally_validated=false: the runtime binds only the existing Mol/Kempen
operator evidence and cannot be promoted to a Belgian national claim by a UI
label or model filename.
{
"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
}
]
}
GET /api/v1/detection/model-assets
Returns governed local runtime model files. This is a read-only catalog. GeoIntel never downloads, creates, mutates or deletes model weights from this endpoint.
The backend scans YOLO_MODELS_DIR (default /app/models). When
YOLO_MODEL_PATH resolves to an existing file, production catalog output is
restricted to that explicitly approved active model. When no active model is
configured, supported .pt, .onnx and .engine files remain visible for
development/operator discovery but cannot make the configured detector ready.
Response data:
{
"items": [
{
"model_asset_id": "building-detector-pt",
"filename": "building-detector.pt",
"display_name": "building-detector",
"model_path": "/app/models/building-detector.pt",
"suffix": ".pt",
"framework": "ultralytics/pytorch",
"task_type": "object_detection",
"size_bytes": 123456,
"sha256": "sha256hex",
"active": true,
"status": "approved",
"limitation_message": "Approved local runtime model asset. GeoIntel will not download or mutate model weights.",
"will_download_models": false
}
],
"total": 1,
"model_directory": "/app/models"
}
GET /api/v1/detection/yolo/preflight
Returns a canonical envelope with read-only configured-YOLO runtime preflight state. Optional query parameters:
tile_manifest_path: existing raster tile manifest path to validate.model_asset_id: optional local model asset ID fromGET /api/v1/detection/model-assets; when supplied, preflight validates that asset path instead of the defaultYOLO_MODEL_PATH.check_model_load: defaultfalse; whentrue, explicitly loads only the configured local model file for compatibility smoke. It never downloads weights and never runs inference.
Response data:
{
"model_id": "yolo-configured",
"model_asset_id": null,
"model_path": null,
"tile_manifest_path": null,
"status": "not_configured",
"message": "YOLO_MODEL_PATH is not set. GeoIntel will not download model weights automatically.",
"checks": {
"enabled": true,
"dependencies_available": true,
"accelerator_ready": true,
"model_path_set": false,
"model_file_exists": null,
"model_load_requested": false,
"model_load_ok": null,
"manifest_path_set": null,
"manifest_valid": null,
"tile_paths_exist": null,
"tile_limit_ok": null
},
"runtime": {
"dependencies_assumed": false,
"model_directory": null,
"yolo_config_dir": "/app/storage/ultralytics",
"torch_version": "2.12.1",
"ultralytics_version": "8.4.88",
"cuda_available": true,
"configured_device": "cuda:0",
"cuda_required": true
},
"tile_count": 0,
"max_tiles": 100,
"will_download_models": false,
"will_run_inference": false
}
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. When YOLO_REQUIRE_CUDA=true, missing CUDA or a CPU device selection fails closed with DETECTION_ACCELERATOR_UNAVAILABLE or DETECTION_ACCELERATOR_MISCONFIGURED; production inference never silently falls back to CPU.
The configured model exposes only classes declared by YOLO_MODEL_CLASSES;
the current promoted server detector is building-only. Unsupported class
filters fail with DETECTION_CLASS_NOT_VALIDATED. With production scope
enforcement enabled, the raster must belong to a persisted Area matching the
configured validated Mol/Kempen evidence or inference fails with
DETECTION_VALIDATION_SCOPE_UNAVAILABLE.
Request:
{
"project_id": "uuid",
"dataset_id": "uuid",
"model_id": "yolo-placeholder",
"model_asset_id": null,
"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=trueYOLO_MODEL_PATHpointing to an existing local model file- backend optional AI dependencies installed with
geointel-backend[ai] tile_manifest_pathpointing to an existing raster tile manifest generated by the raster tile operation
model_asset_id may be supplied with model_id: "yolo-configured" to select a
specific local model file from the read-only model asset catalog. The backend
resolves the ID to a file inside the configured model directory and persists the
asset ID, path and SHA-256 in the job and analysis-run parameters for
reproducibility. Clients must not submit arbitrary model paths.
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.
The manifest path, and every tile path inside it, must resolve under
STORAGE_ROOT. The path arrives in the request and a manifest entry may name an
absolute tile path, so without that check the field is an unbounded reference to
the host filesystem — and a file outside the root is by definition not the
governed, runtime-produced artifact the persistence model requires. Rejection is
STORAGE_PATH_OUTSIDE_ROOT; GEOINTEL_ALLOW_EXTERNAL_ARTIFACT_PATHS opts out
for provisioning workflows that stage tiles before ingest.
The manifest must carry explicit CRS metadata (crs, source_crs or
dataset_crs). A manifest without it fails with
DETECTION_TILE_MANIFEST_INVALID rather than being georeferenced against an
assumed EPSG:4326, which would place detections plausibly but wrongly.
Tiles are read with rasterio: the visible RGB bands are selected explicitly and
percentile-stretched to 8-bit, so 16-bit and 4-band (RGB + NIR) orthophotos
reach the model as the kind of image it was trained on. Tiles are predicted in
batches of YOLO_BATCH_SIZE.
Post-processing removes two artefacts of tiled inference:
- boxes truncated by an interior tile edge are dropped, because the
overlapping neighbouring tile observed the same object completely
(
YOLO_SUPPRESS_TILE_EDGE_DETECTIONS, default on). Boxes against the outer raster edge are kept; - duplicates are suppressed on IoU and on intersection-over-smaller-area, so an object wider than the tile overlap does not survive as two partial boxes.
result_json reports raw_detection_count, suppressed_detection_count,
tile_edge_truncated_count, duplicate_iou_threshold and
containment_suppression_threshold.
POST /api/v1/detection/run-async
Same request body as POST /api/v1/detection/run, but queues the run instead of
executing it inside the request, and returns a JobRead. Tiled GPU inference
over up to YOLO_MAX_TILES tiles takes minutes; performing it in the request
holds a worker thread and times the client out. Cheap validation (project,
dataset, dataset type) still happens synchronously, so an invalid request is
rejected immediately rather than by a job that fails minutes later.
Queued jobs are executed by the background analysis worker
(GEOINTEL_ANALYSIS_WORKER_ENABLED, poll interval
GEOINTEL_ANALYSIS_WORKER_POLL_SECONDS), which claims a job before dispatching
it so the same run is never started twice. Poll GET /api/v1/jobs/{id} for
progress. POST /api/v1/segmentation/run-async behaves identically.
Unavailable model response:
{
"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_TYPEwhen the dataset is not raster.DETECTION_MODEL_NOT_FOUNDwhen the model id is unknown.DETECTION_MODEL_ASSET_NOT_FOUNDwhenmodel_asset_idis not present in the configured model directory.FIXTURE_MODE_REQUIREDwhenmanual-fixture-detectoris requested withoutparameters_json.fixture_mode=true.DETECTION_TILE_MANIFEST_REQUIREDwhenyolo-configuredis requested withouttile_manifest_path.DETECTION_TILE_MANIFEST_NOT_FOUNDwhen the provided manifest path does not exist.DETECTION_TILE_MANIFEST_INVALIDwhen the manifest cannot be parsed or lacks tile metadata.DETECTION_TILE_LIMIT_EXCEEDEDwhen the manifest exceedsYOLO_MAX_TILES.- Configured YOLO inference forwards
YOLO_MAX_DETECTIONSto Ultralyticsmax_detand defaults to1000so dense building AOIs are not silently limited by the upstream default of 300 detections before persisted QA/QC. - Configured YOLO applies cross-tile duplicate suppression after pixel boxes are
converted to EPSG:4326 geometries and before
Detectionrows are persisted. Same-class candidates are confidence-sorted and lower-confidence candidates with geometry IoU greater than or equal toYOLO_DUPLICATE_IOU_THRESHOLDare suppressed. The default is0.5;0disables this GeoIntel-side post-processing for debugging. DETECTION_DEPENDENCY_UNAVAILABLEwhen YOLO dependencies are not installed.DETECTION_MODEL_LOAD_FAILEDwhen 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_idclass_namemin_confidence
GET /api/v1/detection/datasets/{dataset_id}/detections
Returns persisted detections for a raster dataset. Optional filters:
analysis_run_idclass_namemin_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_idclass_nameconfidencemodel_namemodel_versionanalysis_run_iddataset_idjob_idsource_tile_pathbbox_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.
GET /api/v1/projects/{project_id}/quality-checks/{id}/reviews
Returns the evidence queue plus a summary, which now carries
reviewed_metrics: the score with the operator's verdicts applied, next to the
raw one.
The review vocabulary already separates a model error from a reference gap, because official footprints are not automatically perfect ground truth. Those verdicts were only counted, so an operator who established that twelve of forty false positives are buildings the reference simply lacks still saw a precision counting all forty against the model — a number they had personally disproved.
reference_gap_or_changeandqa_alignment_mismatchexonerate a finding: the detection, or the missing detection, was not the model's error.confirmed_model_false_positive/confirmed_model_false_negativekeep it.uncertainandimagery_obscured_or_uncertainalso keep it. Being unable to judge is not evidence in the model's favour, and treating it as such is how a score drifts upward unearned. A decision from a later release the runtime does not recognise is likewise treated as no judgement.
Because part of the evidence is usually unreviewed, the result is an interval:
pessimistic assumes every unreviewed finding is a model error, optimistic
assumes none is, and adjudicated equals the pessimistic reading so a partly
reviewed check never presents as a settled one. review_complete says whether
the interval has collapsed.
POST /api/v1/detection/runs/compare
Scores several persisted runs against one reference and ranks them on average precision.
The workbench ranks model variants by a stored F1, each measured at that variant's own confidence threshold — a figure that says as much about the threshold as about the model, so a conservatively calibrated detector looks worse than a liberal one without detecting anything differently. Average precision describes the whole ranking the model produced. The F1 at each run's own threshold stays in the response next to it, so the difference between the two readings is auditable rather than hidden.
Comparability is reported before any ranking. Runs over different source
rasters, scored against different references, without a proven inference
footprint, or covering a different evaluated population are not alternatives to
one another, and comparability.blocking_reasons names which of those applies.
The numbers are still returned — they are simply not a ranking.
Each run is scored through the same QA path the workbench uses, so a comparison and the persisted quality checks cannot drift apart.
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.
The map-driven building workflow immediately loads the persisted evidence from the resulting quality check. For current Flemish building validation it labels strict matches as GRB-confirmed, unmatched detections as unconfirmed AI-only proposals and unmatched reference footprints as official GRB buildings missed by AI. AI-only proposals are never promoted to authoritative building results.
Request:
{
"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:
precisionrecallf1_scoremean_ioufalse_positivesfalse_negativesquality_check_id
Configured-YOLO QA automatically reads tile_manifest_path from the persisted
AnalysisRun.parameters_json. Candidate and reference geometries are clipped
to the union of the manifest's tile bounds after explicit CRS transformation to
EPSG:4326. Before reference geometries are materialized, the service applies
that coverage with an indexed PostGIS ST_Intersects predicate. The full
dataset count is retained separately so raw/evaluated/excluded counts remain
auditable without transferring a regional reference dataset to Python. The
response additionally returns:
candidate_feature_count_rawandreference_feature_count_raw;coverage, including raw/evaluated/excluded/boundary-clipped population counts, tile count, source CRS values and coverage mode;box_to_footprint_diagnostics, which compares candidate boxes with reference envelopes at the same IoU threshold.
The canonical precision, recall, F1 and mean IoU always remain based on
candidate geometry versus the persisted reference footprint. Envelope results
are explicitly diagnostic_only and are persisted in
quality_checks.findings_json; they never replace or inflate canonical metrics.
box_to_footprint_diagnostics.candidate_geometry_mode reports whether the
candidates are axis_aligned_boxes or footprint_polygons. For a box detector
the strict footprint IoU has a ceiling below 1 on rotated or non-rectangular
buildings, and the response says so in warnings and in interpretation.
Matching is deterministic. Candidates are ranked by confidence, highest first,
with feature identity as tiebreaker, before the greedy IoU assignment. Database
row order is not usable for this: every detection in a run shares one
transaction timestamp, so ordering by created_at left the assignment — and
therefore the score and the false-positive evidence shown to a reviewer —
undefined between identical runs.
calibration_thresholds asks for named confidence cuts alongside the run's own
operating point. They are read off the same matching pass, so a sweep costs no
extra inference at all. Each row reports the tally, precision, recall and F1 at
that cut, and best_f1_in_sweep marks the F1-optimal one.
This replaces re-running the model once per threshold. Detections above a higher cut are a subset of a lower-cut run, and duplicate suppression walks candidates in descending confidence, so a lower-confidence box can never displace a higher-confidence one: the kept set above any cut is identical whichever threshold the run itself used. Three thresholds therefore cost one GPU pass rather than three, and produce the same numbers.
The response also returns precision_recall_curve: precision, recall and F1 at
every confidence value present in the run, plus average_precision, best_f1
and best_f1_threshold. A single F1 describes one operating point and cannot
compare two models whose calibration differs; the curve can. average_precision,
best_f1 and best_f1_threshold are persisted as metrics rows alongside the
existing ones.
Segmentation QA (POST /api/v1/segmentation/runs/{analysis_run_id}/qa/reference)
applies the same tile-coverage clipping and returns the same coverage block.
Without it, every reference feature outside the inferred tiles counted as a
false negative and recall was understated by an arbitrary amount.
Configured-YOLO QA fails closed with DETECTION_QA_COVERAGE_UNAVAILABLE when
manifest provenance is absent, DETECTION_QA_COVERAGE_MISMATCH when it belongs
to another raster, DETECTION_QA_COVERAGE_INVALID when bounds/CRS are invalid,
or REFERENCE_FEATURES_OUTSIDE_COVERAGE when no reference polygons overlap the
actual inference coverage. Explicit fixture/legacy runs without a manifest keep
the documented unbounded comparison behavior.
If the reference dataset has no persisted vector features, the endpoint returns REFERENCE_FEATURES_NOT_FOUND. It does not calculate fake QA metrics.
Future analysis route: /api/v1/analysis/building-stats
Not implemented in the active API surface. Future input is expected to combine an area with a vector building layer.
Future analysis route: /api/v1/analysis/object-detection
Request:
{
"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.
Future analysis route: /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_configuredfixture-segmenter: configured for explicit test/demo fixtures onlyyolo-seg-configured:not_configuredsam-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:
{
"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_TYPEwhen the dataset is not raster.SEGMENTATION_MODEL_NOT_FOUNDwhen the model id is unknown.FIXTURE_MODE_REQUIREDwhenfixture-segmenteris requested withoutparameters_json.fixture_mode=true.INVALID_FIXTURE_SEGMENTATIONSwhen fixture payloads are not a list.INVALID_FIXTURE_GEOMETRYwhen 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_idclass_namemin_confidence
GET /api/v1/segmentation/datasets/{dataset_id}/segmentations
Returns persisted segmentation records for a raster dataset. Optional filters:
analysis_run_idclass_namemin_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_idclass_nameconfidencearea_m2model_namemodel_versionanalysis_run_iddataset_idjob_idsource_tile_pathtile_indexmask_pathbbox_jsonprovenance_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:
{
"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
Compares two persisted vector datasets in the same project and returns a synchronous job envelope. This is a lightweight V1 foundation for object-level change review, not a temporal run-history engine.
Request:
{
"source_dataset_id": "uuid",
"target_dataset_id": "uuid",
"iou_threshold": 0.8,
"modified_threshold": 0.3,
"include_unchanged": true,
"bbox": {"min_x": 5.0, "min_y": 51.1, "max_x": 5.2, "max_y": 51.3, "crs": "EPSG:4326"},
"area_id": "uuid",
"preview_limit": 2000
}
bbox and area_id bound the comparison to the operator's selection, resolved
the same way every other analysis resolves it: the drawn rectangle intersected
with the named work area. Both populations are loaded through an indexed
ST_Intersects predicate rather than being read into Python in full. Without a
selection the comparison still covers both datasets entire, which is rarely the
question and — with include_unchanged true — previously returned a
FeatureCollection holding both datasets.
Features are not clipped to the selection. A change class describes a whole object, so comparing a clipped earlier footprint against an unclipped later one would manufacture "modified" along the selection edge. Features the edge crosses are compared in full and counted in a warning instead.
modified_threshold separates a redrawn footprint from two distinct objects.
Between it and iou_threshold the change class is modified; below it the
source is removed and the target added. Without that class an extended
building appeared as one removal plus one addition, hiding the category the
analysis exists to show and inflating both counts.
preview_limit caps the returned geometry — modified, added, removed and only
then unchanged — while every count still describes the whole selection.
preview_truncated says whether the cap applied.
Response is a canonical API envelope containing a JobRead payload. On success,
result_json contains:
{
"source_dataset_id": "uuid",
"target_dataset_id": "uuid",
"source_feature_count": 2,
"target_feature_count": 2,
"added_count": 1,
"removed_count": 1,
"unchanged_count": 1,
"iou_threshold": 0.8,
"warnings": [],
"generated_at": "2026-06-16T00:00:00Z",
"geojson": {
"type": "FeatureCollection",
"features": []
}
}
Change detection prefers persisted vector_features. If an older vector dataset
has no persisted vector rows, it falls back to the stored GeoJSON artifact and
adds a warning to result_json.warnings. Supported comparable geometry types are
Polygon and MultiPolygon; point/line geometries return UNSUPPORTED_GEOMETRY.
GeoJSON feature properties include:
change_type:added,removedorunchangedsource_dataset_idtarget_dataset_idsource_feature_idtarget_feature_idiou
Limitations:
- Change detection itself performs no provider fetch. Bounded GRB acquisition is a separate explicit Dataset operation; OSM and Sentinel remain disabled.
- No fake object lifecycle classification.
- No
changedclassification without durable object ids/versioning. - No first-class change table yet; the current output is stored in job
result_jsonand rendered in the frontend map.
QA/QC
POST /api/v1/qa/detections-vs-reference
Request:
{
"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 111 also includes feature-level evidence arrays for map/review handoff:
match_evidence: matched candidate/reference feature ids with IoU.false_positive_evidence: unmatched candidate feature ids.false_negative_evidence: unmatched reference feature ids.
These arrays are derived from the same persisted/source geometries used for IoU matching. They are not separate QA records yet; they are persisted inside quality_checks.findings_json.
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:
{
"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": {
"matches": 1,
"false_positives": 1,
"false_negatives": 1,
"match_evidence": [
{
"candidate_feature_id": "candidate-feature-id",
"reference_feature_id": "reference-feature-id",
"iou": 0.83
}
],
"false_positive_evidence": [
{
"candidate_feature_id": "candidate-extra-id"
}
],
"false_negative_evidence": [
{
"reference_feature_id": "reference-missing-id"
}
]
},
"metrics": [
{
"metric_key": "precision",
"metric_value": 0.5
}
]
}
],
"total": 1,
"limit": 50,
"offset": 0
}
GET /api/v1/projects/{project_id}/quality-checks/{quality_check_id}/evidence/geojson
Returns a canonical envelope containing a read-only QA/QC evidence overlay for a
persisted quality check. The endpoint reads feature ids from
quality_checks.findings_json.match_evidence,
false_positive_evidence and false_negative_evidence, resolves them against
persisted candidate/reference geometries and returns a GeoJSON FeatureCollection.
Supported resolution paths:
- dataset QA candidate/reference geometries from
vector_features; - detection QA candidate geometries from persisted
detections; - segmentation QA candidate geometries from persisted
segmentations; - reference geometries from persisted
vector_features.
Response:
{
"data": {
"quality_check_id": "uuid",
"project_id": "uuid",
"candidate_dataset_id": "uuid-or-null",
"reference_dataset_id": "uuid",
"analysis_run_id": "uuid-or-null",
"feature_count": 4,
"warnings": [],
"geojson": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "match_candidate:feature-id",
"geometry": {},
"properties": {
"qa_evidence_role": "match_candidate",
"quality_check_id": "uuid",
"candidate_feature_id": "candidate-feature-id",
"reference_feature_id": "reference-feature-id",
"iou": 0.83
}
}
]
}
}
}
qa_evidence_role is one of match_candidate, match_reference,
false_positive or false_negative. Missing persisted feature ids are reported
in warnings; no fake geometries are produced.
Detection-backed candidate evidence also exposes provenance read from the
persisted detections row: detection_id, job_id, confidence,
model_name, model_version, source_tile_path and bbox_json. Existing
properties_json fields such as tile_index remain present. Segmentation-backed
candidate evidence exposes the equivalent persisted model/source fields plus
segmentation_id, mask_path and area_m2. These are additive GeoJSON
properties; the canonical envelope and endpoint path are unchanged.
For detection QA, false-positive and false-negative evidence properties also
include review_decision, review_notes, reviewed_by and reviewed_at.
Missing review rows are represented as review_decision=unreviewed. Evidence
resolution is bounded to identifiers stored by the selected quality check.
GET /api/v1/projects/{project_id}/quality-checks/{quality_check_id}/reviews
Returns the paginated operator review queue for a persisted
detections_vs_reference quality check. Optional query parameters are
evidence_role=false_positive|false_negative, decision, reviewed=true|false,
limit (1-200) and offset. Items derive only from persisted QA evidence.
{
"data": {
"items": [{
"id": null,
"project_id": "uuid",
"quality_check_id": "uuid",
"analysis_run_id": "uuid",
"evidence_role": "false_positive",
"evidence_feature_id": "detection-uuid",
"detection_id": "detection-uuid",
"decision": "unreviewed",
"confidence": 0.62,
"class_name": "building"
}],
"total": 1,
"limit": 50,
"offset": 0,
"summary": {
"total": 73,
"reviewed": 0,
"remaining": 73,
"false_positive_total": 17,
"false_negative_total": 56,
"decision_counts": {"unreviewed": 73}
}
}
}
POST /api/v1/projects/{project_id}/quality-checks/{quality_check_id}/reviews
Creates or updates one durable operator decision. The evidence id must belong to the quality check and resolve to the persisted Detection or reference VectorFeature. False-positive and false-negative roles accept only their role-specific decisions.
{
"evidence_role": "false_positive",
"evidence_feature_id": "detection-uuid",
"decision": "qa_alignment_mismatch",
"notes": "The detection box overlaps the irregular GRB footprint.",
"reviewed_by": "operator"
}
Allowed decisions are confirmed_model_false_positive,
confirmed_model_false_negative, reference_gap_or_change,
qa_alignment_mismatch, imagery_obscured_or_uncertain, uncertain and
unreviewed. Invalid role/decision combinations return
INVALID_DETECTION_REVIEW_DECISION.
Exports
POST /api/v1/exports/geojson
Export detections, segmentations or vector layer to GeoJSON.
Dataset vector export request:
{
"export_kind": "dataset",
"dataset_id": "uuid",
"name": "optional-basename"
}
Map vector selection export request:
{
"export_kind": "vector_selection",
"dataset_id": "uuid",
"area_id": "optional-uuid-used-as-an-official-area-constraint",
"bbox": {
"min_x": 5.0,
"min_y": 51.0,
"max_x": 5.1,
"max_y": 51.1,
"crs": "EPSG:4326"
},
"limit": 250,
"name": "optional-basename"
}
Detection run export request:
{
"export_kind": "detection_run",
"analysis_run_id": "uuid",
"intended_use": "review",
"name": "optional-basename"
}
intended_use is review by default. Every detection FeatureCollection and
export record contains a machine-readable geointel_result / result_trust
contract. Raw model output remains authoritative: false and is classified as
unverified_ai_review_output when authoritative QA is absent or incomplete.
Feature properties repeat the classification so it survives GIS workflows
that discard collection-level metadata.
intended_use: operational fails closed with
DETECTION_OPERATIONAL_EXPORT_BLOCKED unless the same persisted run has a
completed detection-versus-reference check that proves all of the following:
- the reference Dataset is authoritative and specifically approved as primary authority for building validation;
- inference coverage was derived from the persisted tile-manifest union;
- imagery/reference time compatibility is proven;
- geometry is supported and no QA warnings remain;
- false-positive and false-negative counts are both explicitly present and zero.
Even a passing operational export remains AI-derived and therefore retains
authoritative: false, the exact quality-check/reference identifiers and an
operator-review limitation. Model confidence by itself never unlocks export.
Segmentation run export request:
{
"export_kind": "segmentation_run",
"analysis_run_id": "uuid",
"name": "optional-basename"
}
Response persists an exports row and writes a deterministic JSON artifact:
{
"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. Detection export
in the frontend is deliberately labelled as a control layer and requests
intended_use: review; it cannot silently produce an operationally approved
artifact. Vector selection
exports query persisted PostGIS vector_features with the supplied EPSG:4326
bbox, write the selected FeatureCollection as a vector_selection_geojson
artifact, and persist bbox/feature-count metadata in the export record. When
area_id is present, the export scope is bbox ∩ Area. The frontend sends the
active Area for drawn, manual and full-work-area selections so no result can
leak outside the chosen official boundary. A bbox enclosing the complete Area
still resolves to the exact Area geometry. Raster
datasets are rejected for dataset and selection GeoJSON export.
POST /api/v1/exports/map-result
Persists the active map result before opening the Downloads workspace. The backend recomputes the result from persisted data; it never accepts client metrics as authoritative export content.
Current vector or governed raster request:
{
"project_id": "uuid",
"mode": "current",
"dataset_id": "uuid",
"bbox": {
"min_x": 5.10,
"min_y": 51.17,
"max_x": 5.11,
"max_y": 51.18,
"crs": "EPSG:4326"
},
"area_id": null,
"partitioned": false,
"product_key": null,
"theme_id": "buildings",
"name": "buildings-analysis"
}
Historical comparison request:
{
"project_id": "uuid",
"mode": "evolution",
"earlier_dataset_id": "uuid",
"later_dataset_id": "uuid",
"bbox": {
"min_x": 5.10,
"min_y": 51.17,
"max_x": 5.11,
"max_y": 51.18,
"crs": "EPSG:4326"
},
"theme_id": "forest"
}
Vector results reuse the authoritative vector_selection_geojson flow.
Governed raster results are persisted as map_analysis_json; temporal
comparisons use map_evolution_json. Metadata records the bbox, optional exact
Area scope, source datasets, theme and server_recomputed=true.
POST /api/v1/exports/metadata
Exports project metadata JSON for projects, datasets, persisted QA/QC summary rows and existing export history.
{
"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.
HTML report artifacts are intentionally download-only through /download.
Calling /content for project_report_html returns:
code: EXPORT_CONTENT_UNSUPPORTED
message: Export content preview is only available for JSON and GeoJSON artifacts. Download HTML report artifacts instead.
GET /api/v1/exports/{export_id}/download
Downloads the stored JSON/GeoJSON/HTML export artifact as a raw file response
with a Content-Disposition attachment filename. JSON and GeoJSON artifacts
use application/json; HTML report artifacts use text/html. 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 for JSON/GeoJSON
artifacts.
Future export route: /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, V1 readiness summary, QA/QC summary, known limitations and export history state. This does not create a PDF and does not introduce a report designer.
{
"project_id": "uuid",
"name": "optional-basename"
}
Response persists an exports row with export_type: project_report_html. Download the report through:
GET /api/v1/exports/{export_id}/download
PDF/report-designer functionality can be added after core GeoAI workflows work.
Temporal datasets and area evolution
Temporal metadata describes the source observation, not API run history. A
snapshot is a normal persisted dataset grouped by temporal_series_key and
ordered by observed_at. Optional validity uses valid_from and valid_to;
temporal_granularity is snapshot, day, month, year or period.
Dataset upload accepts those temporal fields plus source_version. When a
dataset declares source_metadata.selection_aggregation, the vector bbox
selection response also contains a summary with metric label/value/unit,
aggregation method, feature count, estimate status and an optional warning.
Supported PostGIS aggregations are feature count, intersection area,
intersection length, numeric sum and area-weighted numeric sum. Area and length
are measured after transformation to EPSG:31370.
Configured supplemental metrics can use filter_property plus
filter_values. Filters are server-owned dataset metadata, not arbitrary
client SQL or request expressions.
PATCH /api/v1/projects/{project_id}/datasets/{dataset_id}/temporal
Updates the temporal provenance of an existing dataset. Series key and observation date are required together. It does not alter features or manufacture a historical observation.
GET /api/v1/projects/{project_id}/datasets/{dataset_id}/versions
Lists immutable storage/provenance versions. Uploads and derived datasets create version 1 in the same persistence transaction.
GET /api/v1/projects/{project_id}/temporal/series
Returns dated project series in the canonical envelope. Each item contains its source/layer identity, first and last observations and ordered datasets.
POST /api/v1/projects/{project_id}/temporal/compare
{
"earlier_dataset_id": "uuid",
"later_dataset_id": "uuid",
"bbox": {"min_x": 5.0, "min_y": 51.0, "max_x": 5.2, "max_y": 51.2, "crs": "EPSG:4326"},
"area_id": "optional persisted Area uuid",
"preview_limit": 500
}
Both datasets must belong to the project and the same temporal series, with
the earlier observation preceding the later one. The response contains source
snapshot references, selection bbox, earlier/later metric values,
absolute/percentage change, estimate status, warnings and GeoJSON evidence.
metric remains the backwards-compatible primary measurement. metrics
contains every aggregation that is compatible between both snapshots and
timeline contains the same persisted metric for every dated snapshot in the
series. When area_id is supplied it must belong to the project and the exact
persisted Area geometry is used; the bbox remains only the bounded map extent.
Added/removed/modified object changes are calculated only when source
provenance declares stable feature identities; otherwise
object_changes.available=false and no object history is inferred.
Governed regional GRB snapshots use the official OGC feature identifier as their object identity. New imports declare the identity scheme and accepted collection prefixes explicitly. Existing operator-managed GRB snapshots are accepted only when their authoritative, complete and area-clipped provenance matches an approved regional operator and every selected identifier is present, unique and uses the expected prefix. Missing, duplicate or unexpected identifiers fail closed to metric-only comparison. The existing 5,000-feature selection limit also remains in force. Differences between daily GRB editions describe changes in the official registration; they do not prove that a physical change happened on the exact publication date.
For reference_layer_name=agriculture, the primary map metric is exact
intersected declared-use area in hectares. Supplemental metrics use
server-owned filters on the normalized official main-crop group. Annual ALZ
Datasets share one scope-specific temporal series, but declare
identity_stable=false; their temporal response compares area totals and
returns no parcel-level added/removed/modified claims.
For reference_layer_name=building_registry, the primary metric is exact
intersected building-footprint area in hectares. Supplemental server-owned
metrics expose register building/status counts, aggregate building-unit and
address-status counts, and confirmed GRB matches. Filtered feature_count
metrics apply their configured property filter in PostGIS just like filtered
area/sum metrics. Address labels and house/box numbers are never part of the
queryable Feature properties or response contract.
The governed snapshot is scoped to its persisted area_id. The frontend may
prefer it over the regional GRB building layer only when that exact Area is
active; another municipality or the full region must continue to use the
regional GRB Dataset. No new register-specific API endpoint exists: upload,
GeoJSON, exact selection and temporal provenance use the existing Dataset
contracts.
Local GeoIntel assistant
The assistant is an optional read-only language interface over persisted GeoIntel measurements. The browser never connects to Ollama directly and does not choose an arbitrary provider URL.
GET /api/v1/assistant/status
Returns configured, not_configured or unavailable, the configured default
model and the number of locally installed models. It never downloads a model.
GET /api/v1/assistant/models
Returns the models reported by Ollama GET /api/tags in the canonical
envelope. A chat request can only select a model from this list.
POST /api/v1/projects/{project_id}/assistant/query
{
"question": "Hoe evolueerde de bosoppervlakte?",
"model": "qwen3.5:9b",
"bbox": {"min_x": 5.0, "min_y": 51.0, "max_x": 5.2, "max_y": 51.2, "crs": "EPSG:4326"},
"area_id": "optional persisted Area uuid",
"history": []
}
The backend validates project/Area ownership, calculates current semantic
metrics from PostGIS and includes dated observations only for persisted
temporal series. Geometry is not sent to Ollama. The response contains the
answer, used model, scope label, context metrics, discovered temporal series,
source dataset ids and warnings. Missing measurements remain unavailable;
specifically, no water volume is inferred from 2D water geometry. The backend
sets an explicit Ollama context window and returns
OLLAMA_RESPONSE_TRUNCATED instead of accepting a response with
done_reason=length as a complete answer.
Bathymetry and VHA profile acquisition
GET /api/v1/projects/{project_id}/datasets/bathymetry/sources
Returns the governed bathymetry source registry in the canonical envelope.
VHA inland profiles are operational. MDK Belgian Continental Shelf is
not_configured by default and becomes operational only after the operator
explicitly enables bounded acquisition and pins a coverage identifier. The
pinned SPW Walloon bathymetry archive is operational through a bounded,
explicit operator import. There is no browser-side source fetch and no
arbitrary source URL.
GET /api/v1/projects/{project_id}/datasets/bathymetry/sources/mdk_bcp_bathymetry/readiness
Runs one bounded, read-only WCS 1.0.0 GetCapabilities request with mandatory
system TLS verification and a configured response-size limit. Status is one
of disabled, invalid_configuration, tls_error,
endpoint_unavailable, invalid_capabilities or reachable. A reachable
response lists coverage identifiers, advertised formats and CRS values. There
is no insecure TLS fallback and this readiness endpoint never performs a
GetCoverage request.
POST /api/v1/projects/{project_id}/datasets/bathymetry/mdk/acquire
Runs one explicit, bounded WCS 1.0.0 GetCoverage acquisition as a synchronous
Job. The request contains an EPSG:4326 bbox, optional persisted area_id and
force_refresh. Acquisition is fail-closed unless
MDK_BATHYMETRY_ACQUISITION_ENABLED=true, a coverage identifier is explicitly
configured, the strict-TLS readiness probe is reachable and that identifier is
advertised by the live capabilities document.
The configured bbox-area, response-size, timeout and pixel-dimension limits are
always enforced. A successful GeoTIFF is validated and imported through the
existing Dataset raster flow with request hash, response hash, acquisition
time, MDK attribution and the LAT vertical reference in provenance. No depth
values are synthesized and no water volume is inferred.
POST /api/v1/projects/{project_id}/datasets/bathymetry/profiles/acquire
{
"bbox": {"min_x": 5.0, "min_y": 51.0, "max_x": 5.3, "max_y": 51.3, "crs": "EPSG:4326"},
"area_id": "optional persisted Area uuid",
"force_refresh": false
}
The endpoint creates a synchronous vector.bathymetry_profiles.acquire Job.
It queries the official VHA Digital Atlas in bounded pages, intersects every
point with the persisted Area when supplied and persists one ordinary
reference Dataset plus VectorFeature rows. The result reports exact profile,
document, structured-depth/width and watercourse counts and the source
measurement-date range.
The acquired Dataset uses
source_name=vmm_vha_bathymetry_profiles,
dataset_role=reference and
reference_layer_name=bathymetry_profiles. Existing vector content and
selection endpoints provide GeoJSON and metrics. The contract does not expose
a continuous bathymetric surface, current water depth or water volume.
POST /api/v1/projects/{project_id}/datasets/bathymetry/profiles/partitions/finalize
Finalizes a complete municipality-partition manifest. The request supplies a
scope key, every expected municipality Area id, one ready VHA Dataset id for
each non-empty partition, explicit Area ids with zero source profiles, a
SHA-256 manifest identity and observation time. The backend verifies project,
Area, provider, Dataset and one-Dataset-per-Area consistency. Only a complete
accounting sets partitioned_source_audit=true and
regional_partitions_complete=true; partial operator runs remain hidden at
regional scope.
POST /api/v1/projects/{project_id}/datasets/bathymetry/profiles/partitions/select
Runs one bounded spatial selection across the latest complete flanders
VHA manifest. The request reuses VectorSelectionRequest: EPSG:4326 bbox,
optional persisted Area and a result limit of at most 1,000 features.
The backend first rejects incomplete or inconsistent manifests, selects only
municipality Dataset partitions whose recorded bounds overlap the request and
then queries their persisted vector_features with one PostGIS intersection.
When the Area is a municipality, only that exact Area partition is eligible.
A regional Area can combine all intersecting partitions without treating one
municipality Dataset as representative regional data.
The canonical response extends the ordinary vector-selection result with
partition_count, available_partition_count, partition_scope_key,
source_name and the contributing dataset_ids. GeoJSON is capped by the
request limit, while total_feature_count and all configured depth/width
metrics are calculated across the complete spatial result. Empty municipalities
return an empty, honest result. No provider request occurs during analysis.
POST /api/v1/projects/{project_id}/datasets/{dataset_id}/raster/bathymetry/select
Runs a bounded selection against a ready persisted
source_name=spw_bathymetry raster. The request contains an EPSG:4326 bbox
and optional persisted area_id, matching the other governed raster-selection
contracts.
The response reports:
- mean, minimum, maximum, p10 and p90 waterbed elevation in
m mDNG; - exact raster-cell surface with surveyed bed values in hectares;
- source coverage percentage inside the selected geometry;
- source resolution, survey period
2019-2022, vertical reference and explicit unsupported metrics.
current_water_depth_m, water_volume_m3 and vertical-datum conversion remain
unsupported. The endpoint reads the persisted EPSG:3812 geometry-aligned
raster; it does not query SPW and does not synthesize missing cells.
GET /api/v1/projects/{project_id}/datasets/{dataset_id}/raster/bathymetry/image
Returns a transparent PNG rendering of the persisted bathymetry COG for the MapLibre image-overlay path. Rendering changes presentation only. Analytical values always come from the stored Float32 source cells.
Future provider output continues to use DatasetService and, for vectors, VectorFeatureService. Arbitrary service URLs, browser-side fetches, insecure TLS bypasses and startup downloads remain forbidden.
Governed regional official-vector acquisition
GET /api/v1/projects/{project_id}/datasets/official-vector/products
Returns the fixed official vector registry in the canonical { "data": ... }
envelope. Every item includes its theme, geometry contract, authority,
licence/attribution, provider-native collection, query mode and
coverage_zones. The fixed allowlist contains:
- Flanders:
bwk_natura2000_2025anddov_soil_types; - Wallonia:
spw_picc_buildings,spw_picc_roads,spw_picc_waterwaysandspw_picc_water_surfaces; - Brussels:
urbis_buildingsandurbis_cadastral_parcels.
Arbitrary collection names or URLs are never accepted.
POST /api/v1/projects/{project_id}/datasets/official-vector/acquire
Request:
{
"bbox": {
"min_x": 5.05,
"min_y": 51.15,
"max_x": 5.25,
"max_y": 51.30,
"crs": "EPSG:4326"
},
"area_id": "optional-area-uuid",
"product_key": "bwk_natura2000_2025",
"force_refresh": false
}
The synchronous vector.official.acquire Job validates the metric request
size, intersects bbox with the persisted Area, retrieves every bounded page,
clips source geometry in the provider-native metric CRS and persists
EPSG:4326 features through DatasetService. A repeated exact request can
reuse the 24-hour cache. Provider errors, unstable/incomplete pagination and
safety-limit violations fail without persisting a truncated Dataset.
Walloon and Brussels products additionally require a persisted exact regional
coverage Area (Wallonia or Brussels-Capital Region). SPW/PICC uses stable
OBJECTID ArcGIS REST paging; UrbIS uses WFS 2.0 paging ordered by
INSPIRE_ID. A caller cannot make a Flemish product cover Wallonia, or merge
different regional authorities into one Dataset.
bwk_natura2000_2025 preserves BWK EVAL, EENH*, HAB* and PHAB*
semantics. dov_soil_types preserves mapped soil, texture, drainage, profile
and substrate classes and is dated as the 1949-1971 survey period. Neither
contract accepts a caller-supplied endpoint.
PICC building footprints, road axes and hydrographic axes/surfaces retain their source identifiers and report source-appropriate counts, footprint/ surface hectares or line kilometres. UrbIS building footprints and cadastral parcels remain separate products with their own Paradigm/FPS Finance licence notes. These products do not claim semantic parity with GRB and do not expose water volume.
Governed Landgebruik Vlaanderen forest and agriculture
The existing
GET /api/v1/projects/{project_id}/datasets/thematic-raster/products registry
also returns forest_land_use_2025 for source class 12 and
agricultural_land_use_2025 for source classes 13 and 14. Both use the
existing thematic acquisition and selection contracts. Persisted rasters are
binary masks; the source class allowlist and original source-value range are
retained and validated.