openapi: 3.0.3

info:
  title: LocatieAPI
  version: "1.0.0"
  description: |
    Nederlandse adres- en postcode-API van Score Agency.

    De API kent twee versies naast elkaar:

    * **v3** is response-compatibel met postcodeapi.nu. Wie daar vandaan komt,
      wisselt alleen de base-URL om en hoeft verder niets te veranderen. Deze
      versie gebruikt `application/problem+json` voor fouten en de Engelse
      foutteksten van de concurrent.
    * **v1** is van onszelf en biedt meer: toevoegingen, autocomplete, bulk,
      adresvalidatie en reverse geocoding. Fouten hebben hier het formaat
      `{"error": {"code": "...", "message": "..."}}`.

    ## Omgevingen

    | Omgeving | Base-URL | Sleutel |
    |---|---|---|
    | Live | `https://api.locatieapi.nl` | `lat_live_...` |
    | Sandbox | `https://sandbox.locatieapi.nl` | `lat_test_...` |

    Elk pad werkt daarnaast onder `https://locatieapi.nl/api/...`, zodat een
    integratie kan starten voordat de subdomeinen in DNS staan.

    De sandbox is een vaste set van vier adressen en telt nooit mee voor de
    maandbundel. Zie het schema `SandboxCases` onderaan dit document.

    ## Verbruik

    Elke aanroep telt als een call. Een bulkverzoek telt per regel. Betaalde
    plannen krijgen 10% coulance boven de bundel; het gratis plan stopt hard op
    de bundel. Sandboxverkeer telt nooit mee.

  contact:
    name: LocatieAPI support
    email: support@locatieapi.nl
    url: https://locatieapi.nl
  license:
    name: Proprietary

servers:
  - url: https://api.locatieapi.nl
    description: Live
  - url: https://sandbox.locatieapi.nl
    description: Sandbox, vaste testset
  - url: https://locatieapi.nl/api
    description: Fallback op pad, zelfde endpoints

tags:
  - name: v3
    description: Compatibel met postcodeapi.nu
  - name: v1 adressen
    description: Adressen opzoeken, inclusief toevoegingen
  - name: v1 zoeken
    description: Autocomplete en reverse geocoding
  - name: v1 verwerking
    description: Bulk en validatie

security:
  - ApiKeyAuth: []

