Skip to contents

The goal of kvkapiR is to provide a convenient R programming language interface to the Dutch Chamber of Commerce (KvK) APIs. This package is built using the httr2 package and follows best practices for wrapping APIs in R, as outlined in the httr2 documentation. It simplifies authentication, request handling, and response parsing when interacting with the KvK API.

The package provides access to all the KvK APIs:

  • KvK Search API
  • KvK Basisprofiel API
  • KvK Vestigingsprofiel API
  • KvK Naamgeving API

More details on the API can be found on the developers website of the KvK.

📋 Important Note: This package is developed independently and is not officially affiliated with or endorsed by the Dutch Chamber of Commerce (Kamer van Koophandel). It provides an unofficial interface to their public APIs.

Loading the package

API keys

Optaining an API key

To access the KvK API, you need an API key. You can apply for an API key and learn about different KvK APIs on the KvK Developer Portal.

Temporary API Key (Current Session Only)

If you only need an API key for the current session, use the kvk_set_api_key() function.

kvk_set_api_key(api_key = "your_api_key_here")

⚠️ Note: This only sets the key for the current R session. The key will be lost once you restart R.

Persistent API Key (Stored in .Renviron)

For persistent storage across R sessions, you can manually add your API key to your .Renviron file:

  1. Open your .Renviron file: usethis::edit_r_environ()
  2. Add the line: KVK_API_KEY=your_api_key_here
  3. Save the file and restart R

⚠️ Warning: Only store your API key this way if you can safely store it on your system.

Search the KvK API

The kvk_search() function is a wrapper for the KvK Search API. It automatically handles pagination to retrieve all available results, up to the API limit of 1,000 records.

Below are examples demonstrating how to perform searches and handle API responses.

Example 1: single variable, less then 1.000 records

Lets search for KvK registrations in the village of Koudum. Since this is a small village with fewer than 1.000 registered businesses (the API limit), all records are retrieved in one request.

koudum <- kvk_search(plaats = "Koudum")
koudum
#> # A tibble: 512 × 6
#>    kvkNummer vestigingsnummer naam                     adres        type  links 
#>    <chr>     <chr>            <chr>                    <list>       <chr> <list>
#>  1 01036576  000007810083     Stichting Gemeenschapsc… <named list> neve… <list>
#>  2 92692966  000032358083     M. de Boer               <named list> neve… <list>
#>  3 60112891  000045511098     Corriente 't Kofjehúske  <named list> neve… <list>
#>  4 40005051  000021251479     Bogerman                 <named list> neve… <list>
#>  5 30168016  000057781184     It Fûgelnêst             <named list> neve… <list>
#>  6 01123666  000040288196     Winterberging IJzendoorn <named list> hoof… <list>
#>  7 58496505  000027833429     HFK Verhuur              <named list> hoof… <list>
#>  8 62724843  000031708129     De Klink Exploitatie B.… <named list> hoof… <list>
#>  9 01091668  000000678279     Multiservice Beheer en … <named list> hoof… <list>
#> 10 41005555  000021707499     Stichting Jongerenwerk … <named list> hoof… <list>
#> # ℹ 502 more rows

Example 2: single variable, more then 1.000 records

The city of Rotterdam is significantly larger than Koudum. Searching for all registrations in Rotterdam will return more than 1,000 records, exceeding the API limit. When this happens, a warning is displayed:

rotterdam <- kvk_search(plaats = "Rotterdam")
#> ! API response contains more than 1000 results. Only the first 1000 will be retrieved.
rotterdam
#> # A tibble: 1,000 × 6
#>    kvkNummer vestigingsnummer naam                     adres        type  links 
#>    <chr>     <chr>            <chr>                    <list>       <chr> <list>
#>  1 30019112  000022201726     Albron Campus Catering   <named list> neve… <list>
#>  2 41126601  000023151153     Albert Schweitzer Groep… <named list> neve… <list>
#>  3 24483298  000023336749     Klooster Afrikaander     <named list> neve… <list>
#>  4 34360247  000022666397     UWV WERKbedrijf Rotterd… <named list> neve… <list>
#>  5 24483298  000022493352     Gemeentewerken Rotterda… <named list> neve… <list>
#>  6 24104827  000020343264     Simonis Verf B.V.        <named list> neve… <list>
#>  7 24335233  000010150471     Shop Rosie               <named list> neve… <list>
#>  8 24483298  000022962077     GGD Rotterdam-Rijnmond   <named list> neve… <list>
#>  9 34238535  000060066164     Levéo Groep B.V.         <named list> neve… <list>
#> 10 27295998  000059996013     Temporary Works Design … <named list> neve… <list>
#> # ℹ 990 more rows

