API
De browsable BROSTAR API is te vinden op https://www.brostar.nl/api/ of op https://www.staging.brostar.nl/api/
API Keys
Voor elke request die op de API gedaan wordt, moet een API key gebruikt worden om de gebruiker te authenticeren. Een API key is gebruikersgebonden en kunnen momenteel alleen aangevraagd worden bij Nelen & Schuurmans. Het eigenhandig beheren van API keys in de API en frontend zal spoedig worden ontwikkeld.
Een request op de BROSTAR API, inclusief een API Key, ziet er als volgt uit:
import requests
from requests.auth import HTTPBasicAuth
BROSTAR_API_KEY = ... # Verberg altijd je API Key!
auth = HTTPBasicAuth(
username="__key__",
password=BROSTAR_API_KEY,
)
url = "https://www.brostar.nl/api/"
r = requests.get(url, auth=auth)
Note
De username in de basic authenticatie is altijd __key__
Endpoints
De API bestaat uit de volgende endpoints:
Authenticatie en authorisatie endpoints:
Functionele endpoints:
Data endpoints:
Organisations
Note
Organisaties kunnen alleen aangemaakt worden door medewerkers van Nelen & Schuurmans. Staat jouw organisatie nog niet geregistreerd? Contact info@nelen-schuurmans.nl
Organisaties zijn de basis van het datamodel van de BROSTAR. Elke gebruiker, maar ook elk data object zoals geimporteerde data vanuit de BRO, valt onder een organisatie. Als gebruiker zie je dus alleen de data die onder jouw organisatie valt.
Een organisatie bestaat uit een combinatie van een naam en een kvk nummer. Als je als gebruiker een import taak start, zonder specifiek een kvk nummer op te geven, wordt altijd het kvk nummer van de organisatie waar je onder valt gebruikt in de communicatie naar de BRO. Het is echter mogelijk om een ander kvk nummer op te geven, waarmee je openbare data van andere organisaties kan importeren.
Om het opzoeken van kvk nummers makkelijker te maken, is het mogelijk om alle geregistreerde organisaties in de BROSTAR, inclusief kvk nummer, in te zien. Deze zijn in te zien op het https://www.brostar.nl/api/organisations/ endpoint.
Warning
Om de data in de browsable in te kunnen zien moet een gebruiker ingelogd zijn.
Users
Elke gebruiker krijgt een eigen account op basis van een email adres. Vooralsnog vindt het gebruikersmanagement via Nelen & Schuurmans plaats. Later wordt dit eventueel vergemakkelijkt en in handen van de admins van een organisatie gegeven.
Elk account hangt onder een organisatie.
Op het https://www.brostar.nl/api/users/logged-in/ kunnen user-gegevens ingezien worden.
Importtasks
Importtaken zorgen ervoor dat de huidig aanwezige data in de BRO in de BROSTAR database belanden. Deze data is vervolgens in de specifieke endpoints op te vragen.
Importtaken vinden plaats op basis van POST requests. In deze request wordt een combinatie van een BRO domein (GAR, GLD, GMW, GMN of FRD) en een kvk nummer meegegeven. Voordat de import start, wordt alle data voor dat domein in de BROSTAR verwijderd. Nadat een taak is geslaagd, is de data in de BROSTAR voor dat specifieke domein dus up-to-date met de BRO.
De data die wordt geimporteerd is slechts de huidige versie van de metadata. Er wordt dus bijvoorbeeld geen geschiedenis van een GMW of de standen van een GLD geimporteerd. Hiervoor kan de BRO zelf bevraagd worden.
Note
De aanwezigheid van de data in de BROSTAR is essentieel voor de frontend om te bestaan. Het kan dus zijn dat je als scripter, die alleen bezig is met het aanleveren van data, geen gebruik maakt van dit endpoint. Toch kan het in sommige gevallen handig zijn. Voorbeelden hiervan zijn om een vertaling van een NITG-code naar een BRO-id te maken of te controleren of bepaalde objecten reeds aangeleverd zijn.
Hieronder een voorbeeld van een POST request om een GMN import taak voor eigen organisatie te starten:
import requests
from requests.auth import HTTPBasicAuth
BROSTAR_API_KEY = ...
auth = HTTPBasicAuth(
username="__key__",
password=BROSTAR_API_KEY,
)
url = "https://www.brostar.nl/api/importtasks/ "
payload = {
"bro_domain": "GMN"
}
r = requests.post(url, auth=auth, payload=payload)
Uploadtasks
Uploadtaken zijn dé kracht van de BROSTAR. Het idee achter dit endpoint is dat er slechts JSON opgesteld hoeft te worden om data aan te leveren. Om dit te visualiseren volgt hieronder een code snippet, waarmee een GMN aangemaakt kan worden in de BRO.
import requests
from requests.auth import HTTPBasicAuth
BROSTAR_API_KEY = ...
auth = HTTPBasicAuth(
username="__key__",
password=BROSTAR_API_KEY,
)
url = "https://www.brostar.nl/api/importtasks/ "
metadata = {
"requestReference":"test",
"deliveryAccountableParty":"12345678",
"qualityRegime":"IMBRO"
}
sourcedocument_data = {
"objectIdAccountableParty":"test",
"name":"test",
"deliveryContext":"kaderrichtlijnWater",
"monitoringPurpose":"strategischBeheerKwaliteitRegionaal",
"groundwaterAspect":"kwantiteit",
"startDateMonitoring":"2024-01-01",
"measuringPoints":[
{
"measuringPointCode":"PUT00001",
"broId":"GMW000000000001",
"tubeNumber":"1"
}
]
}
payload = {
"bro_domain": "GMN",
"project_number": "1234",
"registration_type": "GMN_StartRegistration",
"request_type": "registration",
"metadata": metadata,
"sourcedocument_data": sourcedocument_data
}
r = requests.post(url, auth=auth, payload=payload)
De BROSTAR API zal een taak starten waarin 1) een XML bestand wordt aangemaakt, 2) het bestand wordt gevalideerd bij de BRO, 3) het bestand wordt aangeleverd en 4) de voortgang wordt gecontroleerd. De status hiervan wordt bijgehouden in de instantie van de upload taak. Voor elke uploadtaak kan men de volgende eigenschappen inzien om de status bij te houden:
In een post request bestaat de payload uit 6 belangrijke onderdelen:
bro_domain
Het BRO domein bepaalt voor welk type object (GAR, GLD, GMW, GMN of FRD) de data aangeleverd wordt.
project_number
Het BRO project nummer is nodig om data bij de BRO aan te kunnen leveren. Deze kun je in het Bronhouderportaal aanvragen/vinden.
registration_type
Voor elk BRO domein zijn er verschillende type berichten mogelijk. Zo zijn er bijvoorbeeld voor de GMN Startregistration, MeasuringPoint, en Closure als berichten mogelijk.
request_type
Elk registration type kan op verschillende manieren aangeleverd worden. In principe is de registration de standaardoptie, maar als er data aangepast of verwijderd moet worden, dan zijn respectievelijk de replace en delete requests types beschikbaar. In de BRO catalogus staan alle mogelijke combinaties. Dit zijn de beschikbare request types:
-
registration
-
replace
-
move
-
insert
-
delete
metadata
Voor elke combinatie van registration type en request type wordt achter de schermen een XML bestand opgesteld worden (zie voorbeeld startregistratie GMN). Elk XML bestand begint met metadata, die voor alle berichten hetzelfde zijn. De enige variatie hierin is welke combinatie van waardes meegegeven moet worden. De BROSTAR API ontvangt de metadata JSON en valideert deze met het volgende Pydantic model:
from pydantic import BaseModel
class UploadTaskMetadata(BaseModel):
requestReference: str
deliveryAccountableParty: str | None = None
qualityRegime: str
broId: str | None = None
underPrivilege: str | None = None
correctionReason: str | None = None
dateToBeCorrected: str | date | None = None
Elke metadata moet dus minimaal bestaan uit een requestReference en qualityRegime. Afhankelijk van de combinatie van de registration type en de request type kunnen ook andere benodigd zijn. Om een idee te krijgen van welke data er verwacht wordt, kunnen de XML templates van de BROSTAR gebruikt worden. Deze worden door de API gebruikt om de XML bestanden op te stellen, en zijn daarmee de voorbeelden van welke data er in de metadata wordt verwacht.
sourcedocument_data
De sourcedocument data is de data die benodigd is om de specifieke XML bestanden op te stellen voor elke combinatie van de registration type en de request type. Deze kunnen dus enorm verschillen.
Net zoals de metadata, wordt de sourcedocument data door de API gevalideerd met behulp van Pydantic models.
Tip
Voor simpele eenmalige leveringen kunnen deze worden gebruikt om een JSON samen te stellen. Voor grotere, complexere of operationele leveringen, wordt het aangeraden om de Pydantic models te kopieren in de eigen code.
GMN Startregistratie
from pydantic import BaseModel
class GMNStartregistration(BaseModel):
objectIdAccountableParty: str
name: str
deliveryContext: str
monitoringPurpose: str
groundwaterAspect: str
startDateMonitoring: str
measuringPoints: list[MeasuringPoint]
class MeasuringPoint(BaseModel):
measuringPointCode: str
broId: str
tubeNumber: str | int
GMN MeasuringPoint
from pydantic import BaseModel
class GMNMeasuringPoint(BaseModel):
eventDate: str
measuringPointCode: str
broId: str
tubeNumber: str | int
GMN MeasuringPointEndDate
from pydantic import BaseModel
class GMNMeasuringPointEndDate(BaseModel):
eventDate: str
measuringPointCode: str
broId: str
tubeNumber: str | int
GMN TubeReference
from pydantic import BaseModel
class GMNTubeReference(BaseModel):
eventDate: str
measuringPointCode: str
GMN Closure
from pydantic import BaseModel
class GMNClosure(BaseModel):
endDateMonitoring: str
GWM GMWConstruction
from pydantic import BaseModel
class GMWConstruction(BaseModel):
objectIdAccountableParty: str
deliveryContext: str
constructionStandard: str
initialFunction: str
numberOfMonitoringTubes: str | int
groundLevelStable: str
wellStability: str | None = None
owner: str | None = None
maintenanceResponsibleParty: str | None = None
wellHeadProtector: str
wellConstructionDate: str
deliveredLocation: str
horizontalPositioningMethod: str
localVerticalReferencePoint: str
offset: str | float
verticalDatum: str
groundLevelPosition: str | float | None = None
groundLevelPositioningMethod: str
monitoringTubes: list[MonitoringTube]
class MonitoringTube(BaseModel):
tubeNumber: str | int
tubeType: str
artesianWellCapPresent: str
sedimentSumpPresent: str
numberOfGeoOhmCables: str | int
tubeTopDiameter: str | float | None = None
variableDiameter: str | float
tubeStatus: str
tubeTopPosition: str | float
tubeTopPositioningMethod: str
tubePackingMaterial: str
tubeMaterial: str
glue: str
screenLength: str | float
screenProtection: str | None = None
sockMaterial: str
plainTubePartLength: str | float
sedimentSumpLength: str | float | None = None
geoOhmCables: list[GeoOhmCable] | None = None
class GeoOhmCable(BaseModel):
cableNumber: str | int
electrodes: list[Electrode]
class Electrode(BaseModel):
electrodeNumber: str | int
electrodePackingMaterial: str
electrodeStatus: str
electrodePosition: str | float
GMW Events
from pydantic import BaseModel
class GMWEvent(BaseModel):
eventDate: str
class GMWElectrodeStatus(GMWEvent):
electrodes: list[Electrode]
class GMWGroundLevel(GMWEvent):
wellStability: str | None = None
groundLevelStable: str
groundLevelPosition: str
groundLevelPositioningMethod: str
class GMWGroundLevelMeasuring(GMWEvent):
groundLevelPosition: str
groundLevelPositioningMethod: str
class GMWInsertion(GMWEvent):
tubeNumber: str
tubeTopPosition: str
tubeTopPositioningMethod: str
insertedPartLength: str
insertedPartDiameter: str
insertedPartMaterial: str
class MonitoringTubeLengthening(BaseModel):
tubeNumber: str | int
variableDiameter: str | float
tubeTopPosition: str | float
tubeTopPositioningMethod: str
tubeMaterial: str
glue: str
plainTubePartLength: str | float
class GMWLengthening(GMWEvent):
wellHeadProtector: str | None = None
monitoringTubes: list[MonitoringTubeLengthening]
class GMWMaintainer(GMWEvent):
maintenanceResponsibleParty: str
class GMWOwner(GMWEvent):
owner: str
class MonitoringTubePositions(BaseModel):
tubeNumber: str | int
tubeTopPosition: str | float
tubeTopPositioningMethod: str
class GMWPositions(GMWEvent):
wellStability: str | None = None
groundLevelStable: str
groundLevelPosition: str
groundLevelPositioningMethod: str
monitoringTubes: list[MonitoringTubePositions]
class GMWPositionsMeasuring(GMWEvent):
monitoringTubes: list[MonitoringTube]
groundLevelPosition: str | None = None
groundLevelPositioningMethod: str | None = None
class GMWRemoval(GMWEvent):
pass
class GMWShift(GMWEvent):
groundLevelPosition: str
groundLevelPositioningMethod: str
class MonitoringTubeShortening(BaseModel):
tubeNumber: str | int
tubeTopPosition: str | float
tubeTopPositioningMethod: str
plainTubePartLength: str | float
class GMWShortening(GMWEvent):
wellHeadProtector: str | None = None
monitoringTubes: list[MonitoringTubeShortening]
class MonitoringTubeStatus(BaseModel):
tubeNumber: str | int
tubeStatus: str
class GMWTubeStatus(GMWEvent):
monitoringTubes: list[MonitoringTubeStatus]
class GMWWellHeadProtector(GMWEvent):
wellHeadProtector: str
GAR StartRegistration
from pydantic import BaseModel
class GAR(BaseModel):
objectIdAccountableParty: str
qualityControlMethod: str
groundwaterMonitoringNets: list[str] | None = None
gmwBroId: str
tubeNumber: str | int
fieldResearch: FieldResearch
laboratoryAnalyses: list[LaboratoryAnalysis] | None = None
class LaboratoryAnalysis(BaseModel):
responsibleLaboratoryKvk: str | None = None
analysisProcesses: list[AnalysisProcess] = []
class AnalysisProcess(BaseModel):
date: str | date
analyticalTechnique: str
valuationMethod: str
analyses: list[Analysis]
class Analysis(BaseModel):
parameter: str | int
unit: str
analysisMeasurementValue: str | float
limitSymbol: str | None = None
reportingLimit: str | float | None = None
qualityControlStatus: str
class FieldResearch(BaseModel):
samplingDateTime: str | datetime
samplingOperator: str | None = None
samplingStandard: str
pumpType: str
primaryColour: str | None = None
secondaryColour: str | None = None
colourStrength: str | None = None
abnormalityInCooling: str
abnormalityInDevice: str
pollutedByEngine: str
filterAerated: str
groundWaterLevelDroppedTooMuch: str
abnormalFilter: str
sampleAerated: str
hoseReused: str
temperatureDifficultToMeasure: str
fieldMeasurements: list[FieldMeasurement] | None = None
class FieldMeasurement(BaseModel):
parameter: str | int
unit: str
fieldMeasurementValue: str | float
qualityControlStatus: str
GLD StartRegistration
from pydantic import BaseModel
class GLDStartregistration(BaseModel):
objectIdAccountableParty: str | None = None
groundwaterMonitoringNets: list[str] | None = None
gmwBroId: str
tubeNumber: str | int
GLD Addition
from pydantic import BaseModel
class GLDAddition(BaseModel):
date: str
observationId: str | None = None
observationProcessId: str | None = None
measurementTimeseriesId: str | None = None
validationStatus: str | None = None
investigatorKvk: str
observationType: str
evaluationProcedure: str
measurementInstrumentType: str
processReference: str
airPressureCompensationType: str | None = None
beginPosition: str
endPosition: str
resultTime: str
timeValuePairs: list[TimeValuePair]
class TimeValuePair(BaseModel):
time: str | datetime
value: float | str
statusQualityControl: str
censorReason: str | None = None
censoringLimitvalue: str | float | None = None
FRD StartRegistration
from pydantic import BaseModel
FRD StartRegistration
from pydantic import BaseModel
class FRDStartRegistration(BaseModel):
objectIdAccountableParty: str | None = None
groundwaterMonitoringNets: list[str] | None = None
gmwBroId: str
tubeNumber: str | int
FRD MeasurementConfiguration
from pydantic import BaseModel
class FRDGemMeasurementConfiguration(BaseModel):
measurementConfigurations: list[MeasurementConfiguration]
class MeasurementConfiguration(BaseModel):
measurementConfigurationID: str
measurementE1CableNumber: str | int
measurementE1ElectrodeNumber: str | int
measurementE2CableNumber: str | int
measurementE2ElectrodeNumber: str | int
currentE1CableNumber: str | int
currentE1ElectrodeNumber: str | int
currentE2CableNumber: str | int
currentE2ElectrodeNumber: str | int
FRD EmmInstrumentConfiguration
from pydantic import BaseModel
class FRDEmmInstrumentConfiguration(BaseModel):
instrumentConfigurationID: str
relativePositionTransmitterCoil: str | int
relativePositionPrimaryReceiverCoil: str | int
secondaryReceiverCoilAvailable: str
relativePositionSecondaryReceiverCoil: str | int | None = None
coilFrequencyKnown: str
coilFrequency: str | int | None = None
instrumentLength: str | int
FRD EmmMeasurement
from pydantic import BaseModel
class FRDEmmMeasurement(BaseModel):
measurementDate: date | str
measurementOperatorKvk: str
determinationProcedure: str
measurementEvaluationProcedure: str
measurementSeriesCount: str | int
measurementSeriesValues: str
relatedInstrumentConfigurationId: str
calculationOperatorKvk: str
calculationEvaluationProcedure: str
calculationCount: str | int
calculationValues: str
FRD GemMeasurement
from pydantic import BaseModel
class FRDGemMeasurement(BaseModel):
measurementDate: str | date
measurementOperatorKvk: str
determinationProcedure: str
evaluationProcedure: str
measurements: list[GemMeasurement]
relatedCalculatedApparentFormationResistance: RelatedCalculatedApparentFormationResistance | None = None
class RelatedCalculatedApparentFormationResistance(BaseModel):
calculationOperatorKvk: str
evaluationProcedure: str
elementCount: str | int
values: str
class GemMeasurement(BaseModel):
value: str | int
unit: str
configuration: str
Check status
Nadat een taak 4 keer bij de BRO op status is gecontroleerd, is het mogelijk dat de BRO de taak nog steeds niet heeft verwerkt. In dit geval zal de BROSTAR stoppen met de status controleren, en schiet de status van de upload taak op UNFINISHED. In dit geval zal handmatig de voortgang van de taak gecontroleerd moeten worden. Dit gebeurt via een POST request op het check-status endpoint.
De url van dit endpoint is als volgt opgesteld:
https://www.brostar.nl/api/uploadtasks/{uuid}/check_status/
De mogelijke responses op dit endpoint zijn:
-
201 - The task has been started after being stuck on PENDING
-
303 - The task is still running
-
303 - The upload task has allready finished with status: {status}
-
303 - The upload failed. Check the detail page for more info
-
304 - The upload is still not completely handled in the BRO. Check the status later again.
Read XML
Om meer inzicht te geven in de data die naar de BRO wordt gestuurd, is het mogelijk om de XML bestanden in te zien. Dit kan op het read_xml endpoint. De url hiervoor is:
https://www.brostar.nl/api/uploadtasks/{uuid}/read_xml/
Bulk uploadtasks
Warning
Het bulk uploadtask endpoint is maatwerk. Contact info@nelen-schuurmans.com om de mogelijkheden te verkennen om een specifieke bulk upload te realiseren.
Het bulk upload endpoint is gemaakt om eenvoudig een groot aantal leveringen te realiseren. Op het bulk endpoint is het mogelijk om CSV/Excel bestanden aan te leveren. Achter de schermen wordt een taak gestart die deze bestanden opknipt in meerdere uploadtaken.
Het bulk upload endpoint is een stukje maatwerk in de BROSTAR. Momenteel bestaat alleen de optie om een specifiek formaat van GAR CSV bestanden aan te leveren. Deze zijn ontwikkeld voor Provincie Noord-Brabant, waarbij hun formaat van lab- en veldbestanden zijn gebruikt als input. Daardoor is het voor hen mogelijk om 2 bestanden in de frontend te slepen, wat metadata op te geven, en de bestanden naar de API te sturen. Hiermee wordt alle data dus vertaald naar uploadtaken, die vervolgens de data naar de BRO sturen.
Hieronder staat een voorbeeld van hoe een bulk upload opgestuurd kan worden via een script.
Wow
Dit stukje code, in combinatie met de XLSX bestanden, is dus alles wat nodig is om duizenden GAR berichten te registreren in de BRO!
import requests
import json
from requests.auth import HTTPBasicAuth
BROSTAR_API_KEY = ...
auth = HTTPBasicAuth(
username="__key__",
password=BROSTAR_API_KEY,
)
url = "https://brostar.nl/api/bulkuploads/"
payload = {
"bro_domain": "GMN"
}
lab_path = 'lab.xlsx'
veldwerk_path ='veldwerk.xlsx'
metadata_json = json.dumps(
{
"requestReference": "gar_bulk_upload",
"qualityRegime": "IMBRO",
"samplingOperator": "12345678",
"samplingStandard": "NTA8017v2016",
"qualityControlMethod": "handboekProvinciesRIVMv2017",
"responsibleLaboratoryKvk": "12345678",
"groundwaterMonitoringNets": ["GMN000000000001"]
}
)
with open(veldwerk_path, "rb") as fp_fieldwork, open(lab_path, "rb") as fp_lab:
files = {
'fieldwork_file': ('veldwerk.xlsx', fp_fieldwork, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'),
'lab_file': ('lab.xlsx', fp_lab, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
}
data = {
"bulk_upload_type": "GAR",
"project_number": 1,
"metadata": metadata_json,
}
# Send the POST request
r = requests.post(url, auth=auth, data=data, files=files)
print(r.json())
Data endpoints
De data endpoints zijn een tijdelijke opslag voor de data die volgt uit de importtaken. De urls van de data endpoints zijn:
-
https://www.brostar.nl/api/gmn/gmns/
-
https://www.brostar.nl/api/gmn/measuringpoints/
-
https://www.brostar.nl/api/gmws/gmws/
-
https://www.brostar.nl/api/gmws/monitoringtubes/
-
https://www.brostar.nl/api/gar/gars/
-
https://www.brostar.nl/api/gld/glds/
-
https://www.brostar.nl/api/frd/frds/
Op deze endpoints zijn de lijsten van objecten te zien. Deze lijsten van de metadata van objecten bieden een mooi overzicht van alles wat er in de BRO aan data staat. Dit is een mooie vervanging voor de uitgifteservice van de BRO, aangezien daar alleen requests gedaan kunnen worden op basis van individuele objecten. Deze endpoints kunnen dus helpen bij het scripten, maar dienen vooral voor de frontend om de data snel op te kunnen vragen om het vervolgens in de kaart en tabellen weer te geven.