openapi: 3.1.0
info:
  title: nederland.cloud API
  version: 1.0.0-beta
  description: |
    Platform met alle API's van Nederland, gestandaardiseerd. Elke bron-API is
    individueel aanspreekbaar via /v1/{bron}/{entiteit}; achter de schermen
    vragen wij live op bij de bron, zetten de data om naar de nederland.cloud-
    standaard en geven die door. Geen gecombineerde data, geen datastore.

    De nederland.cloud-standaard (elke API, elk antwoord):
    * vaste envelop: `api`, `versie`, `onderdeel`, `gegenereerd_op`, `duur_ms`,
      `bronnen[]` (herkomst, licentie, attributie, timing), `data`
    * veldconventies: snake_case, ISO-8601, coördinaten in WGS84 én RD
    * `null` = onbekend/afgeschermd bij de bron
    * fouten: altijd RFC 7807
  contact:
    name: nederland.cloud (initiatief van identix)
servers:
  - url: https://nederland.cloud
  - url: https://nederland.identix.org
  - url: http://localhost:8080
paths:
  /v1/bronnen:
    get:
      summary: Catalogus van alle beschikbare API's
      responses:
        "200":
          description: Lijst met API's (endpoint, parameters, licentie, status) + roadmap
  /v1/bag/adressen:
    get:
      summary: BAG · Adressen — adres-zoekactie (PDOK Locatieserver)
      parameters:
        - name: q
          in: query
          required: true
          schema: { type: string, minLength: 2 }
          example: Dam 1, Amsterdam
        - name: rows
          in: query
          required: false
          schema: { type: integer, minimum: 1, maximum: 50, default: 8 }
        - name: start
          in: query
          required: false
          schema: { type: integer, minimum: 0, default: 0 }
      responses:
        "200":
          description: Gestandaardiseerde adresobjecten
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AdresResponse" }
        "400": { $ref: "#/components/responses/Fout" }
        "502": { $ref: "#/components/responses/Fout" }
  /v1/bag/suggest:
    get:
      summary: BAG · Type-ahead-suggesties
      parameters:
        - name: q
          in: query
          required: true
          schema: { type: string, minLength: 2 }
          example: keizersgr
        - name: rows
          in: query
          required: false
          schema: { type: integer, minimum: 1, maximum: 25, default: 6 }
      responses:
        "200": { description: Suggesties {id, weergavenaam, type} }
        "400": { $ref: "#/components/responses/Fout" }
  /v1/bag/lookup:
    get:
      summary: BAG · Object per Locatieserver-id
      parameters:
        - name: id
          in: query
          required: true
          schema: { type: string }
          example: adr-2a8dc1af055da20b8bcdc8e4dbda1eaa
      responses:
        "200": { description: Object (adres-objecten volledig genormaliseerd) }
        "400": { $ref: "#/components/responses/Fout" }
        "404": { $ref: "#/components/responses/Fout" }
  /v1/cbs/kerncijfers:
    get:
      summary: CBS · Kerncijfers wijken en buurten per regio
      parameters:
        - name: regio
          in: query
          required: true
          schema: { type: string, pattern: "^(BU|WK|GM)[0-9A-Z]+$" }
          example: WK0363AE
        - name: jaar
          in: query
          required: false
          schema: { type: integer, example: 2024 }
          description: Peiljaar; standaard de nieuwste beschikbare tabel.
      responses:
        "200":
          description: Gestandaardiseerde kerncijfers
          content:
            application/json:
              schema: { $ref: "#/components/schemas/KerncijfersResponse" }
        "400": { $ref: "#/components/responses/Fout" }
        "502": { $ref: "#/components/responses/Fout" }
  /v1/cbs/regios:
    get:
      summary: CBS · Alle regio's van één niveau binnen een gebied
      parameters:
        - name: binnen
          in: query
          required: true
          schema: { type: string, pattern: "^(BU|WK|GM)[0-9A-Z]+$" }
          example: GM0363
        - name: niveau
          in: query
          required: true
          schema: { type: string, enum: [buurt, wijk, gemeente] }
          example: wijk
      responses:
        "200": { description: Lijst regio's met codes }
        "400": { $ref: "#/components/responses/Fout" }
  /v1/kadaster/percelen:
    get:
      summary: Kadaster · Kadastrale percelen bij een locatie
      parameters:
        - name: lon
          in: query
          required: true
          schema: { type: number, minimum: -180, maximum: 180 }
          example: 4.8930
        - name: lat
          in: query
          required: true
          schema: { type: number, minimum: -90, maximum: 90 }
          example: 52.3730
        - name: radius
          in: query
          required: false
          schema: { type: integer, minimum: 5, maximum: 2000, default: 50 }
          description: Zoekstraal in meters.
      responses:
        "200":
          description: Gestandaardiseerde percelen
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PercelenResponse" }
        "400": { $ref: "#/components/responses/Fout" }
        "502": { $ref: "#/components/responses/Fout" }
  /v1/kadaster/percelen/{id}:
    get:
      summary: Kadaster · Eén perceel per kadastrale objectidentificatie
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200": { description: Perceel }
        "404": { $ref: "#/components/responses/Fout" }
  /v1/kadaster/grenzen:
    get:
      summary: Kadaster · Kadastrale grenzen bij een locatie
      parameters:
        - { name: lon, in: query, required: true, schema: { type: number }, example: 4.8930 }
        - { name: lat, in: query, required: true, schema: { type: number }, example: 52.3730 }
        - { name: radius, in: query, required: false, schema: { type: integer, minimum: 5, maximum: 2000, default: 50 } }
      responses:
        "200": { description: "Grenzen: type_grens, kwaliteit, perceel_links_id, perceel_rechts_id, geometrie" }
        "400": { $ref: "#/components/responses/Fout" }
  /v1/kadaster/bebouwing:
    get:
      summary: Kadaster · Bebouwing bij een locatie (met BAG-pandkoppeling)
      parameters:
        - { name: lon, in: query, required: true, schema: { type: number }, example: 4.8930 }
        - { name: lat, in: query, required: true, schema: { type: number }, example: 52.3730 }
        - { name: radius, in: query, required: false, schema: { type: integer, minimum: 5, maximum: 2000, default: 50 } }
      responses:
        "200": { description: "Bebouwing: bag_pand_id, status_bgt, geometrie" }
        "400": { $ref: "#/components/responses/Fout" }
  /v1/kadaster/openbareruimtenamen:
    get:
      summary: Kadaster · Openbare ruimtenamen (straatnamen) bij een locatie
      parameters:
        - { name: lon, in: query, required: true, schema: { type: number }, example: 4.8930 }
        - { name: lat, in: query, required: true, schema: { type: number }, example: 52.3730 }
        - { name: radius, in: query, required: false, schema: { type: integer, minimum: 5, maximum: 2000, default: 50 } }
      responses:
        "200": { description: "Namen: naam, type, bag_openbare_ruimte_id, geometrie" }
        "400": { $ref: "#/components/responses/Fout" }
  /v1/kadaster/nummeraanduidingreeksen:
    get:
      summary: Kadaster · Huisnummerreeksen bij een locatie
      parameters:
        - { name: lon, in: query, required: true, schema: { type: number }, example: 4.8930 }
        - { name: lat, in: query, required: true, schema: { type: number }, example: 52.3730 }
        - { name: radius, in: query, required: false, schema: { type: integer, minimum: 5, maximum: 2000, default: 50 } }
      responses:
        "200": { description: "Reeksen: label, vbo-ids, bebouwing_id, geometrie" }
        "400": { $ref: "#/components/responses/Fout" }
  /v1/luchtmeetnet/metingen:
    get:
      summary: Luchtmeetnet · Metingen per station, met optionele filters
      parameters:
        - name: station
          in: query
          required: true
          schema: { type: string, pattern: "^NL[0-9]+$" }
          example: NL49565
        - name: formule
          in: query
          required: false
          schema: { type: string }
          example: PM10
        - name: van
          in: query
          required: false
          schema: { type: string, format: date-time }
        - name: tot
          in: query
          required: false
          schema: { type: string, format: date-time }
      responses:
        "200":
          description: Gestandaardiseerde metingen
          content:
            application/json:
              schema: { $ref: "#/components/schemas/MetingenResponse" }
        "400": { $ref: "#/components/responses/Fout" }
        "502": { $ref: "#/components/responses/Fout" }
  /v1/luchtmeetnet/stations:
    get:
      summary: Luchtmeetnet · Stationlijst (optioneel gefilterd)
      parameters:
        - name: zoek
          in: query
          required: false
          schema: { type: string }
          example: amsterdam
      responses:
        "200": { description: Stations }
  /v1/catalogus/datasets:
    get:
      summary: Catalogus · Zoek in alle open datasets van Nederland (20.000+)
      parameters:
        - name: q
          in: query
          required: false
          schema: { type: string }
          example: energielabel
        - name: instantie
          in: query
          required: false
          schema: { type: string }
          example: gemeente-rotterdam
        - name: rows
          in: query
          required: false
          schema: { type: integer, minimum: 1, maximum: 100, default: 10 }
        - name: start
          in: query
          required: false
          schema: { type: integer, minimum: 0, default: 0 }
      responses:
        "200": { description: "Datasets: titel, instantie, licentie, resources (met api-vlag)" }
        "502": { $ref: "#/components/responses/Fout" }
  /v1/catalogus/datasets/{id}:
    get:
      summary: Catalogus · Eén dataset per slug of uuid
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200": { description: Datasetdetail }
        "404": { $ref: "#/components/responses/Fout" }
  /v1/catalogus/instanties:
    get:
      summary: Catalogus · Alle publicerende instanties met dataset-aantallen
      responses:
        "200": { description: Instanties }
  /v1/rdw/voertuigen:
    get:
      summary: RDW · Voertuigen per kenteken of merk
      parameters:
        - name: kenteken
          in: query
          required: false
          schema: { type: string }
          example: XS005T
        - name: merk
          in: query
          required: false
          schema: { type: string }
          example: TESLA
        - name: rows
          in: query
          required: false
          schema: { type: integer, minimum: 1, maximum: 100, default: 10 }
      responses:
        "200": { description: "Genormaliseerde voertuiggegevens (ISO-datums, getallen als getallen)" }
        "400": { $ref: "#/components/responses/Fout" }
        "502": { $ref: "#/components/responses/Fout" }
  /v1/rce/monumenten:
    get:
      summary: RCE · Rijksmonumenten per nummer, adres of plaats
      parameters:
        - { name: rijksmonumentnummer, in: query, required: false, schema: { type: string }, example: "518313" }
        - { name: postcode, in: query, required: false, schema: { type: string }, example: 1012JS }
        - { name: straat, in: query, required: false, schema: { type: string }, example: Dam }
        - { name: woonplaatsnaam, in: query, required: false, schema: { type: string }, example: Amsterdam }
        - { name: page, in: query, required: false, schema: { type: integer, minimum: 1, default: 1 } }
      responses:
        "200": { description: "Monumenten met BAG-koppelingen (heeft_pand, heeft_verblijfsobject)" }
        "400": { $ref: "#/components/responses/Fout" }
        "502": { $ref: "#/components/responses/Fout" }
  /v1/datasets/{id}/query:
    get:
      summary: Universele query-API · filter, sorteer en pagineer door elke dataset
      description: |
        Bevraagt een dataset als een normale API. Eerste aanroep materialiseert de
        dataset (24-uursverversing); daarna filteren, sorteren en pagineren lokaal.
        Filters: kolom=waarde, kolom~deel, kolom!=waarde, kolom>=getal, kolom<=getal (EN).
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
        - name: waar
          in: query
          required: false
          schema: { type: array, items: { type: string } }
          example: postcode~9401
        - name: velden
          in: query
          required: false
          schema: { type: string }
          example: postcode,label
        - name: sorteer
          in: query
          required: false
          schema: { type: string }
        - name: richting
          in: query
          required: false
          schema: { type: string, enum: [asc, desc], default: asc }
        - name: rijen
          in: query
          required: false
          schema: { type: integer, minimum: 1, maximum: 500, default: 50 }
        - name: start
          in: query
          required: false
          schema: { type: integer, minimum: 0, default: 0 }
      responses:
        "200": { description: "Query-resultaat met materiaal-status en herkomst" }
        "400": { $ref: "#/components/responses/Fout" }
        "404": { $ref: "#/components/responses/Fout" }
  /v1/datasets/{id}/data:
    get:
      summary: Universele ontsluiting · lever de inhoud van elke dataset
      description: |
        Raadpleegt de catalogus, kiest de resource, haalt de data op en normaliseert:
        JSON direct, CSV als tabel (kolommen + rijen), ArcGIS als features.
        Niet-machine-leesbare resources (PDF/XLS/…) leveren een heldere verklaring.
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
        - name: resource
          in: query
          required: false
          schema: { type: integer, minimum: 0, default: 0 }
        - name: rijen
          in: query
          required: false
          schema: { type: integer, minimum: 1, maximum: 500, default: 50 }
      responses:
        "200": { description: Genormaliseerde dataset-inhoud (vorm: tabel|features|json|tekst|niet_machine_leesbaar) }
        "400": { $ref: "#/components/responses/Fout" }
        "404": { $ref: "#/components/responses/Fout" }
  /v1/health:
    get:
      summary: Gezondheidscheck
      responses:
        "200": { description: ok }
components:
  responses:
    Fout:
      description: RFC 7807-fout
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Fout" }
  schemas:
    Fout:
      type: object
      properties:
        type: { type: string }
        title: { type: string }
        status: { type: integer }
        detail: { type: string }
    Bron:
      type: object
      properties:
        naam: { type: string, example: CBS StatLine }
        instantie: { type: string, example: Centraal Bureau voor de Statistiek }
        licentie: { type: string, example: CC BY 4.0 }
        attributie: { type: string, example: "CBS, Den Haag" }
        duur_ms: { type: number }
        uit_cache: { type: boolean }
        status: { type: string, enum: [ok, fout] }
    Adres:
      type: object
      properties:
        nummeraanduiding_id: { type: string, example: "0363200003761447" }
        straat: { type: string, example: Dam }
        huisnummer: { type: integer, example: 1 }
        huisletter: { type: string, nullable: true }
        huisnummertoevoeging: { type: string, nullable: true }
        postcode: { type: string, example: 1012JS }
        woonplaats: { type: string, example: Amsterdam }
        weergavenaam: { type: string }
        gemeente:
          type: object
          properties:
            code: { type: string, example: "0363" }
            naam: { type: string, example: Amsterdam }
        provincie:
          type: object
          properties:
            code: { type: string, example: PV27 }
            naam: { type: string, example: Noord-Holland }
        wijk:
          type: object
          properties:
            code: { type: string, example: WK0363AE }
            naam: { type: string, nullable: true, example: Burgwallen-Oude Zijde }
        buurt:
          type: object
          properties:
            code: { type: string, example: BU0363AE02 }
            naam: { type: string, nullable: true, example: Oude Kerk e.o. }
        percelen:
          type: array
          items:
            type: object
            properties:
              kadastrale_aanduiding: { type: string, example: ASD04-F-6417 }
        geometrie:
          type: object
          properties:
            centroide_wgs84:
              type: object
              nullable: true
              properties:
                lon: { type: number, example: 4.893 }
                lat: { type: number, example: 52.373 }
            centroide_rd:
              type: object
              nullable: true
              properties:
                lon: { type: number, example: 121347.914 }
                lat: { type: number, example: 487347.519 }
    Kerncijfer:
      type: object
      properties:
        waarde: { type: number, nullable: true, description: null = onbekend/afgeschermd bij de bron }
        eenheid: { type: string, example: personen }
    Perceel:
      type: object
      properties:
        kadastrale_aanduiding: { type: string, example: "Amsterdam F 6417" }
        kadastrale_aanduiding_code: { type: string, example: "ASD04-F-6417" }
        kadastrale_gemeente: { type: string, example: Amsterdam }
        sectie: { type: string, example: F }
        perceelnummer: { type: integer, example: 6417 }
        oppervlak_m2: { type: integer, example: 816 }
        soort_grootte: { type: string, example: Vastgesteld }
        status: { type: string, example: Geldig }
        geldig_vanaf: { type: string, format: date-time, nullable: true }
        identificatie: { type: string, nullable: true }
        geometrie_wgs84: { type: object, nullable: true, description: GeoJSON-geometrie }
    Meting:
      type: object
      properties:
        stof: { type: string, example: PM10 }
        waarde: { type: number, nullable: true }
        eenheid: { type: string, nullable: true, example: "µg/m³" }
        gemeten_op: { type: string, format: date-time, nullable: true }
    Envelop:
      type: object
      properties:
        api: { type: string, example: nederland.cloud }
        versie: { type: string }
        onderdeel: { type: string, example: bag.adressen }
        gegenereerd_op: { type: string, format: date-time }
        duur_ms: { type: number, nullable: true }
        bronnen:
          type: array
          items: { $ref: "#/components/schemas/Bron" }
    AdresResponse:
      allOf:
        - $ref: "#/components/schemas/Envelop"
        - type: object
          properties:
            data:
              type: object
              properties:
                zoekterm: { type: string }
                aantal: { type: integer }
                resultaten:
                  type: array
                  items: { $ref: "#/components/schemas/Adres" }
    KerncijfersResponse:
      allOf:
        - $ref: "#/components/schemas/Envelop"
        - type: object
          properties:
            data:
              type: object
              properties:
                regio:
                  type: object
                  properties:
                    code: { type: string, example: WK0363AE }
                    niveau: { type: string, enum: [buurt, wijk, gemeente] }
                    naam: { type: string, nullable: true }
                    indelingswijziging: { type: string, nullable: true }
                kerncijfers:
                  type: object
                  additionalProperties: { $ref: "#/components/schemas/Kerncijfer" }
    PercelenResponse:
      allOf:
        - $ref: "#/components/schemas/Envelop"
        - type: object
          properties:
            data:
              type: object
              properties:
                locatie:
                  type: object
                  properties:
                    lon: { type: number }
                    lat: { type: number }
                aantal: { type: integer }
                percelen:
                  type: array
                  items: { $ref: "#/components/schemas/Perceel" }
    MetingenResponse:
      allOf:
        - $ref: "#/components/schemas/Envelop"
        - type: object
          properties:
            data:
              type: object
              properties:
                station: { type: string }
                aantal: { type: integer }
                metingen:
                  type: array
                  items: { $ref: "#/components/schemas/Meting" }