Example 3: combining multiple search terms in variables

You can combine multiple search terms in any parameter to refine your search. The API treats multiple words within a parameter as an AND operation by default.

# Search for businesses with both "snackbar" and "hoekje" in their name
snackbar_hoekje <- kvk_search(naam = "snackbar hoekje")
snackbar_hoekje
#> # A tibble: 17 × 7
#>    kvkNummer vestigingsnummer naam       adres        type  links  vervallenNaam
#>    <chr>     <chr>            <chr>      <list>       <chr> <list> <chr>        
#>  1 01117517  000001223720     Snackbar … <named list> hoof… <list>  NA          
#>  2 08714826  000015163474     V.O.F. Sn… <named list> hoof… <list>  NA          
#>  3 01034036  000005461081     Bar-Snack… <named list> hoof… <list>  NA          
#>  4 67276644  000014002736     Café-Snac… <named list> hoof… <list>  NA          
#>  5 27238930  000004196724     Snackbar … <named list> hoof… <list>  NA          
#>  6 04034441  000009116559     Klaver 4 … <named list> hoof… <list>  NA          
#>  7 01048627  000003587142     Snackwage… <named list> hoof… <list> "SNACKBAR 'T…
#>  8 09053061  000006155480     Cafetaria… <named list> hoof… <list> "SNACKBAR 'T…
#>  9 33167324  000003928373     Cafetaria… <named list> hoof… <list> "Snackbar/Av…
#> 10 06046872  000014471450     Toon´s et… <named list> hoof… <list> "SNACKBAR 'T…
#> 11 34148178  000002749122     Grillroom… <named list> hoof… <list>  NA          
#> 12 81885326  000014587653     Snackbar … <named list> hoof… <list>  NA          
#> 13 01047770  000019937814     Jack's Ca… <named list> hoof… <list> "SNACKBAR \"…
#> 14 01117517  NA               Snackbar … <NULL>       rech… <list>  NA          
#> 15 08714826  NA               V.O.F. Sn… <NULL>       rech… <list>  NA          
#> 16 06046872  NA               Toon´s et… <NULL>       rech… <list> "SNACKBAR 'T…
#> 17 04034441  NA               Klaver 4 … <NULL>       rech… <list>  NA

Example 4: Searching with Multiple Criteria

You can combine multiple search criteria in a query.

For example, let’s search for businesses in Utrecht where the name contains both snackbar and hoek:

snackbar <- kvk_search(plaats = "Utrecht", naam = "snackbar")
snackbar
#> # A tibble: 17 × 7
#>    kvkNummer vestigingsnummer naam       adres        type  links  vervallenNaam
#>    <chr>     <chr>            <chr>      <list>       <chr> <list> <chr>        
#>  1 30055828  000001534106     "Snackbar… <named list> hoof… <list>  NA          
#>  2 30149968  000001373366     "Snackbar… <named list> hoof… <list>  NA          
#>  3 30061168  000007852940     "Snackbar… <named list> hoof… <list>  NA          
#>  4 91479894  000057155518     "De Snack… <named list> hoof… <list>  NA          
#>  5 77162838  000044881207     "Snackbar… <named list> hoof… <list>  NA          
#>  6 30048901  000015639428     "Snackbar… <named list> neve… <list>  NA          
#>  7 63545659  000032452004     "Snackbar… <named list> hoof… <list>  NA          
#>  8 60975784  000028183169     "Snackbar… <named list> hoof… <list>  NA          
#>  9 91242894  000002767961     "Snackbar… <named list> hoof… <list>  NA          
#> 10 30083297  000009193693     "Cafetari… <named list> hoof… <list> "SNACKBAR HO…
#> 11 30050751  000007808208     "Petit-Re… <named list> hoof… <list> "Snackbar \"…
#> 12 30192953  000002554313     "Cafetari… <named list> hoof… <list> "Snackbar de…
#> 13 30156975  000013513508     "Bestaria… <named list> hoof… <list> "Snackbar \"…
#> 14 86470833  000005957397     "Giros"    <named list> hoof… <list> "Snackbar Gi…
#> 15 29036082  000020552270     "Restaura… <named list> hoof… <list> "Snackbar de…
#> 16 76537293  000044313047     "Biltstra… <named list> hoof… <list> "Snackbar de…
#> 17 76449548  000044233884     "Snackbar… <named list> hoof… <list>  NA

