 ##  [The official way takes days. The task must be solved today.](/node/194) 

    *Submitted by Lennart on Thu, 16 Apr 2026 - 15:56*  

  ![The official way takes days. The task must be solved today.](/sites/default/files/styles/wide/public/2026-04/composite_4.png.webp?itok=Rfuku_fj)

 

On Wednesday, I needed something mundane: a list of board members in a number of Danish companies. Not for a database. Not for a product. Just for an afternoon of lead research — who sits where, and which chairmen overlap with people I'm already talking to.

That kind of data is public. It's on `datacvr.virk.dk`. Every single company in Denmark has a page. It should take twenty minutes.

It didn't. And it's worth telling because it illustrates a very common tension in practical work with data and AI: **the official way is often the right way — but what do you do while it's being set up?**

## The Official Ways

For programmatic access to CVR data, there are two canonical ways:

1. **Datafordeler.dk** — the state's own distribution channel. Complete, free, maintained by the Danish Business Authority. Requires MitID Erhverv or email-based setup with IP whitelisting. Realistic timeframe: 1-3 business days for approval plus an evening for the actual integration.
2. **Cvrapi.dk premium** — commercial API on top of the same data. Premium token requires email contact. Also several days.

Both are the right choices in the long run. Neither solves my task today.

## The Open Page is Already There

While I waited, I manually opened a CVR page in the browser and looked. The board is listed there. The management is listed there. The chairman is marked. Each person is a clickable link to their own page, where all their board positions are listed.

All the data I need is in the HTML. The only obstacle is Cloudflare's bot protection, which returns 403 to anything that smells of a script.

`curl`: blocked. `http get` in nushell: blocked. WebFetch from my AI tool: blocked.

It's reasonable from virk.dk's side — they don't want automated traffic straining their servers. But it also means that a very common task has to be solved through a real browser.

## The Plugin That Made the Difference

[`nu_plugin_browse`](https://github.com/tyarel8/nu_plugin_browse) is a small nushell plugin written by Tyarel8. It exposes one new command: `http browse`. Behind the scenes, it opens Chrome (headless or visible), waits for a number of seconds, and returns the fully rendered HTML.

The interesting flag is `--with-head`. It opens a visible Chrome window instead of a headless one. Cloudflare's challenge system recognizes headless browsers as suspicious, but lets real browser windows through.

```
let html = (http browse --with-head --wait 12sec
  "https://datacvr.virk.dk/enhed/virksomhed/47458714")

```

A 12-second window opens. The Cloudflare challenge runs, the JavaScript application renders, and I get 422KB of HTML back. Inside it lies Lego's complete circle of people.

## From HTML to Structured Table

HTML is not a good format to work with. It needs to be transformed into something I can filter, sort, and cross-reference. This is where nushell's own strength comes in: `parse --regex` with named groups, and a small state machine in a `for` loop.

The idea is to find three kinds of "events" in document order:

- a category heading (Management, Board, Supervisory Board)
- a role in parentheses (Chairman, Vice-Chairman, Director)
- a person link with a name

Once you've captured them, you iterate linearly through the list, keeping track of the current category and a pending role. Every time a person appears, you emit a row with category + role + name.

The result is a regular nushell table:

categoryrolenameManagementDirectorJesper AndersenBoardChairmanCarsten RasmussenBoardVice-ChairmanPoul Hartvig NielsenBoardTrine ChristensenBoardSubstituteSidsel Numan AndersenWhich can be sorted, filtered, joined with other tables, and piped to anything. Total time from task to working tool: about an hour and a half, including finding the plugin.

## What I'm Not Saying

I'm not saying that scraping is better than the official API. It's not. This solution has clear weaknesses:

- It opens a visible browser window per lookup — not good for batch processing
- It's slow (10-15 seconds per company)
- It's fragile — if virk.dk changes its HTML layout, the parser will break
- It must respect virk.dk's resources — it's not a tool for fetching all Danish boards at night

The Datafordeler setup is running in parallel. When it's ready, I'll switch the backend. The interface itself — `cvr virk 47458714` — can remain the same.

## The Deeper Principle

This is a good example of what I call *compound systems*: a workflow consists of multiple components, and the right choice for each component depends on *what you need right now*, not what is the officially correct answer in its general form.

The components in my little tool are:

- a headless browser (Chrome) to interact with a protected website
- a plugin (nu\_plugin\_browse) that makes it accessible from the shell
- nushell itself for orchestration, parsing, and structuring
- a small regex-based state machine to convert HTML to a table
- a pipe architecture so the output can be used with anything else in my toolkit

None of the components are smart on their own. Together, they turn a data source that was otherwise practically inaccessible into a regular nushell table.

It's precisely the same architectural pattern as compound AI systems: small, deterministic, well-defined parts that individually solve a part of the problem, and which together become something usable. Generative AI is one component you can throw into such systems, but not necessarily the whole answer.

## What It Means for SMEs

Next time your team is faced with a data task where "we're waiting for access from IT/the vendor/the authority," ask yourself:

- Is the data publicly or semi-publicly available somewhere you can access it now?
- Is there a pragmatic way that delivers 80% of the value in 5% of the time, while the official way is being set up?
- What is the cost of waiting versus having a temporary solution that will break in three months?

The answer isn't always scraping. It could be a spreadsheet. It could be a CSV file sent manually once a week. It could be opening 20 websites manually the first time and automating it the second time.

But if the answer is always "we're waiting for the official integration," there's often half a year's work that just doesn't get done. That kind of cost is hard to see on a spreadsheet — but it's real.

## Finally

Thanks to Tyarel8 for [nu\_plugin\_browse](https://github.com/tyarel8/nu_plugin_browse). It's a small plugin with a big impact — precisely the kind of tool that makes the difference between "we're waiting for access" and "we have a working prototype tool ready."

The entire solution is available as a nushell module in my CVR toolkit. When the Datafordeler credentials arrive, I'll switch the backend. Until then: `cvr virk 47458714` returns Lego's board in 12 seconds. That's enough for me to move on to what I was actually supposed to do.