{
  "openapi": "3.1.0",
  "info": {
    "title": "InfraBIDShare Publieke API",
    "version": "1.0.0",
    "description": "Publieke API van de InfraBIDShare Data Suite. Alle endpoints vereisen een API-token in de header `Authorization: Bearer <token>` (of `X-API-Key`). Tokens beheer je in de beheer-UI onder API-toegang.\n\n### Caching en eviction\n\nGecachete endpoints (`/datasets`, `/netkaarten`) gebruiken een in-memory responscache per worker-instantie:\n\n- **Sleutel**: clientvingerafdruk (SHA-256 van `X-API-Key`/`Authorization`/client-IP, ingekort tot 16 tekens) + genormaliseerde, alfabetisch gesorteerde queryparameters. Lege parameters tellen niet mee, zodat `?thema=&protocol=wfs` en `?protocol=wfs` dezelfde cache-entry delen.\n- **TTL-eviction**: elke entry krijgt een absolute vervaltijd (120 s voor `/datasets`, 300 s voor `/netkaarten`). Een verlopen entry wordt bij de eerstvolgende lees verwijderd en opnieuw opgebouwd (`X-Cache: MISS`).\n- **Capaciteits-eviction**: boven 200 entries wordt bij elke schrijfactie een opruimronde uitgevoerd die alle verlopen entries verwijdert.\n- **Instantie-eviction**: de cache leeft in het geheugen van één worker-instantie. Bij een deploy, herstart of koude start is de cache leeg, en parallelle instanties delen hun cache niet, `X-Cache: MISS` na een eerdere `HIT` is dus normaal gedrag, geen fout.\n- **Validatie**: elke respons draagt een zwakke `ETag` (hash van de body). Stuur `If-None-Match` mee; bij een gelijke hash volgt `304 Not Modified` zonder body. `Cache-Control: private, max-age=<ttl>` en `Vary: x-api-key, authorization` voorkomen dat gedeelde caches data tussen clients mengen.\n- **Headers**: `X-Cache: HIT|MISS`, `ETag`, plus op alle dataset-endpoints `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, `X-RateLimit-Policy` en `Retry-After` bij `429`.\n\n### Rate limiting\n\nElke client (bepaald via een hash van `X-API-Key`/`Authorization`, anders client-IP) heeft per endpoint een eigen venster van 60 seconden:\n\n- `GET /datasets`: 60 req/min\n- `GET /datasets/{id}` en refresh-status: 120 req/min\n- `GET /datasets/{id}/layers`: 120 req/min; `PUT`/`POST` laagimport: 20 req/min\n- `POST /datasets/{id}/refresh`: 10 req/min\n- `POST /query`: 30 req/min\n- `GET /netkaarten`: 30 req/min\n\nDaarnaast geldt een globale tokenlimiet van 120 verzoeken per minuut. Bij overschrijding volgt `429` met `Retry-After` (seconden) en de `X-RateLimit-*` headers; `304`-responses tellen wel mee. Gecachete responses (`X-Cache: HIT`) verbruiken ook budget, dus gebruik `If-None-Match` en respecteer `Retry-After` met exponentiële backoff.\n\nMutaties (refresh, laagimport, statuswijziging) invalideren de cache niet actief; reken op maximaal één TTL-venster vertraging of forceer verse data met een unieke, onschadelijke queryparameter."
  },
  "servers": [
    {
      "url": "https://infrabidshare.com",
      "description": "Deze omgeving"
    }
  ],
  "tags": [
    {
      "name": "Bronbewaking",
      "description": "Health-status en checkgeschiedenis per dataset"
    },
    {
      "name": "Datasets",
      "description": "Datasetregister en handmatige refresh"
    },
    {
      "name": "Kaartquery",
      "description": "Polygoonqueries met cache/API-first-strategie"
    },
    {
      "name": "Interoperabiliteit",
      "description": "DCAT en OGC API - Records"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "apiKeyAuth": []
    }
  ],
  "paths": {
    "/api/public/v1/health": {
      "get": {
        "tags": [
          "Bronbewaking"
        ],
        "summary": "Actuele health-status per dataset",
        "description": "Geeft per actieve dataset de laatst bekende status (gezond/traag/storing/vervallen/onbekend), de laatste responstijd, of de controle verouderd is (>24u) en de planning van de automatische controle.",
        "operationId": "getHealth",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "description": "Filter op health-status.",
            "schema": {
              "type": "string",
              "enum": [
                "onbekend",
                "gezond",
                "traag",
                "storing",
                "vervallen"
              ]
            },
            "example": "storing"
          },
          {
            "name": "dataset_id",
            "in": "query",
            "description": "Filter op één dataset (technische dataset_id, bijv. `bgt`).",
            "schema": {
              "type": "string",
              "maxLength": 80
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Paginanummer (vanaf 1).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "description": "Aantal resultaten per pagina.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Overzicht van health-statussen",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/public/v1/health/history": {
      "get": {
        "tags": [
          "Bronbewaking"
        ],
        "summary": "Laatste N controles per dataset",
        "operationId": "getHealthHistory",
        "parameters": [
          {
            "name": "dataset_id",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 80
            },
            "description": "Beperk tot één dataset."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "onbekend",
                "gezond",
                "traag",
                "storing",
                "vervallen"
              ]
            },
            "description": "Filter datasets op huidige status."
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Aantal controles per dataset.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 10
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Checkgeschiedenis per dataset",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/public/v1/datasets/{datasetId}/refresh": {
      "get": {
        "tags": [
          "Datasets"
        ],
        "summary": "Actuele voortgang / wachtrijstatus van een dataset",
        "operationId": "getRefreshStatus",
        "parameters": [
          {
            "$ref": "#/components/parameters/DatasetIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Wachtrij- en planningstatus",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Dataset niet gevonden"
          }
        }
      },
      "post": {
        "tags": [
          "Datasets"
        ],
        "summary": "Handmatige controle/refresh triggeren",
        "operationId": "triggerRefresh",
        "parameters": [
          {
            "$ref": "#/components/parameters/DatasetIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Controle uitgevoerd",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "description": "Er loopt al een controle voor deze dataset"
          }
        }
      }
    },
    "/api/public/v1/datasets/{datasetId}/layers": {
      "get": {
        "tags": [
          "Datasets"
        ],
        "summary": "Exporteer laag-/featuretype-mapping van een dataset",
        "description": "Geeft alle vastgelegde WMS-lagen en WFS-featuretypes van een dataset, inclusief standaardlaag, CRS en volgorde. Direct herbruikbaar als importbody.\n\nElke laag bevat portaal-metadata zodat een client titels, velden en geometrietype automatisch kan renderen: `titel`, `sleutel` (waarde voor `?lagen=`), `geometrietype` (punt/lijn/vlak/multi/raster/onbekend), `geojson_type`, `bevraagbaar`, `velden` (`naam`, `type`, `titel`, `verplicht`), `veldnamen`, `bbox` (EPSG:4326), `min_zoom`/`max_zoom`, `metadata_bijgewerkt_at`, `metadata_volledig` en `links` (`tiles`, `tilejson`, `query`). De respons bevat daarnaast `samenvatting` met tellingen per geometrietype.",
        "operationId": "exportDatasetLayers",
        "parameters": [
          {
            "$ref": "#/components/parameters/DatasetIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Laagmapping met portaal-metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DatasetLayersResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Dataset niet gevonden"
          }
        }
      },
      "put": {
        "tags": [
          "Datasets"
        ],
        "summary": "Importeer laag-/featuretype-mapping (vervangen of samenvoegen)",
        "description": "Importeert lagen voor een dataset. Modus `vervang` (standaard) verwijdert lagen die niet in de body staan; `samenvoegen` werkt alleen bestaande bij en voegt nieuwe toe. Matching gebeurt prefix-onafhankelijk op featuretype of laagnaam. Maximaal één laag mag `standaard: true` hebben. POST doet hetzelfde als PUT.",
        "operationId": "importDatasetLayers",
        "parameters": [
          {
            "$ref": "#/components/parameters/DatasetIdPath"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "lagen"
                ],
                "properties": {
                  "modus": {
                    "type": "string",
                    "enum": [
                      "vervang",
                      "samenvoegen"
                    ],
                    "default": "vervang"
                  },
                  "lagen": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 1000,
                    "items": {
                      "type": "object",
                      "required": [
                        "titel"
                      ],
                      "properties": {
                        "titel": {
                          "type": "string"
                        },
                        "laagnaam": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "WMS/OGC-laagnaam"
                        },
                        "featuretype": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "WFS-featuretype"
                        },
                        "toelichting": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "crs": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "example": "EPSG:28992"
                        },
                        "standaard": {
                          "type": "boolean",
                          "default": false
                        },
                        "volgorde": {
                          "type": "integer",
                          "default": 0
                        },
                        "actief": {
                          "type": "boolean",
                          "default": true
                        },
                        "geometrietype": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "punt",
                            "lijn",
                            "vlak",
                            "multi",
                            "raster",
                            "onbekend",
                            null
                          ],
                          "description": "Geometrietype van de laag; leeg = afleiden uit het protocol."
                        },
                        "velden": {
                          "type": "array",
                          "maxItems": 500,
                          "description": "Attribuutvelden die het portaal kan tonen of filteren.",
                          "items": {
                            "type": "object",
                            "required": [
                              "naam"
                            ],
                            "properties": {
                              "naam": {
                                "type": "string",
                                "example": "identificatie"
                              },
                              "type": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "example": "string"
                              },
                              "titel": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "example": "Identificatie"
                              },
                              "verplicht": {
                                "type": [
                                  "boolean",
                                  "null"
                                ]
                              }
                            }
                          }
                        },
                        "bbox": {
                          "type": [
                            "array",
                            "null"
                          ],
                          "minItems": 4,
                          "maxItems": 4,
                          "items": {
                            "type": "number"
                          },
                          "description": "[minx, miny, maxx, maxy] in EPSG:4326.",
                          "example": [
                            3.2,
                            50.7,
                            7.3,
                            53.6
                          ]
                        },
                        "min_zoom": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "minimum": 0,
                          "maximum": 24
                        },
                        "max_zoom": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "minimum": 0,
                          "maximum": 24
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Importresultaat",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Ongeldige body"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Dataset niet gevonden"
          }
        }
      },
      "post": {
        "tags": [
          "Datasets"
        ],
        "summary": "Lagen importeren of vervangen (gelijk aan PUT)",
        "description": "Zelfde payload en gedrag als `PUT`; beschikbaar voor clients die geen PUT kunnen versturen.",
        "operationId": "postDatasetsDatasetIdLayers",
        "x-required-scope": "layers:write",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/datasets/{datasetId}/schema": {
      "get": {
        "tags": [
          "Datasets"
        ],
        "summary": "Veldschema per laag voor automatische filters en formulieren",
        "description": "Geeft per laag alle kolommen met genormaliseerd datatype (`tekst`, `geheel_getal`, `kommagetal`, `boolean`, `datum`, `datumtijd`, `geometrie`, `onbekend`), het bijbehorende `json_type`, een `ui_widget`-hint (tekstveld, getalveld, schakelaar, datumkiezer, datumtijdkiezer, verborgen) en de toegestane `operatoren`.\n\nPer laag bevat `formulier` direct bruikbare lijsten: `filtervelden`, `facetvelden`, `verplichte_velden` en `geometriefilter` (bbox/polygoon/buffer). Elk veld heeft `voorbeeld_where` voor de `?where=`-parameter van de tiles- en mbtiles-endpoints.",
        "operationId": "getDatasetSchema",
        "parameters": [
          {
            "$ref": "#/components/parameters/DatasetIdPath"
          },
          {
            "name": "lagen",
            "in": "query",
            "description": "Komma-gescheiden laagsleutels; alleen die lagen worden teruggegeven.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "bevraagbaar",
            "in": "query",
            "description": "`1` filtert rasterlagen weg en levert alleen vectorlagen met featurequery.",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Veldschema met kolommen, typen en formulierdefinitie per laag",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Dataset niet gevonden"
          }
        }
      }
    },
    "/api/public/v1/datasets": {
      "get": {
        "tags": [
          "Datasets"
        ],
        "summary": "Datasetregister",
        "description": "Het volledige datasetregister met metadata (thema, bronhouder, licentie, CRS, protocol, health en versiedatum).\n\nResponses worden 120 seconden gecachet per client en filtercombinatie: de respons bevat `ETag` en `X-Cache: HIT|MISS`; stuur `If-None-Match` mee om een `304 Not Modified` (zonder body) te krijgen.\n\nRecent toegevoegde datasets:\n- `rdw-parkeren-geometrie-gebied` (REST/Socrata, RDW open data parkeren, EPSG:4326), laag `nsk3-v9n7` met parkeergebiedgeometrie (WKT-kolom `areageometryastext`); ondersteunt SoQL-parameters (`$where`, `$select`, `$limit`, `$offset`) en `.geojson`-output.\n- `rdw-parkeren-geometrie-gebied-soda3` (REST/Socrata SODA v3), endpoint `https://opendata.rdw.nl/api/v3/views/nsk3-v9n7/query.json`; SoQL via `?query=SELECT * WHERE within_box(areageometryastext, maxLat, minLon, minLat, maxLon) LIMIT 1000`, GeoJSON via `query.geojson`, bulk via `/export`. Optioneel app-token (`SOCRATA_APP_TOKEN`) wordt server-side als `X-App-Token` meegestuurd voor hogere throttle-limieten.\n- RDW Open Data Parkeren (NPR) als losse datasets: `rdw-parkeren-parkeergebied`, `rdw-parkeren-specificaties-parkeergebied`, `rdw-parkeren-gebied-regeling`, `rdw-parkeren-tijdvak`, `rdw-parkeren-tariefdeel`, `rdw-parkeren-parkeeradres`, `rdw-parkeren-parking-open`, `rdw-parkeren-verkooppunt`, `rdw-parkeren-geo-verkooppunt`, `rdw-parkeren-betaalmethode-verkooppunt`, `rdw-parkeren-betaalmethode-gebied`, `rdw-parkeren-geldigheidsuitbreiding`, `rdw-parkeren-index-statisch-dynamisch`, `rdw-parkeren-gps-parkeerlocatie`, allemaal SODA v3 (`/api/v3/views/{id}/query.json`), EPSG:4326.\n- RDW voertuigregisters: `rdw-gekentekende-voertuigen`, `rdw-gekentekende-voertuigen-brandstof`, `rdw-gekentekende-voertuigen-carrosserie-specificatie` (koppelen op `kenteken`).\n- Elke dataset bevat `query_ondersteuning` met `modus` (`bbox` | `attributen` | `zoek` | `tegels`) en voor Socrata-bronnen `geo_modus` (`point` | `latlon` | `wkt` | `geen`), zodat een portaal-UI direct weet of bbox-filters zinvol zijn.\n- WKT-bronnen (`rdw-parkeren-geometrie-gebied`, `-soda3`) filteren server-side via `within_box(areageometryastext::point, ...)` en worden genormaliseerd naar GeoJSON.\n- Attribuuttabellen (tarieven, adressen, tijdvakken) leveren features zonder geometrie; koppelen doe je op `areaid` en `areamanagerid`.\n- Geometrie: `rdw-parkeren-geo-verkooppunt` heeft een echte point-kolom (`location`, `within_box`). `rdw-parkeren-gps-parkeerlocatie` heeft losse WGS84-kolommen; bbox-queries vertalen naar `longitude BETWEEN ... AND latitude BETWEEN ...` en de geometrie is Point(longitude, latitude) in EPSG:4326.\n- `pdok-kadaster-location-api` (OGC API Features, Kadaster/PDOK, EPSG:4326), 13 collecties/lagen: `adres`, `gebouw`, `perceel`, `woonplaats`, `plaats`, `gemeentegebied`, `provinciegebied`, `wegdeel`, `waterdeel`, `spoorbaandeel`, `functioneel_gebied`, `geografisch_gebied`, `inrichtingselement`.\n- `liander-open-data-elektra` (WFS) en `liander-open-data-elektra-arcgis` (ArcGIS FeatureServer), 9 lagen elektriciteitsnet Liander.\n- `liander-open-data-gas` (WFS) en `liander-open-data-gas-arcgis` (ArcGIS FeatureServer, laag-id 0), laag `Gasleiding`, EPSG:28992.\n- `stedin-hoogspanningsstations`, `stedin-hoogspanningsverbindingen`, `stedin-middenspanningsstations`, `stedin-middenspanningsverbindingen`, `stedin-middenlaagspanningsstations`, `stedin-laagspanningsstations`, `stedin-laagspanningsverbindingen`, `stedin-basisgegevens-elektriciteitsnet-zeeland`, shapefile-downloads (`protocol=bestand`), EPSG:28992.\n- `stedin-gasvervangingsdata-2026` en `stedin-gasvervangingsdata-zeeland`, gasleidingen met vervangingsstatus (attribuut `Status`: Te vervangen voor 2028 / ouder dan 30 jaar / 30 jaar of jonger), shapefile-download, EPSG:28992.\n- `tennet-hoogspanningsnet` en `tennet-netkaarten-pdf`, zie `/api/public/v1/netkaarten`.\n\nVoorbeeldrequests - Liander:\n- `GET /api/public/v1/datasets?thema=Nutsinfrastructuur&protocol=wfs`, o.a. de Liander WFS-datasets voor elektra en gas.\n- `GET /api/public/v1/datasets/liander-open-data-elektra`, metadata WFS-variant elektranet.\n- `GET /api/public/v1/datasets/liander-open-data-elektra/layers`, 9 lagen met featuretype-namen voor GetFeature.\n- `GET /api/public/v1/datasets/liander-open-data-elektra-arcgis/layers`, dezelfde lagen als ArcGIS-laag-id's; gebruik `feature_endpoint_url` + `/{laag-id}/query?f=geojson`.\n- `GET /api/public/v1/datasets/liander-open-data-gas-arcgis`, gasleidingen, laag `Gasleiding` (id 0), EPSG:28992.\n- `POST /api/public/v1/query` met body `{ \"dataset_ids\": [\"liander-open-data-gas-arcgis\"], \"buffer_m\": 25, \"geometry\": { \"type\": \"Polygon\", \"coordinates\": [[[5.11,52.08],[5.13,52.08],[5.13,52.10],[5.11,52.10],[5.11,52.08]]] } }`, polygoon-/bufferquery via de cache-eerst strategie.\n\nVoorbeeldrequests - Stedin:\n- `GET /api/public/v1/datasets?protocol=bestand&thema=Nutsinfrastructuur`, alle Stedin shapefile-downloads.\n- `GET /api/public/v1/datasets/stedin-middenspanningsverbindingen`, metadata met `endpoint_url` naar de officiële ZIP-download.\n- `GET /api/public/v1/datasets/stedin-gasvervangingsdata-2026/layers`, laag `Gasvervangingsdata` met attribuut `Status` (Te vervangen voor 2028 / ouder dan 30 jaar / 30 jaar of jonger).\n- `GET /api/public/v1/datasets/stedin-basisgegevens-elektriciteitsnet-zeeland`, regiobestand Zeeland, EPSG:28992.\n- `GET /api/public/v1/health?dataset_id=stedin-gasvervangingsdata-zeeland`, bronbewaking op de downloadlink.\n\nLaag- en bbox-filter (zelfde formaat als `/netkaarten`):\n- `lagen`, komma-gescheiden termen, deelmatch op laagtitel, `laagnaam` of `featuretype`. Datasets zonder matchende laag vallen weg; per dataset worden alleen de matchende lagen teruggegeven.\n- `bbox`, `minx,miny,maxx,maxy` met optioneel `bbox_crs` (standaard `EPSG:4326`). De bbox wordt verwerkt in de kant-en-klare `wfs_getfeature_url` (WFS) en als envelope-query in `arcgis_layer_url`.\n- Elke dataset bevat nu `aantal_lagen` en `lagen[]`; de respons bevat een `filters`-blok met de toegepaste waarden.\n- `GET /api/public/v1/datasets?lagen=gasleiding`\n- `GET /api/public/v1/datasets?protocol=wfs&lagen=station,verbinding&bbox=4.2,51.8,5.1,52.4&bbox_crs=EPSG:4326`\n\nOverige voorbeelden:\n- `GET /api/public/v1/datasets?thema=Nutsinfrastructuur`\n- `GET /api/public/v1/datasets?protocol=bestand`\n- `GET /api/public/v1/datasets/pdok-kadaster-location-api/layers`\n- `GET /api/public/v1/datasets?thema=Nutsinfrastructuur` met header `If-None-Match: W/\"<etag>\"` → `304 Not Modified` binnen het TTL-venster.",
        "operationId": "getDatasets",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Kwaliteitsstatus of `alle`."
          },
          {
            "name": "protocol",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "wfs",
                "wms",
                "wmts",
                "rest",
                "bestand",
                "api"
              ]
            },
            "description": "Bronprotocol. `rest` dekt ook ArcGIS FeatureServer, `bestand` de shapefile-downloads (o.a. Stedin).",
            "example": "rest"
          },
          {
            "name": "thema",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Thema, bijv. `Nutsinfrastructuur`.",
            "example": "Nutsinfrastructuur"
          },
          {
            "name": "beschikbaarheid",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "health",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "onbekend",
                "gezond",
                "traag",
                "storing",
                "vervallen"
              ]
            }
          },
          {
            "name": "wp",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 20
            },
            "description": "Werkpakketcode, bijv. `WP04`."
          },
          {
            "name": "zoek",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 120
            },
            "description": "Vrije zoekterm op dataset-id, naam of bronhouder. Handig om in een portaal-UI in één keer alle bronnen van een bronhouder op te halen, bijv. `?zoek=rdw`.",
            "example": "rdw"
          },
          {
            "name": "lagen",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 400
            },
            "description": "Komma-gescheiden laagtermen (deelmatch op titel, laagnaam of featuretype). Zelfde formaat als `/netkaarten`.",
            "example": "gasleiding,station"
          },
          {
            "name": "bbox",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 120
            },
            "description": "Bounding box `minx,miny,maxx,maxy`; verwerkt in de laag-URL's per dataset.",
            "example": "4.2,51.8,5.1,52.4"
          },
          {
            "name": "bbox_crs",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "EPSG:4326"
            },
            "description": "CRS van de bbox.",
            "example": "EPSG:4326"
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Datasets",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Ongeldige queryparameters (bijv. bbox)"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/public/v1/gallery": {
      "get": {
        "tags": [
          "Datasets"
        ],
        "summary": "Galerijweergave van vrijgegeven datasets",
        "description": "Compacte kaartweergave-data per dataset: basiskenmerken, status, laatste versiedatum, kaartthumbnail en deeplink naar de suite. Alleen actieve datasets met kwaliteitsstatus gecontroleerd of vrijgegeven.",
        "operationId": "getGallery",
        "parameters": [
          {
            "name": "thema",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "protocol",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Galerij-items",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/public/v1/lokaal/{sleutel}": {
      "get": {
        "tags": [
          "Datasets"
        ],
        "summary": "Eigen geleverde datasets als GeoJSON-laag",
        "description": "Levert door InfraBIDShare zelf beheerde datasets direct als GeoJSON `FeatureCollection` in EPSG:4326 (CRS84), klaar om in het Django-portaal als kaartlaag te tonen (bijv. Leaflet `L.geoJSON` of MapLibre `geojson`-source). Punten, lijnen en vlakken worden ondersteund.\n\nBeschikbare sleutels, onderwijs en assets:\n- `lokaal-scholen-totaal` (8.785 punten; property `Type` = BO/VO/MBO/HBO/WO/SO)\n- `lokaal-scholen-bo` (6.183), `lokaal-scholen-sbo-so-vso` (880), `lokaal-scholen-vo` (1.604), `lokaal-scholen-mbo` (58), `lokaal-scholen-hbo-wo` (54)\n- `lokaal-parkeergarages` (2.570), `lokaal-rav-standplaatsen` (254), `lokaal-rotondes` (6.695)\n- `lokaal-verkeersassets-s200` (1.950; property `assettypename` = DRIP/VRI/PRIS/TWI/TDI)\n\nMobiliteit, spoor en veiligheid:\n- `lokaal-metrostations` (124 punten)\n- `lokaal-bushaltes` (41.166 punten)\n- `lokaal-overwegen` (4.381 overwegen)\n- `lokaal-ov-routes` (3.910 lijnen; vereenvoudigd ~10 m)\n- `lokaal-bebouwde-kom` (8.571 vlakken; vereenvoudigd ~10 m)\n- `lokaal-brandweerkazernes` (909 punten incl. type, regio, contact)\n- `lokaal-buko-locaties` (BUKO Infrasupport-vestigingen)\n\nCBS-gebiedsindelingen 2022 (vlakken, gegeneraliseerd):\n- `lokaal-cbs-gemeente` (345), `lokaal-cbs-provincie` (12), `lokaal-cbs-landsdeel` (4)\n- `lokaal-cbs-wijk` (3.340), `lokaal-cbs-buurt` (14.317)\n- `lokaal-cbs-veiligheidsregio` (25), `lokaal-cbs-coropgebied` (40), `lokaal-cbs-arbeidsmarktregio` (35), `lokaal-cbs-ggdregio` (25), `lokaal-cbs-res` (30)\n\nSpoor, zorg en bestuurlijke grenzen:\n- `lokaal-spoorwegen` (29.071 spoortracés; elektrificatie, maxspeed, gebruik, beheerder)\n- `lokaal-treinstations` (420 punten), `lokaal-tramhaltes` (1.358 tram-/metrohaltes)\n- `lokaal-voertuigbeperkingen` (89.501 wegvakken met hoogte-/lengte-/breedte-/aslast-/gewichtsbeperking; property `wvk_id`)\n- `lokaal-ziekenhuizen` (188 punten incl. type, adres, gemeente, contact)\n- `lokaal-veiligheidsregios` (25 vlakken), `lokaal-waterschappen` (21 vlakken)\n\nAttribuutdataset zonder geometrie:\n- `lokaal-crow-keuzebomen` (102 CROW 96b keuzeboomvragen/maatregelen; property `Type` = ETW/GOW/etc., filterbaar met `?lagen=ETW`)\n\nBronbestanden zijn aangeleverd in EPSG:28992 (RD New) en server-side omgezet naar EPSG:4326.\n\nFilters:\n- `lagen`, komma-gescheiden; filtert op `assettypename` of `Type`, bijv. `?lagen=DRIP,VRI`.\n- `bbox`: `minx,miny,maxx,maxy` in EPSG:4326; werkt op de bounding box van elke geometrie (ook lijnen en vlakken).\n- `q`, vrije tekst over alle attributen.\n- `limit`, maximaal aantal features (standaard en maximum 20.000).\n\nCaching en limieten: 10 minuten responscache per filtercombinatie (`X-Cache`, `ETag`, `If-None-Match` → `304`), 60 verzoeken per minuut per client.\n\nVoorbeelden:\n- `GET /api/public/v1/lokaal/lokaal-cbs-gemeente?bbox=4.8,52.3,5.0,52.4`\n- `GET /api/public/v1/lokaal/lokaal-ov-routes?bbox=4.70,51.55,4.85,51.62`\n- `GET /api/public/v1/lokaal/lokaal-brandweerkazernes?q=rotterdam&limit=500`",
        "operationId": "getLokaleDataset",
        "parameters": [
          {
            "name": "sleutel",
            "in": "path",
            "required": true,
            "description": "Dataset-sleutel (`dataset_id`), bijv. `lokaal-rotondes`.",
            "schema": {
              "type": "string"
            },
            "example": "lokaal-scholen-totaal"
          },
          {
            "name": "lagen",
            "in": "query",
            "description": "Komma-gescheiden laag-/typefilter (`assettypename` of `Type`).",
            "schema": {
              "type": "string"
            },
            "example": "DRIP,VRI"
          },
          {
            "name": "bbox",
            "in": "query",
            "description": "Bounding box `minx,miny,maxx,maxy` in EPSG:4326.",
            "schema": {
              "type": "string"
            },
            "example": "4.70,51.55,4.85,51.62"
          },
          {
            "name": "q",
            "in": "query",
            "description": "Vrije tekstfilter over alle attributen.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximaal aantal features (max. 20000).",
            "schema": {
              "type": "integer",
              "default": 20000,
              "maximum": 20000
            }
          }
        ],
        "responses": {
          "200": {
            "description": "GeoJSON FeatureCollection met `metadata`-blok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Onbekende lokale dataset"
          }
        }
      }
    },
    "/api/public/v1/lokaal/{sleutel}/tiles/{z}/{x}/{y}": {
      "get": {
        "tags": [
          "Datasets"
        ],
        "summary": "Vector tiles (MVT) van een lokale dataset",
        "description": "Levert Mapbox Vector Tiles (protobuf) per z/x/y-tegel, zodat kaarten snel renderen met minimaal dataverkeer: alleen de zichtbare tegels worden opgehaald in plaats van de hele GeoJSON.\n\n- Laagnaam in de tegel is gelijk aan de `dataset_id` (bijv. `lokaal-bushaltes`).\n- Zoomniveaus 0 t/m 16; geometrieën worden per zoom vereenvoudigd.\n- Lege tegels geven `204 No Content`.\n- `?lagen=` filtert op `assettypename`/`Type`, net als de GeoJSON-route.\n- `?where=` filtert op attributen met een kleine SQL-achtige expressie, zodat er minder features in de tegel komen: `status = 'actief' AND breedte > 3`, `naam LIKE 'A%'`, `type IN ('rotonde','kruising')`, `beheerder IS NOT NULL`, gecombineerd met `AND` / `OR` / `NOT` en haakjes. Vergelijkingen zijn hoofdletterongevoelig en numerieke strings worden als getal vergeleken. Een ongeldige expressie geeft `400`.\n- `?token=` is toegestaan omdat kaartclients geen headers kunnen meesturen; gebruik bij voorkeur de header `x-api-key`.\n- Tegels zijn 1 uur cachebaar; de tegelindex blijft 15 minuten warm per server-instantie.\n\nGebruik in MapLibre GL:\n```js\nmap.addSource('bushaltes', { type: 'vector', tiles: ['https://www.infrabidshare.com/api/public/v1/lokaal/lokaal-bushaltes/tiles/{z}/{x}/{y}.mvt?token=API_TOKEN'], minzoom: 0, maxzoom: 16 });\nmap.addLayer({ id: 'bushaltes', type: 'circle', source: 'bushaltes', 'source-layer': 'lokaal-bushaltes' });\n```",
        "operationId": "getLokaleDatasetTegel",
        "parameters": [
          {
            "name": "sleutel",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "lokaal-bushaltes"
          },
          {
            "name": "z",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 22
            }
          },
          {
            "name": "x",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "y",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "lagen",
            "in": "query",
            "description": "Komma-gescheiden laag-/typefilter (`assettypename` of `Type`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "where",
            "in": "query",
            "description": "Attributenfilter (SQL-achtig): `=`, `!=`, `<`, `<=`, `>`, `>=`, `LIKE`/`NOT LIKE` (% en _), `IN (...)`, `IS [NOT] NULL`, gecombineerd met `AND`, `OR`, `NOT` en haakjes.",
            "schema": {
              "type": "string"
            },
            "example": "status = 'actief' AND breedte > 3"
          },
          {
            "name": "token",
            "in": "query",
            "description": "API-token als queryparameter voor kaartclients zonder headers.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Mapbox Vector Tile (protobuf)",
            "content": {
              "application/vnd.mapbox-vector-tile": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "204": {
            "description": "Lege tegel"
          },
          "400": {
            "description": "Ongeldige tegelcoördinaten of ongeldige `where`-expressie"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Onbekende lokale dataset"
          }
        }
      }
    },
    "/api/public/v1/lokaal/{sleutel}/tilejson": {
      "get": {
        "tags": [
          "Datasets"
        ],
        "summary": "TileJSON 3.0 voor de vector tiles van een lokale dataset",
        "description": "Geeft een TileJSON-document met `tiles`-template, zoombereik, `vector_layers` en metadata (aantal features, beschikbare lagen). Direct te gebruiken als MapLibre-source: `{ type: 'vector', url: '.../tilejson?token=API_TOKEN' }`.",
        "operationId": "getLokaleDatasetTileJson",
        "parameters": [
          {
            "name": "sleutel",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "lokaal-rotondes"
          },
          {
            "name": "lagen",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "where",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "example": "type IN ('rotonde','kruising')"
          },
          {
            "name": "token",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "TileJSON-document",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Onbekende lokale dataset"
          }
        }
      }
    },
    "/api/public/v1/datasets/{datasetId}/tiles/{z}/{x}/{y}": {
      "get": {
        "tags": [
          "Datasets"
        ],
        "summary": "Vector tiles (MVT) van elke dataset",
        "description": "Levert Mapbox Vector Tiles (protobuf) per z/x/y-tegel voor iedere actieve dataset.\n\n- Lokale datasets worden uit de opslag geïndexeerd (0 t/m 16).\n- Externe bronnen (WFS/ArcGIS/OGC API) worden per tegel-bbox bevraagd via de cache-eerst querylaag; minimaal zoomniveau 9 (anders `422`).\n- `source-layer` in de tegel is gelijk aan de `dataset_id`.\n- Lege tegels geven `204`; `X-Tile-Source` toont `lokaal`, `cache` of `bron`.\n- `?lagen=` filtert op laag/featuretype, `?token=` voor kaartclients zonder headers.\n- `?where=` filtert op attributen met een kleine SQL-achtige expressie, zodat er minder features in de tegel komen: `status = 'actief' AND breedte > 3`, `naam LIKE 'A%'`, `type IN ('rotonde','kruising')`, `beheerder IS NOT NULL`, gecombineerd met `AND` / `OR` / `NOT` en haakjes. Vergelijkingen zijn hoofdletterongevoelig en numerieke strings worden als getal vergeleken. Een ongeldige expressie geeft `400`.\n\n```js\nmap.addSource('ds', { type: 'vector', url: 'https://www.infrabidshare.com/api/public/v1/datasets/DATASET_ID/tilejson?token=API_TOKEN' });\nmap.addLayer({ id: 'ds', type: 'line', source: 'ds', 'source-layer': 'DATASET_ID' });\n```",
        "operationId": "getDatasetTegel",
        "parameters": [
          {
            "name": "datasetId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "z",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 22
            }
          },
          {
            "name": "x",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "y",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "lagen",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "where",
            "in": "query",
            "description": "Attributenfilter, zie beschrijving.",
            "schema": {
              "type": "string"
            },
            "example": "status = 'actief'"
          },
          {
            "name": "token",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Mapbox Vector Tile (protobuf)",
            "content": {
              "application/vnd.mapbox-vector-tile": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "204": {
            "description": "Lege tegel"
          },
          "400": {
            "description": "Ongeldige tegelcoördinaten of ongeldige `where`-expressie"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Onbekende dataset"
          },
          "422": {
            "description": "Zoomniveau te laag voor een externe bron"
          },
          "502": {
            "description": "Bron niet bereikbaar"
          }
        }
      }
    },
    "/api/public/v1/register": {
      "get": {
        "tags": [
          "Datasets"
        ],
        "summary": "Catalogusregister: externe datasetrecords uit landelijke catalogi",
        "description": "Doorzoekbaar register met records uit data.overheid.nl (DONL), PDOK, NED, Bodemloket en ArcGIS Living Atlas NL. `in_beheer=1` toont records die al als beheerde dataset in InfraBIDShare staan.",
        "parameters": [
          {
            "name": "zoek",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "thema",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "catalogus",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "bronhouder",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "wp",
            "in": "query",
            "description": "Werkpakketcode, bijv. WP04.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "in_beheer",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "0",
                "1"
              ]
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 500
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Registerrecords met paginering",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/public/v1/register/mapping": {
      "get": {
        "tags": [
          "Datasets"
        ],
        "summary": "Veldmapping van registerrecords naar het genormaliseerde datasetmodel",
        "description": "Geeft per veld van het datasetmodel (zoals gebruikt door `/api/public/v1/datasets`) welke registervelden de bron zijn, welke transformatie wordt toegepast en welke registervelden bewust niet worden overgenomen. Met `?record_id=` of `?voorbeelden=n` krijg je concrete gemapte voorbeelden terug.",
        "operationId": "getRegisterMapping",
        "parameters": [
          {
            "name": "record_id",
            "in": "query",
            "description": "Toon de mapping toegepast op één registerrecord.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "voorbeelden",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0,
              "maximum": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Mappingdefinitie en optionele voorbeelden",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Registerrecord niet gevonden"
          }
        }
      }
    },
    "/api/public/v1/register/{recordId}/schema": {
      "get": {
        "tags": [
          "Datasets"
        ],
        "summary": "Veldschema van één registerrecord voor filters en UI-formulieren",
        "description": "Geeft alle registervelden met genormaliseerd datatype, `json_type`, `ui_widget`-hint, toegestane operatoren en de huidige waarde van het record. Het `formulier`-blok bevat direct bruikbare lijsten: `zoekveld` (welke velden `?zoek=` doorzoekt), `filtervelden` (met de queryparameter op `/register`), `facetvelden` en paginatie-instellingen.\n\nIs het record gepromoveerd naar beheer (`in_beheer=true`), dan bevat `lagen[]` ook het volledige laagschema van de gekoppelde dataset en verwijst `links.dataset_schema` naar `/datasets/{dataset_id}/schema`.\n\n`record_id` mag zowel het `record_id` als de `dataset_sleutel` zijn.",
        "operationId": "getRegisterRecordSchema",
        "parameters": [
          {
            "name": "recordId",
            "in": "path",
            "required": true,
            "description": "`record_id` of `dataset_sleutel` van het registerrecord.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Veldschema, formulierdefinitie en eventueel laagschema",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Registerrecord niet gevonden"
          }
        }
      }
    },
    "/api/public/v1/register/import": {
      "post": {
        "tags": [
          "Datasets"
        ],
        "summary": "Importeert of ververst het catalogusregister vanuit een CSV-export",
        "description": "Haalt een puntkomma-gescheiden catalogusexport op en upsert de records op `record_id`. Zonder body wordt de standaard registerexport gebruikt.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "csv": {
                    "type": "string",
                    "description": "Inline CSV in plaats van een URL."
                  },
                  "batch": {
                    "type": "integer",
                    "minimum": 50,
                    "maximum": 1000,
                    "default": 500
                  },
                  "max": {
                    "type": "integer",
                    "description": "Maximaal aantal records (voor testruns)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Import afgerond",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "207": {
            "description": "Deels geslaagd; zie `fouten`"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "502": {
            "description": "Bron-CSV niet bereikbaar"
          }
        }
      }
    },
    "/api/public/v1/catalog": {
      "get": {
        "tags": [
          "Datasets"
        ],
        "summary": "Volledige catalogus: alle datasets met lagen, geometrietypen en veldinfo",
        "description": "Eén call met alle actieve datasets inclusief metadata (naam, bronhouder, thema, protocol, licentie), omhullende bbox, laagoverzicht met genormaliseerd geometrietype, veldinfo en directe links (detail, layers, tilejson, mbtiles, query). Bedoeld voor portalen die de catalogus periodiek synchroniseren.",
        "parameters": [
          {
            "name": "thema",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "protocol",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "bronhouder",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "zoek",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Kwaliteitsstatus of `alle` (standaard: gecontroleerd + vrijgegeven).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "compact",
            "in": "query",
            "description": "`1` levert per laag alleen titel, sleutel, geometrietype en aantal velden.",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Aantal datasets per pagina (standaard 100, maximaal 500).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Startpositie voor paginatie (standaard 0).",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Catalogus met facetten (over de volledige selectie), totalen, paginatieblok (`limit`, `offset`, `totaal`, `heeft_meer`, `volgende_url`) en datasets van de huidige pagina",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/public/v1/datasets/{datasetId}/mbtiles": {
      "get": {
        "tags": [
          "Datasets"
        ],
        "summary": "MBTiles-export (SQLite) met vector tiles voor een dataset",
        "description": "Genereert een MBTiles-bestand (SQLite, `format=pbf`) voor een bbox en zoomrange, zodat de lagen offline of op een eigen tileserver gerenderd kunnen worden. Maximaal 4000 tegels per export.",
        "parameters": [
          {
            "name": "datasetId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "bbox",
            "in": "query",
            "description": "`west,zuid,oost,noord` in EPSG:4326 (standaard: Nederland).",
            "schema": {
              "type": "string"
            },
            "example": "4.8,52.3,5.0,52.4"
          },
          {
            "name": "minzoom",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 8
            }
          },
          {
            "name": "maxzoom",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 13
            }
          },
          {
            "name": "lagen",
            "in": "query",
            "description": "Komma-gescheiden laagfilter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "where",
            "in": "query",
            "description": "Attributenfilter, zelfde syntax als bij de tile-endpoints.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "token",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "MBTiles-bestand (SQLite)",
            "content": {
              "application/vnd.mapbox-vector-tile+sqlite3": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Ongeldige zoomrange, bbox of `where`-expressie"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Onbekende dataset of geen features in dit gebied"
          },
          "422": {
            "description": "Te veel tegels gevraagd of zoomrange te grof voor externe bron"
          }
        }
      }
    },
    "/api/public/v1/datasets/{datasetId}/tilejson": {
      "get": {
        "tags": [
          "Datasets"
        ],
        "summary": "TileJSON 3.0 voor de vector tiles van een dataset",
        "description": "TileJSON-document met `tiles`-template, zoombereik, `vector_layers` en metadata (protocol, herkomst, beschikbare lagen). Direct bruikbaar als MapLibre-source.",
        "operationId": "getDatasetTileJson",
        "parameters": [
          {
            "name": "datasetId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lagen",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "where",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "example": "status = 'actief'"
          },
          {
            "name": "token",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "TileJSON-document",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Onbekende dataset"
          }
        }
      }
    },
    "/api/public/v1/wegkenmerken/{soort}": {
      "get": {
        "tags": [
          "Datasets"
        ],
        "summary": "NWB-wegvakkenmerken (maximumsnelheid, wegbreedte)",
        "description": "Levert niet-ruimtelijke wegvakkenmerken die het Django-portaal via `wvk_id` kan koppelen aan een wegvakkenlaag (bijv. `lokaal-voertuigbeperkingen` of het NWB).\n\nSoorten:\n- `maximumsnelheid`, 1.475.162 wegvakken met `maxshd`, `dat_maxshd`, `gme_naam`, `stt_naam`, `wegnummer`, `wegbehnaam`, `lengte`, `betrwbheid`.\n- `wegbreedte`, 1.427.086 wegvakken met `breedte`, `brdt_min`, `brdt_max`, `van`, `tot`, `betr`, `bron`.\n\nFilters: `wvk_id` (komma-gescheiden, max. 500 per verzoek), `gemeente` (alleen bij `maximumsnelheid`), `q`, `limit` (max. 5.000) en `offset` voor paginering.\n\nCaching en limieten: 10 minuten responscache (`X-Cache`, `ETag`, `304`), 60 verzoeken per minuut per client.\n\nVoorbeelden:\n- `GET /api/public/v1/wegkenmerken/maximumsnelheid?wvk_id=58170097,60175072`\n- `GET /api/public/v1/wegkenmerken/maximumsnelheid?gemeente=Vlissingen&limit=1000`\n- `GET /api/public/v1/wegkenmerken/wegbreedte?wvk_id=27142014`",
        "operationId": "getWegkenmerken",
        "parameters": [
          {
            "name": "soort",
            "in": "path",
            "required": true,
            "description": "`maximumsnelheid` of `wegbreedte`.",
            "schema": {
              "type": "string",
              "enum": [
                "maximumsnelheid",
                "wegbreedte"
              ]
            },
            "example": "maximumsnelheid"
          },
          {
            "name": "wvk_id",
            "in": "query",
            "description": "Komma-gescheiden wegvak-id's (max. 500).",
            "schema": {
              "type": "string"
            },
            "example": "58170097,60175072"
          },
          {
            "name": "gemeente",
            "in": "query",
            "description": "Gemeentenaam (deelmatch); alleen bij `maximumsnelheid`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Vrije tekstfilter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1000,
              "maximum": 5000
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Records met metadata en totaalaantal",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Onbekende soort"
          }
        }
      }
    },
    "/api/public/v1/netkaarten": {
      "get": {
        "tags": [
          "Datasets"
        ],
        "summary": "TenneT-netkaarten (services en PDF-downloads)",
        "description": "Levert de TenneT-netkaarten gebundeld: de WFS/ArcGIS-services van het hoogspanningsnet met per laag kant-en-klare GetFeature-URL's, plus de vier officiële PDF-netkaarten.\n\nFilteren kan met `lagen` (komma-gescheiden, matcht op titel, laagnaam of featuretype, deelmatch toegestaan) en `bbox` (de bbox wordt meegegeven in de WFS GetFeature-URL's en als envelope-query op ArcGIS-laag-URL's). De gebruikte filters komen terug in het `filters`-blok van de respons.\n\nCaching en limieten: responses worden 5 minuten gecachet per filtercombinatie (headers `X-Cache: HIT|MISS`, `ETag`; stuur `If-None-Match` voor `304 Not Modified`). Het endpoint kent een eigen limiet van 30 verzoeken per minuut per client, met `X-RateLimit-Limit`, `X-RateLimit-Remaining` en `Retry-After` bij `429`.\n\nVoorbeelden:\n- `GET /api/public/v1/netkaarten`\n- `GET /api/public/v1/netkaarten?lagen=hoogspanningsverbinding,station`\n- `GET /api/public/v1/netkaarten?bbox=4.2,51.8,5.1,52.4&bbox_crs=EPSG:4326`",
        "operationId": "getNetkaarten",
        "parameters": [
          {
            "name": "lagen",
            "in": "query",
            "description": "Komma-gescheiden laagfilters; matcht op titel, laagnaam of featuretype (deelmatch, hoofdletterongevoelig).",
            "schema": {
              "type": "string"
            },
            "example": "hoogspanningsverbinding,station"
          },
          {
            "name": "bbox",
            "in": "query",
            "description": "Bounding box `minx,miny,maxx,maxy` in de CRS van `bbox_crs`.",
            "schema": {
              "type": "string"
            },
            "example": "4.2,51.8,5.1,52.4"
          },
          {
            "name": "bbox_crs",
            "in": "query",
            "description": "CRS van de bbox.",
            "schema": {
              "type": "string",
              "default": "EPSG:4326",
              "enum": [
                "EPSG:4326",
                "EPSG:28992",
                "EPSG:3857"
              ]
            },
            "example": "EPSG:4326"
          }
        ],
        "responses": {
          "200": {
            "description": "Netkaarten (met `X-Cache`, `ETag` en `X-RateLimit-*` headers)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "304": {
            "description": "Niet gewijzigd (ETag-match)"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "description": "Te veel verzoeken; zie `Retry-After`"
          }
        }
      }
    },
    "/api/public/v1/ned": {
      "get": {
        "tags": [
          "Datasets"
        ],
        "summary": "NED energiedata (Nationaal Energie Dashboard)",
        "description": "Zonder parameters: metadata, energiedrager-lagen en de volledige parameterreferentie (point, type, granularity, classification, activity). Met `data=1` haalt de suite de energiedata server-side op bij api.ned.nl met de beheerde X-AUTH-TOKEN-sleutel, zodat het Django-portaal zelf geen NED-sleutel hoeft te bewaren.",
        "operationId": "getNedEnergiedata",
        "parameters": [
          {
            "name": "data",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "1 = proxy naar api.ned.nl/v1/utilizations"
          },
          {
            "name": "point",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            },
            "description": "0 = Nederland, 1-12 provincies, 14 offshore, 28-36 windparken"
          },
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            },
            "description": "Energiedrager, bijv. 1 Wind, 2 Solar, 17 WindOffshore"
          },
          {
            "name": "granularity",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 5
            },
            "description": "3 10min, 4 15min, 5 uur, 6 dag, 7 maand, 8 jaar"
          },
          {
            "name": "granularitytimezone",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "classification",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 2
            },
            "description": "1 Forecast, 2 Current"
          },
          {
            "name": "activity",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            },
            "description": "1 Providing, 2 Consuming, ..."
          },
          {
            "name": "validfrom[after]",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "validfrom[strictly_before]",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "NED-metadata of energiedata",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "502": {
            "description": "NED niet bereikbaar of sleutel geweigerd"
          },
          "503": {
            "description": "NED API-sleutel niet geconfigureerd"
          }
        }
      }
    },
    "/api/public/v1/energieonderbrekingen": {
      "get": {
        "tags": [
          "Datasets"
        ],
        "summary": "Energieonderbrekingen (storingen en gepland onderhoud)",
        "description": "Zonder parameters: metadata en de beschikbare endpoints/lagen van de v2-API van de gezamenlijke netbeheerders. Met `data=1` haalt de suite de storingen server-side op met het beheerde API-token, zodat het Django-portaal zelf geen token hoeft te bewaren.",
        "operationId": "getEnergieonderbrekingen",
        "parameters": [
          {
            "name": "data",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "1 = proxy naar energieonderbrekingen.nl/api/v2"
          },
          {
            "name": "pad",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "disruptions"
            },
            "description": "disruptions | disruptions/{id} | disruptions/{id}/history | suspects | suspects/{suspectId}"
          },
          {
            "name": "networkType",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Type net, bijv. electricity of gas"
          },
          {
            "name": "dso",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Netbeheerder"
          },
          {
            "name": "postalCode",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lat",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "lon",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "radius",
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Metadata of storingsdata",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "502": {
            "description": "Bron niet bereikbaar of token geweigerd"
          },
          "503": {
            "description": "API-token niet geconfigureerd"
          }
        }
      }
    },
    "/api/public/v1/work-packages/{code}/wfs": {
      "get": {
        "tags": [
          "Werkpakketten"
        ],
        "summary": "WFS-export per werkpakket",
        "description": "Serveert automatisch de vector-lagen (WFS, OGC API Features, REST) die aan het werkpakket gekoppeld zijn, inclusief kant-en-klare GetFeature-URL's met bbox en limiet. Met `f=geojson` worden de features live opgehaald en samengevoegd tot één FeatureCollection met `_dataset_id` per feature. Alleen actieve datasets met kwaliteitsstatus gecontroleerd of vrijgegeven.",
        "operationId": "getWorkPackageWfs",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "WP04"
          },
          {
            "name": "bbox",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "minx,miny,maxx,maxy in EPSG:4326. Standaard heel Nederland."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 2000,
              "default": 500
            }
          },
          {
            "name": "thema",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "datasets",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Kommagescheiden dataset_id's als extra filter."
          },
          {
            "name": "f",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "geojson"
              ],
              "default": "json"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Laagbeschrijvingen of samengevoegde GeoJSON",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Werkpakket niet gevonden"
          }
        }
      }
    },
    "/api/public/v1/work-packages/{code}/wmts": {
      "get": {
        "tags": [
          "Werkpakketten"
        ],
        "summary": "WMTS/WMS-export per werkpakket",
        "description": "Serveert de raster-lagen van het werkpakket met tegel-URL-templates ({z}/{x}/{y} voor WMTS, {bbox-epsg-3857} voor WMS), GetCapabilities-URL en previewbeeld. Direct bruikbaar in Leaflet, MapLibre of QGIS.",
        "operationId": "getWorkPackageWmts",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "WP04"
          },
          {
            "name": "bbox",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "minx,miny,maxx,maxy in EPSG:4326 voor de preview."
          },
          {
            "name": "thema",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "datasets",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tegel-lagen",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Werkpakket niet gevonden"
          }
        }
      }
    },
    "/api/public/v1/work-packages/{code}/kmz": {
      "get": {
        "tags": [
          "Werkpakketten"
        ],
        "summary": "KMZ-download per werkpakket",
        "description": "Genereert een KMZ (gezipte KML) met alle vrijgegeven vectordatasets van het werkpakket. Elke dataset is een eigen folder, gekleurd naar bronstatus. De bundel bevat status.csv met bronstatus per dataset en de voortgangsindicatie (dekking objectklassen, gereed rapportage-output, gezonde bronnen).",
        "operationId": "getWorkPackageKmz",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "WP04"
          },
          {
            "name": "bbox",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "minx,miny,maxx,maxy in EPSG:4326."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1000,
              "maximum": 5000
            }
          },
          {
            "name": "thema",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "datasets",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "f",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "kmz",
                "json"
              ]
            },
            "description": "json geeft alleen de status- en voortgangsmetadata terug."
          }
        ],
        "responses": {
          "200": {
            "description": "KMZ-bestand of metadata"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Werkpakket niet gevonden"
          }
        }
      }
    },
    "/api/public/v1/work-packages/{code}/shapefile": {
      "get": {
        "tags": [
          "Werkpakketten"
        ],
        "summary": "Shapefile-download (zip) per werkpakket",
        "description": "Genereert een zip met ESRI shapefiles (shp/shx/dbf/prj/cpg) per dataset en geometrietype (punt/lijn/vlak) in EPSG:4326, plus status.csv en LEESMIJ.txt met bronstatus en voortgangsindicatie. Attributen bevatten dataset-id, bronhouder, kwaliteit, bronstatus, actualiteit en het gereedheidspercentage van het werkpakket.",
        "operationId": "getWorkPackageShapefile",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "WP04"
          },
          {
            "name": "bbox",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "minx,miny,maxx,maxy in EPSG:4326."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1000,
              "maximum": 5000
            }
          },
          {
            "name": "thema",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "datasets",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "f",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "zip",
                "json"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Zip-bestand of metadata"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Werkpakket niet gevonden"
          }
        }
      }
    },
    "/api/public/v1/query": {
      "post": {
        "tags": [
          "Kaartquery"
        ],
        "summary": "Polygoonquery met cache-eerst strategie",
        "description": "Bevraagt één of meer datasets binnen een GeoJSON-polygoon plus bufferring. Een geldig cache-resultaat wordt direct geserveerd; anders wordt de bron bevraagd en het antwoord gecached voor de ingestelde geldigheidsduur (cache_ttl_minuten per dataset). Is de bron onbereikbaar, dan wordt een verlopen cache-resultaat teruggegeven.\n\n### Bronnen zonder bbox-items (zoekfallback)\n\nSommige bronnen kennen geen `/collections/{laag}/items` met bbox, de PDOK Kadaster Location API (`pdok-kadaster-location-api`) is puur een zoekdienst. Zulke datasets hebben in `/api/public/v1/datasets` het blok `query_ondersteuning` met `bbox_items: false`, `modus: \"zoek\"` en de `zoek_url`.\n\n- Zonder `zoekterm` geeft `/query` géén 404, maar een resultaat met `feature_count: 0` en een `foutmelding` die uitlegt dat een zoekterm nodig is; de respons bevat `zoekfallback_nodig: [\"<dataset_id>\"]`.\n- Met `zoekterm` (minimaal 2 tekens) wordt automatisch `/search?q=<zoekterm>&<collectie>[version]=1&bbox=...` aangeroepen en filtert de bbox de zoekresultaten binnen je polygoon (limiet max. 50 per aanroep).\n- Voorbeeld: `{ \"dataset_ids\": [\"pdok-kadaster-location-api\"], \"zoekterm\": \"Damrak\", \"geometry\": { \"type\": \"Polygon\", \"coordinates\": [[[4.88,52.36],[4.90,52.36],[4.90,52.38],[4.88,52.38],[4.88,52.36]]] } }`.",
        "operationId": "postQuery",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "dataset_ids",
                  "geometry"
                ],
                "properties": {
                  "dataset_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "example": [
                      "bgt",
                      "bag"
                    ]
                  },
                  "geometry": {
                    "type": "object",
                    "description": "GeoJSON Polygon in EPSG:4326.",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "Polygon"
                        ]
                      },
                      "coordinates": {
                        "type": "array",
                        "items": {
                          "type": "array",
                          "items": {
                            "type": "array",
                            "items": {
                              "type": "number"
                            }
                          }
                        }
                      }
                    }
                  },
                  "buffer_m": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Bufferring in meters; leeg = de standaardbuffer van de dataset."
                  },
                  "limiet": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 2000,
                    "default": 500
                  },
                  "ververs": {
                    "type": "boolean",
                    "default": false,
                    "description": "true omzeilt de cache en bevraagt de bron."
                  },
                  "zoekterm": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 2,
                    "maxLength": 200,
                    "description": "Zoekfallback voor bronnen zonder bbox-items (o.a. PDOK Location API). De bbox filtert de zoekresultaten.",
                    "example": "Damrak"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resultaat per dataset met cache-informatie",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "get": {
        "tags": [
          "Kaartquery"
        ],
        "summary": "Polygoonquery via queryparameters",
        "description": "Lichte variant van `POST /query` voor clients zonder request body: `?dataset_ids=`, `?bbox=` of `?geometry=` (GeoJSON), `?buffer_m=`, `?limiet=`, `?zoekterm=`.",
        "operationId": "getQuery",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/wegenlegger/{leggerId}/{formaat}": {
      "parameters": [
        {
          "name": "leggerId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "5f6b6f1a-0e5b-4f0e-8f47-6f7b3d8a1c22"
        },
        {
          "name": "formaat",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "gpkg"
        }
      ],
      "get": {
        "tags": [
          "Wegenlegger"
        ],
        "summary": "Oplevering van een vastgestelde wegenlegger",
        "description": "Levert een vastgestelde versie als xlsx, csv, kunstwerken-csv, geojson, gpkg, shapefile, nwb-csv, nwb-json of wfs. Authenticatie met het embedtoken van een klantportaal van dezelfde organisatie; concepten worden niet geserveerd.",
        "operationId": "getWegenleggerLeggerIdFormaat",
        "x-auth": "publiek",
        "security": [],
        "responses": {
          "200": {
            "description": "Gelukt"
          }
        }
      }
    },
    "/api/public/v1/datasets/{datasetId}": {
      "parameters": [
        {
          "name": "datasetId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "bgt"
        }
      ],
      "get": {
        "tags": [
          "Datasets"
        ],
        "summary": "Detail van één dataset",
        "description": "Geeft de volledige registratie van één dataset: metadata, protocol, endpoints, governance-status, lagen en de laatste bronstatus.",
        "operationId": "getDatasetsDatasetId",
        "x-required-scope": "catalog:read",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/object-classes": {
      "get": {
        "tags": [
          "Catalogus"
        ],
        "summary": "Objectklassen per dataset",
        "description": "Lijst met objectklassen (IMBOR/IMKL-achtige entiteiten) en de dataset waar ze bij horen.",
        "operationId": "getObjectClasses",
        "x-required-scope": "catalog:read",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/maplayers": {
      "get": {
        "tags": [
          "Catalogus"
        ],
        "summary": "Kaartlaagdefinities voor viewers",
        "description": "Kant-en-klare laagdefinities (basiskaarten, thema's, werkpakketlagen) voor de viewer-SDK en externe kaartclients.",
        "operationId": "getMaplayers",
        "x-required-scope": "catalog:read",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/governance": {
      "get": {
        "tags": [
          "Governance"
        ],
        "summary": "Governance-overzicht",
        "description": "Dataproducten, kwaliteitsstatus, begrippen en vrijgavestatus voor afnemers. Standaard alleen gecontroleerde en vrijgegeven records; `status=alle` vereist het recht `governance:override`.",
        "operationId": "getGovernance",
        "x-required-scope": "governance:read",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/work-packages": {
      "get": {
        "tags": [
          "Werkpakketten"
        ],
        "summary": "Werkpakketten en gekoppelde datasets",
        "operationId": "getWorkPackages",
        "x-required-scope": "catalog:read",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/sync": {
      "get": {
        "tags": [
          "Synchronisatie"
        ],
        "summary": "Wijzigingen sinds een tijdstempel",
        "description": "Incrementele synchronisatie voor portalen: geeft gewijzigde datasets, lagen en statussen sinds `?sinds=` (ISO-8601).",
        "operationId": "getSync",
        "x-required-scope": "refresh:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      },
      "post": {
        "tags": [
          "Synchronisatie"
        ],
        "summary": "Synchronisatieronde starten",
        "operationId": "postSync",
        "x-required-scope": "refresh:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/token": {
      "get": {
        "tags": [
          "Tokenbeheer"
        ],
        "summary": "Informatie over het gebruikte token",
        "description": "Toont label, scopes, dataset-ACL, versie, verloop- en respijtdatum van het aanroepende token.",
        "operationId": "getToken",
        "x-required-scope": "admin:tokens",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/embed/tokens": {
      "get": {
        "tags": [
          "Tokenbeheer"
        ],
        "summary": "Embedtokens van een klantportaal opvragen",
        "operationId": "getEmbedTokens",
        "x-required-scope": "admin:tokens",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      },
      "post": {
        "tags": [
          "Tokenbeheer"
        ],
        "summary": "Kortlevend embedtoken uitgeven",
        "operationId": "postEmbedTokens",
        "x-required-scope": "admin:tokens",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/embed/exchange": {
      "post": {
        "tags": [
          "Tokenbeheer"
        ],
        "summary": "Embedtoken inwisselen voor een kortlevende insluitcode",
        "description": "Roep dit endpoint server-side aan met het embedtoken (Authorization: Bearer emb_...). Het antwoord bevat een code die enkele minuten geldig is en aan het opgegeven domein gebonden wordt; plaats alleen die code in de iframe-URL (?code=...).",
        "operationId": "postEmbedExchange",
        "x-auth": "publiek",
        "security": [],
        "responses": {
          "200": {
            "description": "Gelukt"
          }
        }
      }
    },
    "/api/public/v1/embed/valideer": {
      "post": {
        "tags": [
          "Tokenbeheer"
        ],
        "summary": "Embedtoken en viewer valideren tegen de portalinstellingen",
        "description": "Server-side validatie voor externe portalen. Body: { token, viewer?, ip?, domein?, doel? (galerij|viewer|dataset), dataset_ref? }. Het antwoord (altijd HTTP 200 bij een uitgevoerde validatie) bevat `ok`, de verwachte `status` en `code` van de echte insluiting, de bijbehorende `uitleg` (titel/melding/oplossing), de afzonderlijke controles in `stappen`, bij succes `zichtbaar` en een kant-en-klare `embed_url`, plus `catalogus` met alle mogelijke foutcodes.",
        "operationId": "postEmbedValideer",
        "x-auth": "publiek",
        "security": [],
        "responses": {
          "200": {
            "description": "Gelukt"
          }
        }
      }
    },
    "/api/public/v1/lokaal/{sleutel}/wfs": {
      "parameters": [
        {
          "name": "sleutel",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "lokaal-bushaltes"
        }
      ],
      "get": {
        "tags": [
          "OGC-services"
        ],
        "summary": "WFS 2.0.0 op een lokale dataset (GetCapabilities/GetFeature)",
        "operationId": "getLokaalSleutelWfs",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/lokaal/{sleutel}/wms": {
      "parameters": [
        {
          "name": "sleutel",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "lokaal-bushaltes"
        }
      ],
      "get": {
        "tags": [
          "OGC-services"
        ],
        "summary": "WMS 1.3.0 op een lokale dataset (GetCapabilities/GetMap/GetFeatureInfo)",
        "operationId": "getLokaalSleutelWms",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/lokaal/{sleutel}/wmts": {
      "parameters": [
        {
          "name": "sleutel",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "lokaal-bushaltes"
        }
      ],
      "get": {
        "tags": [
          "OGC-services"
        ],
        "summary": "WMTS 1.0.0 op een lokale dataset",
        "operationId": "getLokaalSleutelWmts",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/ogc/{workspace}": {
      "parameters": [
        {
          "name": "workspace",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "standaard"
        }
      ],
      "get": {
        "tags": [
          "OGC-services"
        ],
        "summary": "Landing page van de OGC API Features-workspace",
        "operationId": "getOgcWorkspace",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/ogc/{workspace}/conformance": {
      "parameters": [
        {
          "name": "workspace",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "standaard"
        }
      ],
      "get": {
        "tags": [
          "OGC-services"
        ],
        "summary": "Conformance-klassen van de workspace",
        "operationId": "getOgcWorkspaceConformance",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/ogc/{workspace}/collections": {
      "parameters": [
        {
          "name": "workspace",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "standaard"
        }
      ],
      "get": {
        "tags": [
          "OGC-services"
        ],
        "summary": "Collecties (lagen) in de workspace",
        "operationId": "getOgcWorkspaceCollections",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/ogc/{workspace}/collections/{laag}": {
      "parameters": [
        {
          "name": "workspace",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "standaard"
        },
        {
          "name": "laag",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "wegvakken"
        }
      ],
      "get": {
        "tags": [
          "OGC-services"
        ],
        "summary": "Metadata van één collectie",
        "operationId": "getOgcWorkspaceCollectionsLaag",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/ogc/{workspace}/collections/{laag}/items": {
      "parameters": [
        {
          "name": "workspace",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "standaard"
        },
        {
          "name": "laag",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "wegvakken"
        }
      ],
      "get": {
        "tags": [
          "OGC-services"
        ],
        "summary": "Features opvragen (OGC API Features)",
        "description": "Ondersteunt `bbox`, `limit`, `offset`, `datetime` en attributenfilters.",
        "operationId": "getOgcWorkspaceCollectionsLaagItems",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      },
      "post": {
        "tags": [
          "OGC-services"
        ],
        "summary": "Object of objecten toevoegen aan een lokale collectie",
        "description": "Accepteert een GeoJSON Feature (antwoord 201 met `Location`) of een FeatureCollection om meerdere objecten in één keer toe te voegen.",
        "operationId": "postOgcWorkspaceCollectionsLaagItems",
        "x-required-scope": "features:write",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/ogc/{workspace}/collections/{laag}/items/{fid}": {
      "parameters": [
        {
          "name": "workspace",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "standaard"
        },
        {
          "name": "laag",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "wegvakken"
        },
        {
          "name": "fid",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "1024"
        }
      ],
      "get": {
        "tags": [
          "OGC-services"
        ],
        "summary": "Eén object opvragen",
        "description": "Geeft het object met een `ETag`. Stuur die waarde bij het bewerken mee in `If-Match`, dan krijg je 412 als iemand anders het object intussen wijzigde.",
        "operationId": "getOgcWorkspaceCollectionsLaagItemsFid",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      },
      "put": {
        "tags": [
          "OGC-services"
        ],
        "summary": "Object vervangen (OGC API Features Part 4)",
        "operationId": "putOgcWorkspaceCollectionsLaagItemsFid",
        "x-required-scope": "features:write",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      },
      "patch": {
        "tags": [
          "OGC-services"
        ],
        "summary": "Object deels bijwerken",
        "description": "Meegegeven attributen worden samengevoegd met de bestaande waarden.",
        "operationId": "patchOgcWorkspaceCollectionsLaagItemsFid",
        "x-required-scope": "features:write",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      },
      "delete": {
        "tags": [
          "OGC-services"
        ],
        "summary": "Object verwijderen",
        "operationId": "deleteOgcWorkspaceCollectionsLaagItemsFid",
        "x-required-scope": "features:write",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/qfield/pakketten": {
      "parameters": [],
      "get": {
        "tags": [
          "OGC-services"
        ],
        "summary": "Veldwerkpakketten opvragen",
        "description": "Met `?workspace=` beperk je de lijst tot één werkomgeving.",
        "operationId": "getQfieldPakketten",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      },
      "post": {
        "tags": [
          "OGC-services"
        ],
        "summary": "Veldwerkpakket voor QField aanmaken",
        "description": "Kies de lagen, eventueel een `bbox` en de geldigheidsduur. Het pakket is daarna te downloaden als GeoPackage.",
        "operationId": "postQfieldPakketten",
        "x-required-scope": "features:write",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/qfield/pakketten/{id}/download": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "6f2e1c8a-1d4b-4b3e-8a91-5c2d7e0b9a11"
        }
      ],
      "get": {
        "tags": [
          "OGC-services"
        ],
        "summary": "Pakket downloaden (GeoPackage of GeoJSON)",
        "description": "Standaard een `.gpkg` die je direct in QGIS of QField opent; met `?formaat=geojson` krijg je één FeatureCollection.",
        "operationId": "getQfieldPakkettenIdDownload",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/qfield/pakketten/{id}/sync": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "6f2e1c8a-1d4b-4b3e-8a91-5c2d7e0b9a11"
        }
      ],
      "post": {
        "tags": [
          "OGC-services"
        ],
        "summary": "Veldwijzigingen terugmelden",
        "description": "Stuur een FeatureCollection terug. Objecten met `_id` en `_versie` worden bijgewerkt, zonder `_id` toegevoegd en met `_actie: \"verwijder\"` verwijderd. Bij een versieverschil komt er 409 met de conflicten.",
        "operationId": "postQfieldPakkettenIdSync",
        "x-required-scope": "features:write",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/ogc/{workspace}/wfs": {
      "parameters": [
        {
          "name": "workspace",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "standaard"
        }
      ],
      "get": {
        "tags": [
          "OGC-services"
        ],
        "summary": "WFS 2.0.0 van de workspace",
        "operationId": "getOgcWorkspaceWfs",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      },
      "post": {
        "tags": [
          "OGC-services"
        ],
        "summary": "WFS-T transactie (Insert/Update/Delete)",
        "operationId": "postOgcWorkspaceWfs",
        "x-required-scope": "features:write",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/ogc/{workspace}/wms": {
      "parameters": [
        {
          "name": "workspace",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "standaard"
        }
      ],
      "get": {
        "tags": [
          "OGC-services"
        ],
        "summary": "WMS 1.3.0 van de workspace",
        "operationId": "getOgcWorkspaceWms",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/ogc/{workspace}/wmts": {
      "parameters": [
        {
          "name": "workspace",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "standaard"
        }
      ],
      "get": {
        "tags": [
          "OGC-services"
        ],
        "summary": "WMTS 1.0.0 van de workspace",
        "operationId": "getOgcWorkspaceWmts",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/ogc/{workspace}/qgis": {
      "parameters": [
        {
          "name": "workspace",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "standaard"
        }
      ],
      "get": {
        "tags": [
          "OGC-services"
        ],
        "summary": "QGIS-projectbestand of laaglijst voor de workspace",
        "operationId": "getOgcWorkspaceQgis",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/ogc/{workspace}/styles/{bestand}": {
      "parameters": [
        {
          "name": "workspace",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "standaard"
        },
        {
          "name": "bestand",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "wegvakken.sld"
        }
      ],
      "get": {
        "tags": [
          "OGC-services"
        ],
        "summary": "SLD/QML-stijlbestand van een laag",
        "operationId": "getOgcWorkspaceStylesBestand",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/jobs/claim": {
      "post": {
        "tags": [
          "Verwerking"
        ],
        "summary": "Volgende Python-verwerkingsjob claimen",
        "description": "Alleen voor de verwerkingsworker; authenticatie via de header `x-worker-token`.",
        "operationId": "postJobsClaim",
        "x-auth": "worker",
        "security": [],
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "description": "Ongeldig workertoken of job-capability"
          },
          "409": {
            "description": "Job bevindt zich niet in de verwachte status"
          }
        }
      }
    },
    "/api/public/v1/worker/health": {
      "post": {
        "tags": [
          "Verwerking"
        ],
        "summary": "Versie en gezondheid van de verwerkingsworker melden",
        "description": "Alleen voor de verwerkingsworker; authenticatie via de header `x-worker-token`. De worker meldt zijn versielabel en onderdelen en krijgt een eventueel klaargezet herstartcommando terug.",
        "operationId": "postWorkerHealth",
        "x-auth": "worker",
        "security": [],
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "description": "Ongeldig workertoken of job-capability"
          },
          "409": {
            "description": "Job bevindt zich niet in de verwachte status"
          }
        }
      }
    },
    "/api/public/v1/db-health": {
      "get": {
        "tags": [
          "Monitoring"
        ],
        "summary": "Databasegezondheid (latency en signalen)",
        "description": "Lichte gezondheidscheck van de database: responstijd per probe, SLA-drempels en signalen zoals statement timeouts of een niet-laadbare schemacache. Status 200 bij `ok`/`degraded`, 503 bij `kritiek`. Bevat geen data of configuratie.",
        "operationId": "getDbHealth",
        "x-auth": "publiek",
        "security": [],
        "responses": {
          "200": {
            "description": "Gelukt"
          }
        }
      }
    },
    "/api/public/v1/analyse/algoritmes": {
      "get": {
        "tags": [
          "Analyse"
        ],
        "summary": "Beschikbare QGIS-algoritmen",
        "description": "Catalogus van de QGIS-processing algoritmen die het platform aanbiedt, inclusief parameters en het benodigde aantal invoerlagen. Met `?algoritme=` haal je één definitie op, met `?categorie=` filter je de lijst.",
        "operationId": "getAnalyseAlgoritmes",
        "x-auth": "publiek",
        "security": [],
        "responses": {
          "200": {
            "description": "Gelukt"
          }
        }
      },
      "post": {
        "tags": [
          "Analyse"
        ],
        "summary": "Parameters van een algoritme valideren",
        "description": "Controleert een parameterset tegen de definitie van het gekozen algoritme en geeft per parameter een leesbare fout plus de genormaliseerde waarden.",
        "operationId": "postAnalyseAlgoritmes",
        "x-auth": "publiek",
        "security": [],
        "responses": {
          "200": {
            "description": "Gelukt"
          }
        }
      }
    },
    "/api/public/v1/themas": {
      "get": {
        "tags": [
          "Thema's"
        ],
        "summary": "Geharmoniseerde thema's",
        "description": "Lijst met gepubliceerde thema's: één doelschema per thema, gevuld uit meerdere deelbronnen.",
        "operationId": "getThemas",
        "x-required-scope": "catalog:read",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/themas/{code}": {
      "parameters": [
        {
          "name": "code",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Thema's"
        ],
        "summary": "Doelschema en deelbronnen van een thema",
        "description": "Geeft de velden van het doelschema en per deelbron het gebied, de actualiteit en het aantal objecten.",
        "operationId": "getThemasCode",
        "x-required-scope": "catalog:read",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/themas/{code}/dekking": {
      "parameters": [
        {
          "name": "code",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Thema's"
        ],
        "summary": "Dekking toetsen voor een onderzoeksgebied",
        "description": "Toetst een Polygon of MultiPolygon tegen de dekking van de deelbronnen en geeft het gedekte percentage, de betrokken bronnen en de gaten terug.",
        "operationId": "postThemasCodeDekking",
        "x-required-scope": "catalog:read",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/dataproducten/sjablonen": {
      "get": {
        "tags": [
          "Dataproducten"
        ],
        "summary": "Beschikbare productsjablonen",
        "description": "Geeft de sjablonen die deze sleutel mag gebruiken: de standaardsjablonen van het platform en de eigen sjablonen van de organisatie, met code, standaardbuffer en de uitvoervormen.",
        "operationId": "getDataproductenSjablonen",
        "x-required-scope": "dataproducts:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/dataproducten/runs": {
      "post": {
        "tags": [
          "Dataproducten"
        ],
        "summary": "Dataproduct samenstellen",
        "description": "Start een dataproduct voor een aangeleverd gebied (GeoJSON Polygon, MultiPolygon, Feature of FeatureCollection). Velden: `sjabloon` (code of id), `naam`, `gebied`, optioneel `buffer_m`, `callback_url` en `wacht`. Zonder `wacht` volgt 202 met een statuslink; met `wacht` wordt het volledige resultaat teruggegeven. Een `callback_url` krijgt bij afronding een POST met de header `X-InfraBIDShare-Signature` (HMAC-SHA256 over de body).",
        "operationId": "postDataproductenRuns",
        "x-required-scope": "dataproducts:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/dataproducten/runs/{runId}": {
      "parameters": [
        {
          "name": "runId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Dataproducten"
        ],
        "summary": "Status en resultaat van een dataproduct",
        "description": "Geeft de status, de voortgang, de samenvatting, de bronstatus per onderdeel en, zodra het product klaar is, de downloadlinks. Alleen binnen de organisatie van de sleutel.",
        "operationId": "getDataproductenRunsRunId",
        "x-required-scope": "dataproducts:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/dataproducten/runs/{runId}/download/{formaat}": {
      "parameters": [
        {
          "name": "runId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "formaat",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "pdf"
        }
      ],
      "get": {
        "tags": [
          "Dataproducten"
        ],
        "summary": "Dataproduct downloaden",
        "description": "Levert het product als `pdf` (rapport), `gpkg`, `xlsx` of `geojson`. Hetzelfde bestand als in de suite.",
        "operationId": "getDataproductenRunsRunIdDownloadFormaat",
        "x-required-scope": "dataproducts:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/dataproducten/demo/download/{formaat}": {
      "parameters": [
        {
          "name": "formaat",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "pdf"
        }
      ],
      "get": {
        "tags": [
          "Dataproducten"
        ],
        "summary": "Voorbeeldproduct van de demo downloaden",
        "description": "Levert het vaste voorbeeldproduct van de publieke demo als `pdf`, `gpkg`, `xlsx` of `geojson`. Geen sleutel nodig; dit endpoint kan uitsluitend bij die ene demorun en kent een limiet per bezoeker.",
        "operationId": "getDataproductenDemoDownloadFormaat",
        "x-auth": "publiek",
        "security": [],
        "responses": {
          "200": {
            "description": "Gelukt"
          }
        }
      }
    },
    "/api/public/v1/analyse/runs": {
      "post": {
        "tags": [
          "Analyse"
        ],
        "summary": "QGIS-analyse starten",
        "description": "Zet een analyse in de wachtrij op basis van datasets, een polygoon en algoritmeparameters. Antwoordt met de analyse-id; het resultaat haal je op via GET /analyse/runs/{runId}.",
        "operationId": "postAnalyseRuns",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/analyse/runs/{runId}": {
      "parameters": [
        {
          "name": "runId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Analyse"
        ],
        "summary": "Status en resultaat van een analyse",
        "description": "Geeft de status, het aantal objecten, het logboek en, zodra klaar, het resultaat als GeoJSON plus downloadlinks voor Shapefile en KMZ.",
        "operationId": "getAnalyseRunsRunId",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/analyse/runs/{runId}/tilejson": {
      "parameters": [
        {
          "name": "runId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Analyse"
        ],
        "summary": "TileJSON van een analyseresultaat",
        "description": "Beschrijft de vector tiles van een analyseresultaat: tegel-URL's (MVT en GeoJSON), bbox, aantal objecten en de automatische symbologie. Authenticatie via API-token of het embedtoken van het portaal (`?token=`).",
        "operationId": "getAnalyseRunsRunIdTilejson",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/analyse/runs/{runId}/export/{formaat}": {
      "parameters": [
        {
          "name": "runId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "formaat",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "shapefile"
        }
      ],
      "get": {
        "tags": [
          "Analyse"
        ],
        "summary": "Analyseresultaat downloaden",
        "description": "Levert het resultaat van één analyse als `geojson`, `shapefile` (zip), `kmz`, `kml` of `cog` (Cloud Optimized GeoTIFF bij rasteruitvoer). Authenticatie via API-token of het embedtoken van het portaal (`?token=`).",
        "operationId": "getAnalyseRunsRunIdExportFormaat",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/analyse/runs/{runId}/tiles/{z}/{x}/{y}": {
      "parameters": [
        {
          "name": "runId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "z",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "12"
        },
        {
          "name": "x",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "2103"
        },
        {
          "name": "y",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "1345.mvt"
        }
      ],
      "get": {
        "tags": [
          "Analyse"
        ],
        "summary": "Vector tile van een analyseresultaat",
        "description": "Levert één tegel als Mapbox Vector Tile (`.mvt`/`.pbf`) of als vereenvoudigde GeoJSON (`.geojson`), zodat de insluitbare viewer ook grote resultaten vloeiend kan inzoomen.",
        "operationId": "getAnalyseRunsRunIdTilesZXY",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/analyse/runs/{runId}/export-jobs": {
      "parameters": [
        {
          "name": "runId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Analyse"
        ],
        "summary": "Exportjob starten",
        "description": "Zet een asynchrone export in de wachtrij voor `geojson`, `shapefile`, `kmz`, `kml` of `cog`. Antwoordt met de exportjob (status `wachtend`); volg de voortgang via GET /analyse/export-jobs/{jobId}.",
        "operationId": "postAnalyseRunsRunIdExportJobs",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      },
      "get": {
        "tags": [
          "Analyse"
        ],
        "summary": "Exportjobs van een analyse",
        "description": "Overzicht van de recente exportjobs van deze analyse met status, voortgang en downloadlink.",
        "operationId": "getAnalyseRunsRunIdExportJobs",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/analyse/export-jobs/{jobId}": {
      "parameters": [
        {
          "name": "jobId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Analyse"
        ],
        "summary": "Status en voortgang van een exportjob",
        "description": "Geeft status (`wachtend`, `bezig`, `gelukt`, `mislukt`), voortgangspercentage, melding en, zodra klaar, de downloadlink en bestandsgrootte.",
        "operationId": "getAnalyseExportJobsJobId",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/analyse/export-jobs/{jobId}/download": {
      "parameters": [
        {
          "name": "jobId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Analyse"
        ],
        "summary": "Exportbestand ophalen",
        "description": "Stuurt een 302-redirect naar een kortlevende presigned URL (5 minuten) waarvandaan het exportbestand rechtstreeks streamt. Met ?json=1 krijg je in plaats daarvan de link als JSON. Zolang de job nog loopt antwoordt dit endpoint met 409.",
        "operationId": "getAnalyseExportJobsJobIdDownload",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/analyse/claim": {
      "post": {
        "tags": [
          "Analyse"
        ],
        "summary": "Volgende QGIS-analyse claimen",
        "description": "Alleen voor de analyse-worker; authenticatie via `x-worker-token`.",
        "operationId": "postAnalyseClaim",
        "x-auth": "worker",
        "security": [],
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "description": "Ongeldig workertoken of job-capability"
          },
          "409": {
            "description": "Job bevindt zich niet in de verwachte status"
          }
        }
      }
    },
    "/api/public/v1/analyse/runs/{runId}/complete": {
      "parameters": [
        {
          "name": "runId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Analyse"
        ],
        "summary": "QGIS-analyse afronden (idempotent)",
        "description": "Worker-endpoint; vereist `x-worker-token`, `x-worker-id` en de analysesleutel `x-job-capability`.",
        "operationId": "postAnalyseRunsRunIdComplete",
        "x-auth": "worker",
        "security": [],
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "description": "Ongeldig workertoken of job-capability"
          },
          "409": {
            "description": "Job bevindt zich niet in de verwachte status"
          }
        }
      }
    },
    "/api/public/v1/jobs/{jobId}/progress": {
      "parameters": [
        {
          "name": "jobId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Verwerking"
        ],
        "summary": "Voortgang en logregels van een job melden",
        "description": "Worker-endpoint; vereist `x-worker-token` en de job-capability `x-job-capability`.",
        "operationId": "postJobsJobIdProgress",
        "x-auth": "worker",
        "security": [],
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "description": "Ongeldig workertoken of job-capability"
          },
          "409": {
            "description": "Job bevindt zich niet in de verwachte status"
          }
        }
      }
    },
    "/api/public/v1/jobs/{jobId}/upload-url": {
      "parameters": [
        {
          "name": "jobId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Verwerking"
        ],
        "summary": "Uploadlocatie voor een joboutput opvragen",
        "operationId": "postJobsJobIdUploadUrl",
        "x-auth": "worker",
        "security": [],
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "description": "Ongeldig workertoken of job-capability"
          },
          "409": {
            "description": "Job bevindt zich niet in de verwachte status"
          }
        }
      }
    },
    "/api/public/v1/jobs/{jobId}/complete": {
      "parameters": [
        {
          "name": "jobId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Verwerking"
        ],
        "summary": "Job afronden (idempotent)",
        "description": "Zet de job op geslaagd of mislukt en registreert de outputs. Herhaalde aanroepen zijn veilig.",
        "operationId": "postJobsJobIdComplete",
        "x-auth": "worker",
        "security": [],
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "description": "Ongeldig workertoken of job-capability"
          },
          "409": {
            "description": "Job bevindt zich niet in de verwachte status"
          }
        }
      }
    },
    "/api/public/v1/jobs/{jobId}/outputs/{naam}": {
      "parameters": [
        {
          "name": "jobId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "naam",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "resultaat.geojson"
        }
      ],
      "get": {
        "tags": [
          "Verwerking"
        ],
        "summary": "Joboutput downloaden (GeoJSON, Shapefile, KMZ)",
        "operationId": "getJobsJobIdOutputsNaam",
        "x-required-scope": "query:execute",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/viewer.js": {
      "get": {
        "tags": [
          "Integratie"
        ],
        "summary": "Viewer-SDK (JavaScript) voor embedding in externe portalen",
        "description": "Publiek script zonder token; de embed zelf authenticeert met een kortlevend embedtoken.",
        "operationId": "getViewerJs",
        "x-auth": "publiek",
        "security": [],
        "responses": {
          "200": {
            "description": "Gelukt"
          }
        }
      }
    },
    "/api/public/v1/layers.js": {
      "get": {
        "tags": [
          "Integratie"
        ],
        "summary": "Laaghelper-script voor Leaflet-integraties",
        "operationId": "getLayersJs",
        "x-auth": "publiek",
        "security": [],
        "responses": {
          "200": {
            "description": "Gelukt"
          }
        }
      }
    },
    "/api/public/v1/openapi.json": {
      "get": {
        "tags": [
          "Integratie"
        ],
        "summary": "Deze OpenAPI-specificatie",
        "operationId": "getOpenapiJson",
        "x-auth": "publiek",
        "security": [],
        "responses": {
          "200": {
            "description": "Gelukt"
          }
        }
      }
    },
    "/api/public/v1/versie": {
      "get": {
        "tags": [
          "Integratie"
        ],
        "summary": "Versie van de draaiende build",
        "description": "Geeft versie, commit, bouwmoment en omgeving van de live build. De uitrolstap in CI gebruikt dit om te bevestigen dat de nieuwe release daadwerkelijk actief is.",
        "operationId": "getVersie",
        "x-auth": "publiek",
        "security": [],
        "responses": {
          "200": {
            "description": "Gelukt"
          }
        }
      }
    },
    "/api/public/v1/dcat": {
      "get": {
        "tags": [
          "Interoperabiliteit"
        ],
        "summary": "DCAT-AP-NL 3.0-catalogusexport",
        "description": "Exporteert de actieve, vrijgegeven of gecontroleerde datasets als DCAT-AP-NL 3.0-catalogus. `?format=jsonld` (standaard) levert application/ld+json, `?format=xml` levert application/rdf+xml.",
        "operationId": "getDcat",
        "x-required-scope": "catalog:read",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/records": {
      "get": {
        "tags": [
          "Interoperabiliteit"
        ],
        "summary": "OGC API - Records landingspagina",
        "operationId": "getRecords",
        "x-required-scope": "catalog:read",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/records/conformance": {
      "get": {
        "tags": [
          "Interoperabiliteit"
        ],
        "summary": "OGC API - Records conformanceklassen",
        "operationId": "getRecordsConformance",
        "x-required-scope": "catalog:read",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/records/collections": {
      "get": {
        "tags": [
          "Interoperabiliteit"
        ],
        "summary": "OGC API - Records collecties",
        "description": "Bevat de collectie `datasets` met omschrijving, extent en aantal records.",
        "operationId": "getRecordsCollections",
        "x-required-scope": "catalog:read",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/records/collections/datasets/items": {
      "get": {
        "tags": [
          "Interoperabiliteit"
        ],
        "summary": "Datasets als OGC-records (GeoJSON)",
        "description": "Ondersteunt `q`, `bbox`, `limit` (max 500) en `offset`.",
        "operationId": "getRecordsCollectionsDatasetsItems",
        "x-required-scope": "catalog:read",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    },
    "/api/public/v1/records/collections/datasets/items/{recordId}": {
      "parameters": [
        {
          "name": "recordId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "example": "bgt"
        }
      ],
      "get": {
        "tags": [
          "Interoperabiliteit"
        ],
        "summary": "Eén dataset als OGC-record (GeoJSON)",
        "operationId": "getRecordsCollectionsDatasetsItemsRecordId",
        "x-required-scope": "catalog:read",
        "responses": {
          "200": {
            "description": "Gelukt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token mist de vereiste scope"
          },
          "429": {
            "description": "Rate limit bereikt"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API-token uit de beheer-UI."
      },
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      }
    },
    "parameters": {
      "DatasetIdPath": {
        "name": "datasetId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "maxLength": 80
        },
        "example": "bgt"
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Ongeldige queryparameters",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Ontbrekend of ongeldig API-token",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ]
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "page": {
            "type": "integer"
          },
          "per_page": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          },
          "total_pages": {
            "type": "integer"
          }
        }
      },
      "LayerField": {
        "type": "object",
        "description": "Attribuutveld van een laag.",
        "properties": {
          "naam": {
            "type": "string",
            "example": "identificatie"
          },
          "type": {
            "type": [
              "string",
              "null"
            ],
            "example": "string"
          },
          "titel": {
            "type": [
              "string",
              "null"
            ],
            "example": "Identificatie"
          },
          "verplicht": {
            "type": [
              "boolean",
              "null"
            ]
          }
        },
        "required": [
          "naam"
        ]
      },
      "DatasetLayer": {
        "type": "object",
        "description": "Laag met alle metadata die een portaal nodig heeft om titel, velden en geometrie te tonen.",
        "properties": {
          "titel": {
            "type": "string",
            "example": "Wegvakken"
          },
          "laagnaam": {
            "type": [
              "string",
              "null"
            ],
            "description": "WMS/OGC-laagnaam."
          },
          "featuretype": {
            "type": [
              "string",
              "null"
            ],
            "description": "WFS-featuretype."
          },
          "sleutel": {
            "type": "string",
            "description": "Waarde voor de `lagen`-queryparameter."
          },
          "toelichting": {
            "type": [
              "string",
              "null"
            ]
          },
          "crs": {
            "type": "string",
            "example": "EPSG:28992"
          },
          "standaard": {
            "type": "boolean"
          },
          "geometrietype": {
            "type": "string",
            "enum": [
              "punt",
              "lijn",
              "vlak",
              "multi",
              "raster",
              "onbekend"
            ]
          },
          "geojson_type": {
            "type": [
              "string",
              "null"
            ],
            "example": "LineString"
          },
          "bevraagbaar": {
            "type": "boolean",
            "description": "true als de laag als vectorfeatures opvraagbaar is."
          },
          "velden": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LayerField"
            }
          },
          "veldnamen": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "bbox": {
            "type": [
              "array",
              "null"
            ],
            "minItems": 4,
            "maxItems": 4,
            "items": {
              "type": "number"
            }
          },
          "min_zoom": {
            "type": [
              "integer",
              "null"
            ]
          },
          "max_zoom": {
            "type": [
              "integer",
              "null"
            ]
          },
          "metadata_bijgewerkt_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "metadata_volledig": {
            "type": "boolean"
          },
          "links": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "tiles": {
                "type": "string",
                "description": "MVT-template met {z}/{x}/{y}."
              },
              "tilejson": {
                "type": "string"
              },
              "query": {
                "type": "string"
              }
            }
          }
        }
      },
      "DatasetLayersSummary": {
        "type": "object",
        "properties": {
          "aantal": {
            "type": "integer"
          },
          "bevraagbaar": {
            "type": "integer"
          },
          "met_velden": {
            "type": "integer"
          },
          "metadata_volledig": {
            "type": "integer"
          },
          "per_geometrietype": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          }
        }
      },
      "DatasetLayersResponse": {
        "type": "object",
        "properties": {
          "dataset": {
            "type": "object",
            "properties": {
              "dataset_id": {
                "type": "string"
              },
              "naam": {
                "type": "string"
              },
              "protocol": {
                "type": "string"
              },
              "endpoint_url": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "feature_endpoint_url": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "crs": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "aantal": {
            "type": "integer"
          },
          "samenvatting": {
            "$ref": "#/components/schemas/DatasetLayersSummary"
          },
          "geexporteerd_at": {
            "type": "string",
            "format": "date-time"
          },
          "lagen": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DatasetLayer"
            }
          }
        }
      },
      "HealthItem": {
        "type": "object",
        "properties": {
          "dataset_id": {
            "type": "string",
            "example": "bgt"
          },
          "naam": {
            "type": "string",
            "example": "Basisregistratie Grootschalige Topografie"
          },
          "protocol": {
            "type": "string",
            "example": "wfs"
          },
          "endpoint_url": {
            "type": "string",
            "format": "uri"
          },
          "actief": {
            "type": "boolean"
          },
          "laatste_health_status": {
            "type": "string",
            "enum": [
              "onbekend",
              "gezond",
              "traag",
              "storing",
              "vervallen"
            ]
          },
          "laatste_health_check_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "laatste_responstijd_ms": {
            "type": [
              "integer",
              "null"
            ],
            "example": 412
          },
          "controle_verouderd": {
            "type": "boolean",
            "description": "true als de laatste controle ouder is dan 24 uur."
          },
          "planning": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "frequentie": {
                "type": "string",
                "example": "60m"
              },
              "automatisch": {
                "type": "boolean"
              },
              "volgende_run_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "laatste_run_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "laatste_run_status": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "HealthResponse": {
        "type": "object",
        "properties": {
          "count": {
            "type": "integer"
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          },
          "samenvatting": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "example": {
              "gezond": 42,
              "traag": 3,
              "storing": 1
            }
          },
          "gemiddelde_responstijd_ms": {
            "type": [
              "integer",
              "null"
            ]
          },
          "opgehaald_op": {
            "type": "string",
            "format": "date-time"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HealthItem"
            }
          }
        }
      }
    }
  }
}