Example 5: Filtering by Business Type

The API allows filtering by business type using the type parameter. You can search for:

  • hoofdvestiging (main branch)
  • nevenvestiging (secondary branch)
  • rechtspersoon (legal entity)

You can also combine multiple types by passing the type parameter multiple times:

# Search for both main branches AND legal entities in Amsterdam
mixed_types <- kvk_search(
  plaats = "Amsterdam",
  type = "hoofdvestiging",
  type = "rechtspersoon"
)
#> ! API response contains more than 1000 results. Only the first 1000 will be retrieved.
mixed_types
#> # A tibble: 1,000 × 6
#>    kvkNummer vestigingsnummer naam                     adres        type  links 
#>    <chr>     <chr>            <chr>                    <list>       <chr> <list>
#>  1 41208816  000022202625     Stichting tot Behoud va… <named list> hoof… <list>
#>  2 52255786  000022204849     Groenhoed Servicios Y C… <named list> hoof… <list>
#>  3 52257673  000022206469     Ponooc B.V.              <named list> hoof… <list>
#>  4 52800636  000022712550     Werf 't Kromhout B.V.    <named list> hoof… <list>
#>  5 53190173  000023069244     Maxje-Saar B.V.          <named list> hoof… <list>
#>  6 53256417  000000970077     Boeketterie Wendy        <named list> hoof… <list>
#>  7 53225279  000023105658     AAC Rijsnel              <named list> hoof… <list>
#>  8 53034015  000022921362     Borealis Staete Partici… <named list> hoof… <list>
#>  9 41200805  000021790221     Kath. St. Bevordering v… <named list> hoof… <list>
#> 10 52117839  000022081356     MIH PayU B.V.            <named list> hoof… <list>
#> # ℹ 990 more rows

Example 6: Search by Address

You can search for businesses at a specific address using different combinations:

# Search businesses at a specific address
secret_address <- kvk_search(postcode = "2594BD", huisnummer = "10")
secret_address
#> # A tibble: 1 × 6
#>   kvkNummer vestigingsnummer naam    adres            type           links     
#>   <chr>     <chr>            <chr>   <list>           <chr>          <list>    
#> 1 57096317  000028603745     politie <named list [1]> nevenvestiging <list [2]>
# Search all businesses on a specific street
street_businesses <- kvk_search(straatnaam = "Bezuidenhoutseweg", plaats = "Den Haag")
street_businesses
#> # A tibble: 667 × 6
#>    kvkNummer vestigingsnummer naam                     adres        type  links 
#>    <chr>     <chr>            <chr>                    <list>       <chr> <list>
#>  1 32135977  000017722586     HE Den Haag B.V.         <named list> hoof… <list>
#>  2 80195644  000046563466     Crux Company B.V.        <named list> hoof… <list>
#>  3 57096317  000048659274     politie                  <named list> neve… <list>
#>  4 41085610  000020758170     Reclassering Nederland   <named list> neve… <list>
#>  5 57096317  000038480476     politie                  <named list> neve… <list>
#>  6 30046259  000005123917     Rabobank Regio Den Haag  <named list> neve… <list>
#>  7 50030817  000022709053     Kinderopvang Huisje Boo… <named list> neve… <list>
#>  8 27354017  000015683672     Tandartspraktijk A.G. S… <named list> hoof… <list>
#>  9 80942598  000047272619     JDMD Holding B.V.        <named list> hoof… <list>
#> 10 27185211  000003272141     Van Rijn Holding B.V.    <named list> hoof… <list>
#> # ℹ 657 more rows

Retrieving Profile Data

The package provides functions to retrieve three types of detailed profiles from the API:

  • Basisprofiel (Basic Profile) - General company information
  • Vestigingsprofiel (Establishment Profile) - Detailed branch/location information
  • Naamgeving (Name History) - Historical company names