paths:

  /v3/lookup/{postcode}/{number}:
    get:
      tags: [v3]
      summary: Adres opzoeken op postcode en huisnummer
      description: |
        Het antwoord is veld voor veld gelijk aan dat van postcodeapi.nu,
        inclusief de volgorde van de velden.

        Let op twee dingen die vaak misgaan:

        * Een toevoeging in het pad, zoals `29a`, levert een `400` op en geen
          `404`. Het huisnummer moet een geheel getal zijn.
        * De postcode moet precies aan `^[0-9]{4}[a-zA-Z]{2}$` voldoen. Een
          spatie tussen de cijfers en de letters levert een `400` op.

        `location` is `null` bij een postbus. In dat geval is `street`
        letterlijk de tekst `Postbus`.
      operationId: v3Lookup
      parameters:
        - name: postcode
          in: path
          required: true
          description: P6-postcode zonder spatie.
          schema:
            type: string
            pattern: '^[0-9]{4}[a-zA-Z]{2}$'
            example: 6545CA
        - name: number
          in: path
          required: true
          description: Huisnummer als geheel getal, zonder letter of toevoeging.
          schema:
            type: integer
            minimum: 1
            example: 29
      responses:
        '200':
          description: Het adres is gevonden.
          headers:
            Cache-Control:
              description: Alleen bij een 200.
              schema:
                type: string
                example: public, max-age=86400
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
            X-Quota-Limit:
              $ref: '#/components/headers/XQuotaLimit'
            X-Quota-Remaining:
              $ref: '#/components/headers/XQuotaRemaining'
            X-Quota-Reset:
              $ref: '#/components/headers/XQuotaReset'
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V3Address'
              examples:
                adres:
                  summary: Gewoon adres
                  value:
                    postcode: '6545CA'
                    number: 29
                    street: Binderskampweg
                    city: Nijmegen
                    municipality: Nijmegen
                    province: Gelderland
                    location:
                      type: Point
                      coordinates: [5.858910083770752, 51.84376540294041]
                postbus:
                  summary: Postbus, zonder locatie
                  value:
                    postcode: '3030AC'
                    number: 100
                    street: Postbus
                    city: Leusden
                    municipality: Leusden
                    province: Utrecht
                    location: null
        '400':
          $ref: '#/components/responses/ProblemValidation'
        '401':
          $ref: '#/components/responses/ProblemUnauthorized'
        '403':
          $ref: '#/components/responses/ProblemForbidden'
        '404':
          $ref: '#/components/responses/ProblemNotFound'
        '429':
          $ref: '#/components/responses/ProblemTooManyRequests'

  /v1/addresses:
    get:
      tags: [v1 adressen]
      summary: Alle adressen bij een postcode, inclusief toevoegingen
      description: |
        Anders dan v3 geeft dit endpoint alle treffers terug. Een pand met
        toevoegingen levert dus meerdere rijen op.

        De postcode mag hier wel een spatie bevatten: v1 is onze eigen API en
        accepteert ruwe invoer uit een formulier.
      operationId: v1Addresses
      parameters:
        - name: postcode
          in: query
          required: true
          schema:
            type: string
            example: 6545CA
        - name: number
          in: query
          required: false
          schema:
            type: integer
            example: 29
        - name: letter
          in: query
          required: false
          description: Huisletter, een enkel teken.
          schema:
            type: string
            example: A
        - name: addition
          in: query
          required: false
          description: Huisnummertoevoeging.
          schema:
            type: string
            example: '2'
      responses:
        '200':
          description: Nul of meer adressen.
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/V1Address'
                  meta:
                    type: object
                    properties:
                      count:
                        type: integer
                        example: 1
              example:
                data:
                  - id: '0363200012101386'
                    postcode: '1021JT'
                    number: 19
                    letter: null
                    addition: null
                    street: Hamerstraat
                    city: Amsterdam
                    municipality: Amsterdam
                    province: Noord-Holland
                    location:
                      type: Point
                      coordinates: [4.9220415, 52.3848866]
                    purposes: [woonfunctie]
                    surface: 98
                    constructionYear: 1930
                    type: verblijfsobject
                meta:
                  count: 1
        '400':
          $ref: '#/components/responses/ErrorBadRequest'
        '401':
          $ref: '#/components/responses/ErrorUnauthorized'
        '403':
          $ref: '#/components/responses/ErrorForbidden'
        '429':
          $ref: '#/components/responses/ErrorTooManyRequests'

  /v1/addresses/{bagId}:
    get:
      tags: [v1 adressen]
      summary: Eén adres op BAG-nummeraanduiding
      operationId: v1Address
      parameters:
        - name: bagId
          in: path
          required: true
          description: De nummeraanduidingidentificatie uit de BAG.
          schema:
            type: string
            example: '0268200000075154'
      responses:
        '200':
          description: Het adres is gevonden.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/V1Address'
        '401':
          $ref: '#/components/responses/ErrorUnauthorized'
        '404':
          $ref: '#/components/responses/ErrorNotFound'
        '429':
          $ref: '#/components/responses/ErrorTooManyRequests'

  /v1/postcodes/{postcode}:
    get:
      tags: [v1 adressen]
      summary: Samenvatting van een P6-postcode
      description: |
        Geeft de dominante straat, de plaats, de gemeente, de provincie, het
        zwaartepunt en het aantal adressen binnen de postcode.
      operationId: v1Postcode
      parameters:
        - name: postcode
          in: path
          required: true
          schema:
            type: string
            example: 1021JT
      responses:
        '200':
          description: De samenvatting.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/V1PostcodeSummary'
              example:
                data:
                  postcode: '1021JT'
                  street: Hamerstraat
                  city: Amsterdam
                  municipality: Amsterdam
                  province: Noord-Holland
                  location:
                    type: Point
                    coordinates: [4.9220415, 52.3848866]
                  addressCount: 1
                  numberRange:
                    min: 19
                    max: 19
                  isPoBox: false
        '400':
          $ref: '#/components/responses/ErrorBadRequest'
        '401':
          $ref: '#/components/responses/ErrorUnauthorized'
        '404':
          $ref: '#/components/responses/ErrorNotFound'
        '429':
          $ref: '#/components/responses/ErrorTooManyRequests'

  /v1/autocomplete:
    get:
      tags: [v1 zoeken]
      summary: Type-ahead op adres, straat of plaats
      description: |
        Bedoeld om vanuit de browser aan te roepen tijdens het typen in een
        afrekenpagina. Alle zoekacties zijn prefixzoekacties.

        Roep je dit endpoint uit een browser aan, zet dan de toegestane origins
        op de sleutel. Een sleutel zonder originlijst is bedoeld voor gebruik
        vanaf een server.
      operationId: v1Autocomplete
      parameters:
        - name: q
          in: query
          required: true
          description: Minimaal twee tekens. Vier cijfers vooraan worden als postcode gelezen.
          schema:
            type: string
            example: Hamer
        - name: type
          in: query
          required: false
          schema:
            type: string
            enum: [all, address, street, city]
            default: all
        - name: limit
          in: query
          required: false
          description: Wordt afgetopt op de limiet uit de configuratie.
          schema:
            type: integer
            default: 10
            maximum: 10
      responses:
        '200':
          description: De suggesties.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Suggestion'
                  meta:
                    type: object
                    properties:
                      count: { type: integer }
                      query: { type: string }
                      type: { type: string }
                      limit: { type: integer }
              example:
                data:
                  - type: street
                    label: Hamerstraat, Amsterdam
                    value: Hamerstraat
                    postcode: null
                    street: Hamerstraat
                    city: Amsterdam
                    id: '0363300000003331'
                meta:
                  count: 1
                  query: Hamer
                  type: street
                  limit: 10
        '400':
          $ref: '#/components/responses/ErrorBadRequest'
        '401':
          $ref: '#/components/responses/ErrorUnauthorized'
        '403':
          $ref: '#/components/responses/ErrorForbidden'
        '429':
          $ref: '#/components/responses/ErrorTooManyRequests'

  /v1/reverse:
    get:
      tags: [v1 zoeken]
      summary: Dichtstbijzijnde adressen bij een punt
      description: |
        Zoekt eerst binnen een rechthoek rond het punt en rekent daarna de
        werkelijke afstand uit met de haversine-formule over een aardstraal van
        6.371.000 meter. De resultaten staan oplopend op afstand; `distance` is
        in meters, afgerond op één decimaal.
      operationId: v1Reverse
      parameters:
        - name: lat
          in: query
          required: true
          schema:
            type: number
            format: double
            example: 52.3858866
        - name: lon
          in: query
          required: true
          schema:
            type: number
            format: double
            example: 4.9220415
        - name: radius
          in: query
          required: false
          description: Straal in meters. Wordt afgetopt op 1000.
          schema:
            type: integer
            default: 1000
            maximum: 1000
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            default: 10
            maximum: 50
      responses:
        '200':
          description: De adressen binnen de straal.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/V1AddressWithDistance'
                  meta:
                    type: object
                    properties:
                      count: { type: integer }
                      lat: { type: number }
                      lon: { type: number }
                      radius: { type: integer }
                      unit: { type: string, example: meters }
              example:
                data:
                  - id: '0363200012101386'
                    postcode: '1021JT'
                    number: 19
                    letter: null
                    addition: null
                    street: Hamerstraat
                    city: Amsterdam
                    municipality: Amsterdam
                    province: Noord-Holland
                    location:
                      type: Point
                      coordinates: [4.9220415, 52.3848866]
                    purposes: [woonfunctie]
                    surface: 98
                    constructionYear: 1930
                    type: verblijfsobject
                    distance: 111.2
                meta:
                  count: 1
                  lat: 52.3858866
                  lon: 4.9220415
                  radius: 500
                  unit: meters
        '400':
          $ref: '#/components/responses/ErrorBadRequest'
        '401':
          $ref: '#/components/responses/ErrorUnauthorized'
        '403':
          $ref: '#/components/responses/ErrorForbidden'
        '429':
          $ref: '#/components/responses/ErrorTooManyRequests'

  /v1/bulk/lookup:
    post:
      tags: [v1 verwerking]
      summary: Meerdere adressen in één aanroep
      description: |
        Elke regel telt als een aparte call voor de maandbundel. Het maximum
        aantal regels hangt af van het plan: Starter 25, Klein 100,
        Standaard 250, Plus 500, Pro en Enterprise 1.000.

        Regels die niets opleveren komen terug met `found: false` en een reden,
        zodat de volgorde van de invoer bewaard blijft.
      operationId: v1BulkLookup
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [addresses]
              properties:
                addresses:
                  type: array
                  minItems: 1
                  maxItems: 1000
                  items:
                    type: object
                    required: [postcode, number]
                    properties:
                      postcode: { type: string, example: '6545CA' }
                      number: { type: integer, example: 29 }
                      letter: { type: string, nullable: true }
                      addition: { type: string, nullable: true }
            example:
              addresses:
                - postcode: '1021JT'
                  number: 19
                - postcode: '1021JT'
                  number: 9999
      responses:
        '200':
          description: Voor elke regel een uitkomst, in dezelfde volgorde.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/BulkResult'
                  meta:
                    type: object
                    properties:
                      requested: { type: integer }
                      found: { type: integer }
                      billableCalls:
                        type: integer
                        description: Het aantal calls dat dit verzoek uit de bundel haalt.
              example:
                data:
                  - index: 0
                    postcode: '1021JT'
                    number: 19
                    found: true
                    error: null
                    address:
                      id: '0363200012101386'
                      postcode: '1021JT'
                      number: 19
                      letter: null
                      addition: null
                      street: Hamerstraat
                      city: Amsterdam
                      municipality: Amsterdam
                      province: Noord-Holland
                      location:
                        type: Point
                        coordinates: [4.9220415, 52.3848866]
                      purposes: [woonfunctie]
                      surface: 98
                      constructionYear: 1930
                      type: verblijfsobject
                  - index: 1
                    postcode: '1021JT'
                    number: 9999
                    found: false
                    error: not_found
                    address: null
                meta:
                  requested: 2
                  found: 1
                  billableCalls: 2
        '400':
          $ref: '#/components/responses/ErrorBadRequest'
        '401':
          $ref: '#/components/responses/ErrorUnauthorized'
        '403':
          $ref: '#/components/responses/ErrorForbidden'
        '422':
          description: Meer regels dan het plan toestaat.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: too_many_items
                  message: Een bulkverzoek bevat maximaal 100 regels, dit verzoek heeft er 101.
        '429':
          $ref: '#/components/responses/ErrorTooManyRequests'

  /v1/validate:
    post:
      tags: [v1 verwerking]
      summary: Adres controleren met correctievoorstel
      description: |
        Zegt of het adres bestaat en doet bij een kleine afwijking een voorstel.

        De mogelijke waarden van `reason`:

        | reason | Betekenis |
        |---|---|
        | `null` | Het adres klopt. |
        | `invalid_postcode` | De postcode voldoet niet aan het patroon. |
        | `field_mismatch` | Het adres bestaat, maar `street` of `city` klopt niet. Zie `corrections`. |
        | `number_not_found` | De postcode bestaat, het huisnummer niet. `suggestion` bevat het dichtstbijzijnde nummer. |
        | `postcode_not_found` | De postcode bestaat niet. `suggestion` bevat een adres op een postcode die er hooguit twee tekens naast zit. |
        | `not_found` | Geen adres en geen bruikbaar voorstel. |
      operationId: v1Validate
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [postcode, number]
              properties:
                postcode: { type: string, example: '1021JT' }
                number: { type: integer, example: 19 }
                letter: { type: string, nullable: true }
                addition: { type: string, nullable: true }
                street:
                  type: string
                  nullable: true
                  description: Optioneel. Wordt soepel vergeleken, dus hoofdletters en spaties maken niet uit.
                city:
                  type: string
                  nullable: true
                  description: Optioneel.
            example:
              postcode: '1021JT'
              number: 19
              street: Hamerstraat
              city: Amsterdam
      responses:
        '200':
          description: Het oordeel. Ook een ongeldig adres levert een 200 op.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/ValidationResult'
              examples:
                klopt:
                  summary: Adres klopt
                  value:
                    data:
                      valid: true
                      reason: null
                      address:
                        id: '0363200012101386'
                        postcode: '1021JT'
                        number: 19
                        letter: null
                        addition: null
                        street: Hamerstraat
                        city: Amsterdam
                        municipality: Amsterdam
                        province: Noord-Holland
                        location:
                          type: Point
                          coordinates: [4.9220415, 52.3848866]
                        purposes: [woonfunctie]
                        surface: 98
                        constructionYear: 1930
                        type: verblijfsobject
                      corrections: {}
                      suggestion: null
                straatnaamFout:
                  summary: Straatnaam verkeerd overgetypt
                  value:
                    data:
                      valid: false
                      reason: field_mismatch
                      address:
                        id: '0363200012101386'
                        postcode: '1021JT'
                        number: 19
                        letter: null
                        addition: null
                        street: Hamerstraat
                        city: Amsterdam
                        municipality: Amsterdam
                        province: Noord-Holland
                        location:
                          type: Point
                          coordinates: [4.9220415, 52.3848866]
                        purposes: [woonfunctie]
                        surface: 98
                        constructionYear: 1930
                        type: verblijfsobject
                      corrections:
                        street: Hamerstraat
                      suggestion:
                        id: '0363200012101386'
                        postcode: '1021JT'
                        number: 19
                        letter: null
                        addition: null
                        street: Hamerstraat
                        city: Amsterdam
                        municipality: Amsterdam
                        province: Noord-Holland
                        location:
                          type: Point
                          coordinates: [4.9220415, 52.3848866]
                        purposes: [woonfunctie]
                        surface: 98
                        constructionYear: 1930
                        type: verblijfsobject
                postcodeTypefout:
                  summary: Typefout in de postcode
                  value:
                    data:
                      valid: false
                      reason: postcode_not_found
                      address: null
                      corrections: {}
                      suggestion:
                        id: '0363200012101386'
                        postcode: '1021JT'
                        number: 19
                        letter: null
                        addition: null
                        street: Hamerstraat
                        city: Amsterdam
                        municipality: Amsterdam
                        province: Noord-Holland
                        location:
                          type: Point
                          coordinates: [4.9220415, 52.3848866]
                        purposes: [woonfunctie]
                        surface: 98
                        constructionYear: 1930
                        type: verblijfsobject
        '400':
          $ref: '#/components/responses/ErrorBadRequest'
        '401':
          $ref: '#/components/responses/ErrorUnauthorized'
        '403':
          $ref: '#/components/responses/ErrorForbidden'
        '429':
          $ref: '#/components/responses/ErrorTooManyRequests'

