CVR lookups in Go

In Go the standard library covers everything: <code>net/http</code> for the request and <code>encoding/json</code> to decode the response into a struct. No API key, no third-party packages.

Look up a company by CVR number

The struct only holds the fields you need. Fields in the response that aren't in the struct are ignored.

go
package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"time"
)

type Owner struct {
	Name           string `json:"name"`
	Type           string `json:"type"`
	OwnershipRange string `json:"ownership_range"`
}

type Company struct {
	Vat          int     `json:"vat"`
	Name         string  `json:"name"`
	Address      string  `json:"address"`
	Zipcode      int     `json:"zipcode"`
	City         string  `json:"city"`
	IndustryDesc string  `json:"industrydesc"`
	Protected    bool    `json:"protected"`
	Ejere        []Owner `json:"ejere"`
}

var ErrNotFound = errors.New("cvr: company not found")

var client = &http.Client{Timeout: 15 * time.Second}

func GetCompany(cvr int) (*Company, error) {
	res, err := client.Get(fmt.Sprintf("https://apicvr.dk/api/v1/%d", cvr))
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	switch res.StatusCode {
	case http.StatusOK:
		var c Company
		return &c, json.NewDecoder(res.Body).Decode(&c)
	case http.StatusNotFound:
		return nil, ErrNotFound
	default:
		return nil, fmt.Errorf("cvr: unexpected status %d", res.StatusCode)
	}
}

func main() {
	c, err := GetCompany(24256790)
	if err != nil {
		panic(err)
	}
	fmt.Println(c.Name, c.Zipcode, c.City)
	for _, o := range c.Ejere {
		fmt.Println(o.Name, o.OwnershipRange)
	}
}

Search by company name

Fuzzy search finds companies even when the name is slightly misspelled. Each hit has name, cvr_number, industrycode and industrytext.

go
type Hit struct {
	Name         string `json:"name"`
	CvrNumber    int    `json:"cvr_number"`
	IndustryText string `json:"industrytext"`
}

func Search(name string) ([]Hit, error) {
	res, err := client.Get("https://apicvr.dk/api/v1/search/fuzzy/" + url.PathEscape(name))
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	var hits []Hit
	return hits, json.NewDecoder(res.Body).Decode(&hits)
}

// Kræver import af "net/url".

Handle errors

StatusMeaning
200The company was found. The body is JSON.
404No company has that CVR number. Body: {"detail": {"error": "NOT_FOUND"}}
422The CVR number isn't a number.
502The CVR register didn't answer. Try again shortly.

In the example, 404 becomes ErrNotFound, so you can check for it with errors.Is(err, ErrNotFound). Other status codes return a plain error.

Always check protected before using the data for marketing. When it's true, the company has opted out of direct marketing.

Frequently asked questions

Why are some fields null?

Fields without a value in CVR are null in the JSON. With plain types in the struct you get the zero value (e.g. an empty string). Use pointers such as *string if you need to tell the difference.

Can I call the API concurrently from goroutines?

Yes, but keep the pace down, for example with a semaphore allowing a few requests at a time. If you need sustained heavy traffic, email [email protected].