💰 Note: Each profile retrieval costs EUR 0.02 per call (except for government organizations - see below).

Basisprofiel

The basic profile contains general company information:

# Get basic profile for a social organization (Stichting Gemeenschapscentrum Koudum)
stichting_profile <- kvk_get_basisprofiel(kvkNummer = "01036576")
stichting_profile
#> # A tibble: 1 × 10
#>   kvkNummer indNonMailing naam     materieleRegistratie…¹ totaalWerkzamePersonen
#>   <chr>     <chr>         <chr>    <chr>                                   <int>
#> 1 01036576  Nee           Stichti… 19660217                                    5
#> # ℹ abbreviated name: ¹​materieleRegistratie$datumAanvang
#> # ℹ 5 more variables: statutaireNaam <chr>, handelsnamen <list>,
#> #   sbiActiviteiten <list>, links <list>, `_embedded` <list>

Vestigingsprofiel

The establishment profile provides detailed information about a specific location, including geographical data:

# Get establishment profile for the same organization
stichting_vestiging <- kvk_get_vestigingsprofiel(vestigingsnummer = "000007810083")
stichting_vestiging
#> # A tibble: 1 × 16
#>   vestigingsnummer kvkNummer rsin      indNonMailing materieleRegistratie$datu…¹
#>   <chr>            <chr>     <chr>     <chr>         <chr>                      
#> 1 000007810083     01036576  002897295 Nee           19840825                   
#> # ℹ abbreviated name: ¹​materieleRegistratie$datumAanvang
#> # ℹ 11 more variables: statutaireNaam <chr>, eersteHandelsnaam <chr>,
#> #   indHoofdvestiging <chr>, indCommercieleVestiging <chr>,
#> #   voltijdWerkzamePersonen <int>, totaalWerkzamePersonen <int>,
#> #   deeltijdWerkzamePersonen <int>, handelsnamen <list>, adressen <list>,
#> #   sbiActiviteiten <list>, links <list>

# The vestigingsprofiel contains geographical data for mapping and analysis
# Access coordinates through the nested 'adressen' column:
if (nrow(stichting_vestiging) > 0 && length(stichting_vestiging$adressen[[1]]) > 0) {
  address_data <- stichting_vestiging$adressen[[1]]
  if ("geoData" %in% names(address_data)) {
    geo_info <- address_data$geoData
    cat("Geographical coordinates available for mapping and spatial analysis\n")
  }
}

Naamgeving

The name history shows all historical names of a company:

# Get name history
stichting_names <- kvk_get_naamgeving(kvkNummer = "01036576")
stichting_names
#> # A tibble: 1 × 7
#>   kvkNummer rsin      statutaireNaam         naam  startdatum vestigingen links 
#>   <chr>     <chr>     <chr>                  <chr> <chr>      <list>      <list>
#> 1 01036576  002897295 Stichting Gemeenschap… Stic… 19660217   <list [2]>  <list>

Usage Tracking and Cost Management

The kvkapiR package includes automatic session-based usage tracking to help you monitor API calls and costs within your current R session.

View Usage Report

Use kvk_usage_report() to see your current session’s API usage and costs:

# View summary report (default format)
kvk_usage_report()
#> 
#> ── KvK API Usage Report - Current Session ──
#> 
#> ── API Calls
#> • Search calls (free): 7
#> • Profile calls (EUR 0.02 each): 3
#> • Total calls: 10
#> 
#> ── Session Costs
#> • Paid API calls: 3 x EUR 0.02 = EUR 0.06
#> 

# Get structured tibble format for data analysis
usage_data <- kvk_usage_report(format = "tibble")
usage_data
#> # A tibble: 1 × 6
#>   Search Basisprofiel Vestiging Naamgeving `Total Calls` `Costs (EUR)`
#>    <int>        <int>     <int>      <int>         <int>         <dbl>
#> 1      7            1         1          1            10          0.06