components:

  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-Api-Key
      description: |
        De enige ondersteunde manier van authenticeren. Een live-sleutel begint
        met `lat_live_`, een sandboxsleutel met `lat_test_`. Een sleutel werkt
        alleen in zijn eigen omgeving.

        Een sleutel kan beperkt worden tot bepaalde IP-adressen en tot bepaalde
        origins. Voldoet een aanroep daar niet aan, dan is het antwoord een 401,
        gelijk aan het antwoord op een onbekende sleutel.

  headers:
    XRateLimitLimit:
      description: Het aantal calls per seconde dat dit plan toestaat.
      schema: { type: integer, example: 10 }
    XRateLimitRemaining:
      description: Wat er op dit moment nog in de emmer zit.
      schema: { type: integer, example: 9 }
    XRateLimitReset:
      description: Unix-tijdstip waarop de emmer weer vol is.
      schema: { type: integer, example: 1780000001 }
    XQuotaLimit:
      description: De maandbundel. `-1` betekent onbeperkt, wat in de sandbox het geval is.
      schema: { type: integer, example: 5000 }
    XQuotaRemaining:
      description: Wat er nog over is van de bundel. `-1` bij onbeperkt.
      schema: { type: integer, example: 4873 }
    XQuotaReset:
      description: Unix-tijdstip waarop de factuurperiode afloopt.
      schema: { type: integer, example: 1782518399 }
    XRequestId:
      description: Uniek per aanroep. Noem dit nummer bij een supportvraag.
      schema: { type: string, format: uuid }

  schemas:

    GeoJsonPoint:
      type: object
      nullable: true
      description: |
        GeoJSON-punt. De coordinaten staan in de volgorde longitude, latitude,
        zoals GeoJSON voorschrijft. Bij een postbus is dit veld `null`.
      required: [type, coordinates]
      properties:
        type:
          type: string
          enum: [Point]
        coordinates:
          type: array
          minItems: 2
          maxItems: 2
          items:
            type: number
            format: double
          example: [5.858910083770752, 51.84376540294041]

    V3Address:
      type: object
      description: |
        De velden staan in deze volgorde en zijn altijd alle zeven aanwezig,
        ook als ze leeg zijn.
      required: [postcode, number, street, city, municipality, province, location]
      properties:
        postcode:
          type: string
          description: P6, hoofdletters, zonder spatie.
          example: '6545CA'
        number:
          type: integer
          example: 29
        street:
          type: string
          description: Bij een postbus letterlijk de tekst `Postbus`.
          example: Binderskampweg
        city:
          type: string
          example: Nijmegen
        municipality:
          type: string
          example: Nijmegen
        province:
          type: string
          example: Gelderland
        location:
          $ref: '#/components/schemas/GeoJsonPoint'

    V1Address:
      type: object
      required:
        [id, postcode, number, letter, addition, street, city, municipality,
         province, location, purposes, surface, constructionYear, type]
      properties:
        id:
          type: string
          description: BAG-nummeraanduidingidentificatie.
          example: '0268200000075154'
        postcode: { type: string, example: '6545CA' }
        number: { type: integer, example: 29 }
        letter: { type: string, nullable: true, example: null }
        addition: { type: string, nullable: true, example: null }
        street: { type: string, example: Binderskampweg }
        city: { type: string, example: Nijmegen }
        municipality: { type: string, example: Nijmegen }
        province: { type: string, example: Gelderland }
        location:
          $ref: '#/components/schemas/GeoJsonPoint'
        purposes:
          type: array
          description: BAG-gebruiksdoel.
          items: { type: string }
          example: [woonfunctie]
        surface:
          type: integer
          nullable: true
          description: Oppervlakte in vierkante meters.
          example: 142
        constructionYear:
          type: integer
          nullable: true
          example: 2006
        type:
          type: string
          description: verblijfsobject, ligplaats, standplaats of postbus.
          example: verblijfsobject

    V1AddressWithDistance:
      allOf:
        - $ref: '#/components/schemas/V1Address'
        - type: object
          properties:
            distance:
              type: number
              format: double
              description: Afstand in meters, afgerond op één decimaal.
              example: 111.2

    V1PostcodeSummary:
      type: object
      properties:
        postcode: { type: string, example: '1021JT' }
        street: { type: string, example: Hamerstraat }
        city: { type: string, example: Amsterdam }
        municipality: { type: string, example: Amsterdam }
        province: { type: string, example: Noord-Holland }
        location:
          $ref: '#/components/schemas/GeoJsonPoint'
        addressCount: { type: integer, example: 24 }
        numberRange:
          type: object
          properties:
            min: { type: integer, nullable: true, example: 1 }
            max: { type: integer, nullable: true, example: 47 }
        isPoBox: { type: boolean, example: false }

    Suggestion:
      type: object
      properties:
        type:
          type: string
          enum: [address, street, city]
        label:
          type: string
          description: Klaar om in een keuzelijst te tonen.
          example: Hamerstraat, Amsterdam
        value:
          type: string
          description: De waarde om in het invoerveld te zetten.
          example: Hamerstraat
        postcode: { type: string, nullable: true }
        street: { type: string, nullable: true }
        city: { type: string, nullable: true }
        id: { type: string, nullable: true }

    BulkResult:
      type: object
      properties:
        index:
          type: integer
          description: De positie in de aangeleverde lijst.
        postcode: { type: string, nullable: true }
        number: { type: integer, nullable: true }
        found: { type: boolean }
        error:
          type: string
          nullable: true
          enum: [not_found, invalid_postcode, invalid_number, null]
        address:
          allOf:
            - $ref: '#/components/schemas/V1Address'
          nullable: true

    ValidationResult:
      type: object
      properties:
        valid: { type: boolean }
        reason:
          type: string
          nullable: true
          enum: [invalid_postcode, field_mismatch, number_not_found, postcode_not_found, not_found, null]
        address:
          allOf:
            - $ref: '#/components/schemas/V1Address'
          nullable: true
        corrections:
          type: object
          description: Alleen de velden die niet klopten, met de juiste waarde.
          additionalProperties: { type: string }
          example:
            street: Hamerstraat
        suggestion:
          allOf:
            - $ref: '#/components/schemas/V1Address'
          nullable: true

    Problem:
      type: object
      description: Het foutformaat van v3, gelijk aan dat van postcodeapi.nu.
      required: [title]
      properties:
        title:
          type: string
        invalidParams:
          type: array
          items:
            type: object
            properties:
              name: { type: string }
              reason: { type: string }

    Error:
      type: object
      description: Het foutformaat van v1.
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code: { type: string }
            message: { type: string }

    SandboxCases:
      type: object
      description: |
        De sandbox is een vaste set en antwoordt altijd hetzelfde, ongeacht de
        stand van de database:

        | Postcode | Nummer | Antwoord |
        |---|---|---|
        | 6545CA | 29 | 200, Waldeck Pyrmontsingel, Nijmegen, Gelderland |
        | 1021JT | 19 | 200, Hamerstraat, Amsterdam, Noord-Holland |
        | 5038EA | 17 | 200, Stationsstraat, Tilburg, Noord-Brabant |
        | 3030AC | 100 | 200, Postbus, Leusden, Utrecht, location null |
        | 6545CA | 29a | 400 |
        | 6545C | 29 | 400 |
        | 6545CA | 299 | 404 |

        Let op: in de sandbox heet 6545CA 29 `Waldeck Pyrmontsingel`. De echte
        BAG-data voor dat adres zegt `Binderskampweg`; dat is wat de live-API
        teruggeeft.

  responses:

    ProblemValidation:
      description: De aanvraag klopt niet.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          examples:
            toevoegingInHetPad:
              summary: 29a in plaats van 29
              value:
                title: Request validation failed
                invalidParams:
                  - name: number
                    reason: should be integer
            postcodeMetSpatie:
              summary: 6545 CA in plaats van 6545CA
              value:
                title: Request validation failed
                invalidParams:
                  - name: postcode
                    reason: should match pattern "^[0-9]{4}[a-zA-Z]{2}$"

    ProblemUnauthorized:
      description: |
        De sleutel ontbreekt, bestaat niet, is ingetrokken, hoort bij een andere
        omgeving of mag niet vanaf dit IP-adres of deze origin gebruikt worden.
        Alle gevallen geven hetzelfde antwoord.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            title: Invalid API key

    ProblemForbidden:
      description: Dit endpoint zit niet in het plan van deze klant.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            title: Endpoint not available for this plan

    ProblemNotFound:
      description: Er is geen adres bij deze combinatie van postcode en huisnummer.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            title: Resource not found

    ProblemTooManyRequests:
      description: |
        De snelheidslimiet of de maandbundel is bereikt. De header `Retry-After`
        geeft aan na hoeveel seconden het weer zin heeft.
      headers:
        Retry-After:
          schema: { type: integer, example: 1 }
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          examples:
            snelheid:
              summary: Te veel calls per seconde
              value:
                title: Rate limit exceeded
            bundel:
              summary: Maandbundel op, inclusief de coulance van 10%
              value:
                title: Quota exceeded

    ErrorBadRequest:
      description: De aanvraag klopt niet.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: invalid_postcode
              message: Geef een geldige postcode op, bijvoorbeeld 6545CA.

    ErrorUnauthorized:
      description: De sleutel deugt niet. Zie de toelichting bij de 401 van v3.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: invalid_api_key
              message: Invalid API key

    ErrorForbidden:
      description: Dit endpoint zit niet in het plan van deze klant.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: plan_upgrade_required
              message: Het endpoint autocomplete zit niet in het plan gratis.

    ErrorNotFound:
      description: Niet gevonden.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: not_found
              message: Er is geen adres met dit BAG-id.

    ErrorTooManyRequests:
      description: De snelheidslimiet of de maandbundel is bereikt.
      headers:
        Retry-After:
          schema: { type: integer, example: 1 }
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: rate_limit_exceeded
              message: Rate limit exceeded
