KVK API with Go: Querying Dutch Company Data in Golang
Integrate the KVKBase API into your Go project. Includes code examples for HTTP clients, structs, error handling, and a reusable client library — suitable for both beginners and experienced Go developers.
Go is one of the fastest-growing programming languages for backend services, microservices, and command-line tools. Its combination of high performance, straightforward concurrency, and a powerful standard library makes Go a popular choice for teams building reliable API integrations. In this guide, we show you how to integrate the KVKBase API into your Go project to query Dutch company data.
Why Go for KVK integrations?
Go has an excellent built-in HTTP client, solid JSON support, and strict typing via structs. That makes it ideal for API integrations where you need to:
- Quickly validate KVK numbers during customer registration
- Retrieve company data for your CRM or ERP
- Batch-process large lists of KVK numbers
- Build a microservice or internal tool that needs business data
The KVKBase API returns structured JSON, which maps seamlessly to Go’s encoding/json package and struct definitions.
Requirements
- Go 1.21 or higher
- An API key from KVKBase
- No external dependencies needed — only the Go standard library
Step 1: Define your structs
Start by defining structs that represent the API response. Go’s JSON unmarshaling works best when you add struct tags matching the JSON field names.
// kvkbase/types.go
package kvkbase
type Address struct {
Street string `json:"straat"`
HouseNumber string `json:"huisnummer"`
PostalCode string `json:"postcode"`
City string `json:"plaats"`
Country string `json:"land"`
}
type SbiCode struct {
Code string `json:"code"`
Description string `json:"omschrijving"`
}
type Company struct {
KvkNumber string `json:"kvkNummer"`
Name string `json:"naam"`
LegalForm string `json:"rechtsvorm"`
Active bool `json:"actief"`
Address Address `json:"adres"`
SbiCodes []SbiCode `json:"sbiCodes"`
VatNumber string `json:"btwNummer,omitempty"`
}
type APIResponse struct {
Success bool `json:"succes"`
Data *Company `json:"data,omitempty"`
Error string `json:"fout,omitempty"`
}
Step 2: Build a reusable client
Create a client struct that manages your API key and HTTP client. This is the idiomatic Go approach: dependency injection instead of global variables.
// kvkbase/client.go
package kvkbase
import (
"encoding/json"
"fmt"
"net/http"
"time"
)
const baseURL = "https://api.kvkbase.nl/api/v1"
type Client struct {
apiKey string
httpClient *http.Client
}
func NewClient(apiKey string) *Client {
return &Client{
apiKey: apiKey,
httpClient: &http.Client{
Timeout: 10 * time.Second,
},
}
}
func (c *Client) GetCompany(kvkNumber string) (*Company, error) {
url := fmt.Sprintf("%s/kvk/%s", baseURL, kvkNumber)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("x-api-key", c.apiKey)
req.Header.Set("Accept", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("API call failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode == 404 {
return nil, fmt.Errorf("KVK number %s not found", kvkNumber)
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
var result APIResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("JSON decode failed: %w", err)
}
if !result.Success || result.Data == nil {
return nil, fmt.Errorf("API error: %s", result.Error)
}
return result.Data, nil
}
Step 3: Basic usage
// main.go
package main
import (
"fmt"
"log"
"os"
"yourproject/kvkbase"
)
func main() {
apiKey := os.Getenv("KVKBASE_API_KEY")
if apiKey == "" {
log.Fatal("KVKBASE_API_KEY environment variable not set")
}
client := kvkbase.NewClient(apiKey)
company, err := client.GetCompany("12345678")
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("Company name: %s\n", company.Name)
fmt.Printf("KVK number: %s\n", company.KvkNumber)
fmt.Printf("Legal form: %s\n", company.LegalForm)
fmt.Printf("Active: %v\n", company.Active)
fmt.Printf("Address: %s %s, %s %s\n",
company.Address.Street,
company.Address.HouseNumber,
company.Address.PostalCode,
company.Address.City,
)
}
Step 4: Concurrent batch lookups with goroutines
One of Go’s greatest strengths is concurrency through goroutines. This is especially useful when you need to process a list of KVK numbers without waiting for each request to complete sequentially.
// batch.go
package kvkbase
import (
"sync"
)
type BatchResult struct {
KvkNumber string
Company *Company
Err error
}
func (c *Client) BulkLookup(kvkNumbers []string, maxConcurrent int) []BatchResult {
results := make([]BatchResult, len(kvkNumbers))
sem := make(chan struct{}, maxConcurrent) // semaphore for rate limiting
var wg sync.WaitGroup
for i, kvk := range kvkNumbers {
wg.Add(1)
go func(idx int, number string) {
defer wg.Done()
sem <- struct{}{} // acquire slot
defer func() { <-sem }() // release slot
company, err := c.GetCompany(number)
results[idx] = BatchResult{
KvkNumber: number,
Company: company,
Err: err,
}
}(i, kvk)
}
wg.Wait()
return results
}
Set maxConcurrent to avoid hitting the API’s rate limits. A value of 5 to 10 is typically safe.
numbers := []string{"12345678", "87654321", "11223344"}
results := client.BulkLookup(numbers, 5)
for _, r := range results {
if r.Err != nil {
fmt.Printf("%s: ERROR - %v\n", r.KvkNumber, r.Err)
continue
}
fmt.Printf("%s: %s (%s)\n", r.KvkNumber, r.Company.Name, r.Company.LegalForm)
}
Error handling in Go
Go’s explicit error handling makes integrations robust. The client uses fmt.Errorf with %w for error wrapping, preserving context as errors propagate upstream:
company, err := client.GetCompany("00000000")
if err != nil {
if strings.Contains(err.Error(), "not found") {
// KVK number does not exist
fmt.Println("Unknown KVK number")
} else {
// Network or server error
log.Printf("API error: %v", err)
}
return
}
Using in an HTTP handler (Echo / Gin / chi)
If you’re using Go to build a web API, the pattern is the same. Here is an example using the popular chi framework:
// handlers/kvk.go
package handlers
import (
"encoding/json"
"net/http"
"yourproject/kvkbase"
"github.com/go-chi/chi/v5"
)
type KvkHandler struct {
client *kvkbase.Client
}
func NewKvkHandler(client *kvkbase.Client) *KvkHandler {
return &KvkHandler{client: client}
}
func (h *KvkHandler) GetCompany(w http.ResponseWriter, r *http.Request) {
kvkNumber := chi.URLParam(r, "kvkNumber")
company, err := h.client.GetCompany(kvkNumber)
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(company)
}
Register the handler in your router:
r := chi.NewRouter()
kvkHandler := handlers.NewKvkHandler(kvkbase.NewClient(os.Getenv("KVKBASE_API_KEY")))
r.Get("/api/company/{kvkNumber}", kvkHandler.GetCompany)
Testing
Go’s testing package is built in. Use a mock HTTP server to test your client without making real API calls:
// kvkbase/client_test.go
package kvkbase_test
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"yourproject/kvkbase"
)
func TestGetCompany(t *testing.T) {
// Set up mock server
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
response := kvkbase.APIResponse{
Success: true,
Data: &kvkbase.Company{
KvkNumber: "12345678",
Name: "Test BV",
LegalForm: "BV",
Active: true,
},
}
json.NewEncoder(w).Encode(response)
}))
defer server.Close()
client := kvkbase.NewClient("test-key")
company, err := client.GetCompany("12345678")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if company.Name != "Test BV" {
t.Errorf("Expected 'Test BV', got '%s'", company.Name)
}
}
Next steps
With this foundation you can go in any direction:
- Add caching with
sync.Mapor Redis to avoid repeat lookups - Integrate with your customer onboarding flow to auto-fill company details (see also: KVK data for customer onboarding)
- Build a webhook listener that reacts to company status changes
- Combine with company status monitoring for proactive alerts
Ready to start? Get your API key at kvkbase.nl.