# Get detailed format with one row per API call
usage_detailed <- kvk_usage_report(format = "detailed")
usage_detailed
#> # A tibble: 10 × 6
#>    timestamp           date        year month call_type         test_environment
#>    <dttm>              <date>     <int> <int> <chr>             <lgl>           
#>  1 2025-06-22 21:09:16 2025-06-22  2025     6 search            FALSE           
#>  2 2025-06-22 21:09:20 2025-06-22  2025     6 search            FALSE           
#>  3 2025-06-22 21:09:20 2025-06-22  2025     6 search            FALSE           
#>  4 2025-06-22 21:09:21 2025-06-22  2025     6 search            FALSE           
#>  5 2025-06-22 21:09:24 2025-06-22  2025     6 search            FALSE           
#>  6 2025-06-22 21:09:24 2025-06-22  2025     6 search            FALSE           
#>  7 2025-06-22 21:09:26 2025-06-22  2025     6 search            FALSE           
#>  8 2025-06-22 21:09:26 2025-06-22  2025     6 basisprofiel      FALSE           
#>  9 2025-06-22 21:09:27 2025-06-22  2025     6 vestigingsprofiel FALSE           
#> 10 2025-06-22 21:09:27 2025-06-22  2025     6 naamgeving        FALSE

The different output formats serve different purposes:

  • “summary” (default): Human-readable overview with session costs and totals
  • “tibble”: Structured summary of your session usage
  • “detailed”: One row per API call, perfect for detailed analysis

The report shows your current session costs: - Per-query costs: EUR 0.02 for profile retrievals (search calls are free) - Total session costs and call counts by type

Set Usage Alerts

Protect against unexpected costs with session-based usage alerts:

# Set session cost limit of EUR 5.00
kvk_usage_alert(max_cost = 5.00)
#>  Max cost: EUR 5.00 for this session

# Set session limit for paid API calls only (search calls don't count)
kvk_usage_alert(max_calls = 100)
#>  Max paid calls: 100 for this session

# Set both limits for your current session
kvk_usage_alert(max_calls = 100, max_cost = 5.00)
#>  Max paid calls: 100 for this session
#>  Max cost: EUR 5.00 for this session

# Disable all alerts for this session
kvk_usage_alert()
#>  All usage alerts have been disabled for this session

Alert features:

  • Paid calls only: Call limits apply only to paid API calls (basisprofiel, vestigingsprofiel, naamgeving)
  • Search calls are free: Search calls don’t count towards call limits
  • Session-based: Alerts apply only to your current R session
  • Real-time: Immediate notification when limits are exceeded

Export Usage Data

Export your session usage data for external analysis:

# Export session summary (default)
kvk_export_usage("session_summary.csv")

# Export detailed call data (one row per API call)
kvk_export_usage("detailed_calls.csv", format = "detailed")

Usage Tracking Privacy and Control

Session-Based Design: kvkapiR uses session-based usage tracking with these privacy features:

  • Session Only: All usage data is stored in memory for your current R session only
  • No File Storage: No data is written to your computer’s file system
  • No External Transmission: Nothing is sent to external servers
  • Automatic Reset: All tracking data is cleared when you restart R

Control Options:

# Disable tracking completely
Sys.setenv(KVKAPI_DISABLE_TRACKING = "true")

# Clear current session usage data
kvk_reset_usage()

Cost Information

⚠️ Important: The costs mentioned below are charged by the Dutch Chamber of Commerce (KvK) for API usage. These costs may change in the future, and any updates will be reflected in future versions of this package.

Pricing Structure

  • Monthly base fee: EUR 6.20 (charged when you have API access)
  • Search API: Free (after base fee)
  • Profile APIs: EUR 0.02 per call (basisprofiel, vestigingsprofiel, naamgeving)

API Access Requirements

Important: Only authorized business representatives can apply for API access and sign contracts with KvK. Private individuals cannot register for API access.

Authorized representatives include: - Business owners (sole proprietorships) - Directors of companies (BV, NV) - Board members of foundations and associations - Partners in partnerships (VOF, CV, Maatschap) - Authorized signatories with full or limited authority

Government Organizations

Government organizations are exempt from all API costs and can use all APIs without incurring charges.

Test Environment

KvK provides a test environment where you can experiment with the API before using it in production. More information is available on the KvK documentation page.

All search and profile retrieval functions in kvkapiR support testing mode via the test_environment argument, which is set to FALSE by default.

🦆 Tip: The test environment contains fictional data based on characters from the Dutch comic magazine “Donald Duck”. Try searching for names like “Donald” and “Dagobert” to see how the API works with sample data.

The test environment:

  • Uses a different base URL: https://api.kvk.nl/test/api/
  • Contains fictional business data for testing purposes
  • Does not require an API key (the package uses a built-in test key)
  • Has the same functionality and endpoints as production
  • Does not incur any costs
  • Does not count towards usage tracking
  • Displays informative messages for each API call, indicating test environment usage

Note: When using the test environment, each API call displays a message like:

ℹ You are using the KvK test environment. Test data will be returned.

Test Environment Examples

# Search in test environment - no API key needed
# Note: You'll see a message indicating test environment usage
test_results <- kvk_search(naam = "Donald", test_environment = TRUE)
#>  You are using the KvK test environment. Test data will be returned.
test_results
#> # A tibble: 3 × 6
#>   kvkNummer vestigingsnummer naam                      adres        type  links 
#>   <chr>     <chr>            <chr>                     <list>       <chr> <list>
#> 1 68750110  000037178601     Test BV Donald Nevenvest… <named list> neve… <list>
#> 2 68750110  000037178598     Test BV Donald            <named list> hoof… <list>
#> 3 68750110  NA               Test BV Donald            <NULL>       rech… <list>

# Get test profiles - no costs incurred
donald <- kvk_search(naam = "Donald", test_environment = TRUE)
#>  You are using the KvK test environment. Test data will be returned.
test_basisprofiel <- kvk_get_basisprofiel(
  kvkNummer = donald$kvkNummer[1], 
  test_environment = TRUE
)
#>  You are using the KvK test environment. Test data will be returned.
test_basisprofiel
#> # A tibble: 1 × 11
#>   kvkNummer indNonMailing naam     formeleRegistratieda…¹ materieleRegistratie…²
#>   <chr>     <chr>         <chr>    <chr>                  <chr>                 
#> 1 68750110  Ja            Test BV… 20170519               20170519              
#> # ℹ abbreviated names: ¹​formeleRegistratiedatum,
#> #   ²​materieleRegistratie$datumAanvang
#> # ℹ 6 more variables: totaalWerkzamePersonen <int>, statutaireNaam <chr>,
#> #   handelsnamen <list>, sbiActiviteiten <list>, links <list>,
#> #   `_embedded` <list>

test_vestigingsprofiel <- kvk_get_vestigingsprofiel(
  vestigingsnummer = donald$vestigingsnummer[1], 
  test_environment = TRUE
)
#>  You are using the KvK test environment. Test data will be returned.
test_vestigingsprofiel
#> # A tibble: 1 × 17
#>   vestigingsnummer kvkNummer rsin      indNonMailing formeleRegistratiedatum
#>   <chr>            <chr>     <chr>     <chr>         <chr>                  
#> 1 000037178601     68750110  857587973 Ja            20170519               
#> # ℹ 12 more variables: materieleRegistratie <tibble[,1]>, statutaireNaam <chr>,
#> #   eersteHandelsnaam <chr>, indHoofdvestiging <chr>,
#> #   indCommercieleVestiging <chr>, voltijdWerkzamePersonen <int>,
#> #   totaalWerkzamePersonen <int>, deeltijdWerkzamePersonen <int>,
#> #   handelsnamen <list>, adressen <list>, sbiActiviteiten <list>, links <list>

test_naamgeving <- kvk_get_naamgeving(
  kvkNummer = donald$kvkNummer[1], 
  test_environment = TRUE
)
#>  You are using the KvK test environment. Test data will be returned.
test_naamgeving
#> # A tibble: 1 × 7
#>   kvkNummer rsin      statutaireNaam naam          startdatum vestigingen links 
#>   <chr>     <chr>     <chr>          <chr>         <chr>      <list>      <list>
#> 1 68750110  857587973 Test BV Donald Test BV Dona… 20170519   <list [2]>  <list>

Conclusion

The kvkapiR package provides a comprehensive interface for accessing KvK business data in R with:

  • Simple authentication management
  • Full API coverage (search and profile retrieval)
  • Test environment support for development
  • Session-based usage tracking and cost monitoring
  • Flexible search options including multiple filters
  • Real-time usage alerts with smart filtering (paid calls only)
  • Government organization fee exemption
  • Privacy-focused design with no permanent file storage

For more details, refer to the official KvK API documentation.