Sentral Godkjenning Scraper - Norway Approved Contractors
Pricing
from $3.00 / 1,000 per firm returneds
Sentral Godkjenning Scraper - Norway Approved Contractors
From $3 per 1,000 rows. Scrape Norway's Sentral godkjenning register (DiBK, sgregister.dibk.no): all 10,408 centrally approved building enterprises with approval areas, tiltaksklasse, expiry date, insurance flags and address. Optional detail adds employee count. Approval-change monitor included.
Pricing
from $3.00 / 1,000 per firm returneds
Rating
0.0
(0)
Developer
Scrapers Delight
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Categories
Share
🇳🇴 Sentral Godkjenning Scraper — every centrally approved Norwegian building enterprise
Turn Norway's Sentral godkjenning register (sgregister.dibk.no) into a spreadsheet.
All 10,408 centrally approved building enterprises, each with its approval areas, tiltaksklasse,
approval expiry date, insurance flags and business address — plus an approval-change monitor that
tells you who was newly approved, who renewed, and who quietly dropped out.
Every number on this page was measured against the live register on 2026-09-08, not estimated.
Read this before you buy: the register carries no phone number and no e-mail address. Both keys exist in DiBK's own data and are
nullon all 10,408 rows — we emit the columns so nobody has to guess. What you get instead is the organisation number on 100% of rows (which joins straight to Brønnøysund/Brreg and to any Norwegian enrichment source you already use), a website on 17.8%, the postal address on 99.8%, and a dated approval-expiry on 100%.
🧭 What does Sentral Godkjenning Scraper do?
Sentral godkjenning ("central approval") is the Norwegian Building Authority's national quality scheme for the construction industry. A firm that holds it has been vetted by Direktoratet for byggkvalitet (DiBK) for specific functions (designing, executing, applying, inspecting) in specific subject areas (carpentry, groundworks, fire safety, sanitary, architecture …) at a specific tiltaksklasse (project-complexity tier 1–3). Approval runs for three years.
This Actor reads the whole register in one request and gives you:
| 🏗️ 10,408 approved enterprises | The complete register, not a sample |
| 🔢 35,726 approval-area rows | Average 3.4 per firm, maximum 30 |
| 🏅 Tiltaksklasse 1 / 2 / 3 | 5,258 · 3,157 · 1,993 — the big-project shortlist |
| 📅 Approval expiry on every row | 509 expire within 90 days, 2,719 within a year |
| 🧰 4 functions × 36 subject areas | Utførende 9,070 firms · Prosjekterende 4,705 · Ansvarlig søker 2,898 · Uavhengig kontrollerende 690 |
| 📍 1,209 postal towns | Oslo 1,070 · Trondheim 205 · Stavanger 159 · Sandnes 152 · Kristiansand S 151 · Bergen 125 |
| 🛡️ Insurance + apprentice flags | Liability 43.1% · industrial-injury 41.8% · approved training enterprise 43.7% |
| 🌐 Website | 1,854 firms (17.8%) |
| 👷 Employee count (optional) | Only on the detail page — never in the API |
| 🔔 Approval-change monitor | New / renewed / shortened / areas changed / lapsed |
🥇 The two things you cannot get anywhere else
1. Employee count and the approval start date
DiBK's API returns the approval end date but not the start, and it does not return company size at
all. Both sit on the firm's own register page as Antall lønnsmottakere and
Godkjenningsperiode. Turn on "Fetch employee count + approval start date" and the Actor
reads them.
Measured on a 20-firm random sample fetched through an Apify datacenter proxy:
20/20 pages returned HTTP 200 (average 23,381 bytes, 639 ms), 20/20 carried the employee
count, 20/20 carried the approval period, and the period's end date agreed with the API's
approval_period_to on 20/20 — which is what proves the parse is aligned to the right firm.
Employee count is sometimes a band ("1-4") rather than an integer. We keep the published value
verbatim in employee_count_raw and put the range in employee_count_min / employee_count_max.
It is never silently rounded into a single number.
2. The register has no memory — so the monitor is the archive
0 of 10,408 rows carry a past expiry date. The earliest is 2026-09-08 — tomorrow. DiBK publishes no historical endpoint and no lapsed-approvals endpoint: a firm is simply gone from the register the day its approval ends, with no record that it was ever there.
So a dated snapshot plus a diff is the only way anyone can observe an approval being granted or lost. Run this Actor in Monitor mode on a schedule and it keeps that series for you in a named key-value store, with one history line per run. Every run makes the archive more valuable, and it is not something a competitor can backfill.
⚡ Quick start
Press Start. The Actor ships prefilled with mode: register, maxFirms: 50 and enrichment off —
50 rows, about 7 seconds, $0.15. Then narrow it:
// The big-project key-account list in Oslo, soonest renewals first{"postalTowns": ["Oslo"],"minTiltaksklasse": 3,"sortBy": "approval_expiry","maxFirms": 0} // → 387 firms, ~4 s, $1.16
// Carpentry firms whose approval lapses inside 120 days — a dated renewal trigger{"subjectAreas": ["Tømrerarbeid og montering av trekonstruksjoner"],"expiringWithinDays": 120,"sortBy": "approval_expiry","maxFirms": 0} // → 252 firms, ~4 s, $0.76
// "Does this contractor actually hold central approval?" — no extra requests{"organizationalNumbers": ["811600792", "933697991", "999999999"],"maxFirms": 0} // → 3 rows; the third comes back approved:false, in_register:false
// Firm size for a shortlist: filter first, THEN enrich the slice{"postalTowns": ["Bergen"],"minTiltaksklasse": 2,"enrichWithDetails": true,"maxFirms": 0}
// Daily watch — schedule this and read the change rows{ "mode": "monitor", "monitorProfile": "norway-daily" }
📊 What you get — measured field fill over all 10,408 rows
| Field | Fill | Notes |
|---|---|---|
organizational_number | 100% (10,408) | 9 digits, joins to Brreg. No duplicates in the register. |
name | 100% | |
approved | 100% true | The register lists currently-valid approvals only. |
approval_period_to | 100% | ISO date. Range 2026-09-08 → 2029-09-07. |
days_until_expiry · renewal_window | 100% (derived) | expired / 0-30_days / 31-90_days / 91-180_days / 181-365_days / over_365_days |
approval_certificate_url | 100% | DiBK's official PDF certificate. |
business_country | 100% | NO 10,387 · SE 4 · DK 4 · FI 4 · LT 4 · PL 2 · LV 2 · HU 1 |
business_postal_code · business_postal_town | 99.8% (10,388) | |
business_address_line_1 | 98.8% (10,288) | Plus business_address_formatted for CRM import. |
valid_approval_areas[] | 100% | 35,726 entries; function, subject area, DiBK codes, tiltaksklasse, planning-act version. |
approval_area_count · max_tiltaksklasse | 100% (derived) | 1: 5,258 · 2: 3,157 · 3: 1,993 |
functions[] · subject_areas[] (+ codes) | 100% (derived) | Distinct values per firm; approval_areas_text is the flat CSV-friendly version. |
educational_enterprise_approved | 43.7% true | Approved to take apprentices (lærebedrift). |
liability_insurance | 43.1% true | |
industrial_injury_insurance | 41.8% true | |
www | 17.8% (1,854) | Say it plainly: fewer than one in five. |
postal_address_line_1 | 14.8% (1,542) | Separate postal address when it differs. |
likely_sole_proprietorship · company_form_suffix | 7.8% flagged (816) | Heuristic — see honest limits. |
phone | 0% (0 of 10,408) | The register carries none. The column exists and is null. |
email | 0% (0 of 10,408) | Same. |
employee_count_raw / _min / _max | detail only | 20/20 on the sampled pages. Sometimes a band. |
approval_period_from | detail only | 20/20 on the sampled pages. |
data_source · data_licence | 100% | CC BY 4.0 attribution, on every row. |
Monitor mode adds change_type, previous_approval_period_to, previous_approval_area_count,
previous_max_tiltaksklasse and change_detected_at.
🎛️ Filters — all of them run over the complete register
DiBK's API ignores every query parameter. Verified live on 2026-09-08: ?searchString=…,
?organizationalNumber=…, ?page=2&per_page=10 and ?limit=5 each returned the identical
582,138-byte body, and DiBK's own OpenAPI document defines no query parameters at all.
That sounds like a limitation. It is actually the best thing about this Actor's honesty: because the whole register is downloaded every time, a filter is never applied to a truncated page. When the run says "387 firms matched", that is 387 out of all 10,408 — never 387 out of the first page it happened to fetch. Sorting works the same way: the sort runs over every match and only then is your row cap applied, so "the 50 soonest renewals" really are the 50 soonest in Norway.
| Filter | What it does |
|---|---|
| Company-name search | Case- and diacritic-insensitive substring; "tomrer" finds "TØMRER" |
| Organisation numbers | Compliance lookup; not-found numbers come back as approved: false rows |
| Functions | Utførende / Prosjekterende / Ansvarlig søker / Uavhengig kontrollerende |
| Subject areas | All 36, each labelled with its real firm count. Or pass DiBK's 46 short codes. |
| Minimum tiltaksklasse | 1–3; 3 gives the 1,993-firm key-account list |
| Minimum approval areas | Breadth-of-capability proxy |
| Expiring within N days / before / after | The dated renewal trigger |
| Postal towns · postal-code prefixes · country | "tromso" matches all 64 TROMSØ firms |
| Insurance / apprentice-approval flags | |
| Only firms with a website | Cuts to the 1,854 that publish one |
| Exclude probable sole proprietorships | Drops the 816 personal-name rows |
Filtered-out rows are never billed. Filtering happens before delivery, so you pay only for rows that land in your dataset.
🔔 Monitor mode
{ "mode": "monitor", "monitorProfile": "norway-daily" }
- First run records the baseline: 10,408 firms into the named key-value store
sentral-godkjenning-monitor-state. It returns no rows and is not charged as a monitor run. - Every run after that re-reads the register and delivers one row per change:
change_type | Meaning |
|---|---|
new_approval | The firm was not in the register at the previous snapshot |
renewed | The approval end date moved later |
approval_shortened | The approval end date moved earlier |
areas_changed | Approval areas, their codes or the tiltaksklasse changed |
lapsed | The firm has dropped out of the register entirely |
The snapshot always covers the whole register even when your filters are narrow — otherwise "lapsed" would silently mean "stopped matching your filter". Filters are applied to the change rows you receive, and the log always reports the register-wide change counts too.
Two rules that stop a monitor from quietly losing an event — both worth reading before you schedule it:
- A
lapsedfirm is gone from DiBK, so only what the snapshot recorded can be filtered on: organisation number, name, postal town, approval end date, tiltaksklasse and approval-area count. Any other active filter — website, insurance, function, subject area, country, postcode, expiring-within, exclude-sole-proprietorships — cannot be evaluated for a firm that no longer exists in the register. When one of those is on, lapsed rows are withheld and not charged (never delivered-and-billed on a guess), the log names the filter that blocked them, and their snapshot entries are kept, so a later run without that filter still reports the lapse. - A change your
maxFirmscap or yourmaxTotalChargeUsddid not deliver is held, not lost. Those firms keep their previous snapshot entry, so the next run reports them again. The status message says how many are held. (Changes your ownchangeTypes/filters excluded are deliberate and do advance.)
monitorProfile lets one account keep several independent watchlists, each with its own snapshot and
its own run history (default: a year of daily runs).
Verified, not asserted. The store is named, so it survives between runs. Proven on the platform: a baseline run returned 0 rows and 0 monitor-run charges, and the next run read the snapshot back and reported
No approval changeswith exactly onemonitor-run-completedcharge. Proven locally with mutated state: three snapshot entries changed and two phantom firms injected, and the next run classified all five cases correctly — 1new_approval, 1renewed, 1approval_shortened, 1areas_changed, 2lapsed— and wrote them to the history record.
💵 Pricing
Pay per event. No monthly rental, no platform-usage surcharge, no charge for starting a run.
| Event | Price | Fires when |
|---|---|---|
| Per firm returned | $3.00 / 1,000 ($0.003) | A firm lands in your dataset |
| Per detail fetch | $3.00 / 1,000 ($0.003) | A detail page actually returned the employee count / start date |
| Per scheduled run | $50 / 1,000 ($0.05) | One monitoring run (never the baseline run) |
| Per approval change | $20 / 1,000 ($0.02) | One real difference against your previous snapshot |
Worked examples
| Job | Rows | Cost |
|---|---|---|
| Press Start (the 50-row prefill) | 50 | $0.15 |
| Oslo, tiltaksklasse 3, by expiry | 387 | $1.16 |
| Carpentry expiring within 120 days | 252 | $0.76 |
| Everything with a website + tiltaksklasse ≥ 2 | 1,318 | $3.95 |
| The entire register | 10,408 | $31.22 |
| The entire register + employee count on every firm | 10,408 | $62.45 |
| Daily monitor, 30 runs + ~25 changes/day | — | ≈ $16.50 / month |
Billing integrity, in plain terms
- Rows are written and charged atomically. If you set
maxTotalChargeUsd(or hit a free-tier balance), the run stops at your cap — it never hands you rows you were not billed for, and never bills you for rows it did not hand you. - Detail enrichment is charged only when the page returned the fields. A not-found page, an HTTP 429 or a timeout costs nothing.
- When enrichment is on, the run reserves budget for row + detail together before starting a batch, so the enrichment can never eat the cap and leave you with three rows.
- Both of Apify's auto-events are removed, including
apify-default-dataset-item— keeping it would double-charge against the per-firm event. - A run truncated by your cap says so in the status message and names the cap as the reason — and everything that message says about the delivered rows is counted over the rows you actually received, not over the pre-cap set.
- In Monitor mode, a change a cap or a charging limit withheld is held for the next run, and a lapsed firm whose match cannot be proven is withheld rather than billed. See the two rules in the Monitor section.
Measured on the live Actor, 2026-09-08: the 50-row prefill charged exactly firm-scraped: 50
across three runs; an 8-row enriched run charged firm-scraped: 8 + firm-detail-enriched: 8; the
whole-register run charged exactly firm-scraped: 10,408. With maxTotalChargeUsd set to $0.06
and enrichment on, the run delivered 10 rows and charged 10 + 10 events = $0.06 exactly — the
money-based reservation at work, since sizing that batch on the row price alone would have opened 20
rows and then run out of budget mid-enrichment.
⚙️ How it works, and what it costs to run
- One HTTP request for the whole register — no browser, no login, no API key, no CAPTCHA.
- The body is 20,021,162 bytes of JSON but only 582,138 bytes on the wire, because the Actor negotiates brotli — a 34× saving that makes proxy traffic a rounding error (~0.6 MB/run) rather than the main line on your bill.
- That 20 MB is stream-parsed one record at a time through an incremental splitter, never
buffered and
JSON.parsed whole. Peak memory on the platform for the worst case — all 10,408 rows retained, sorted and delivered — measured 159 / 164 / 187 / 229 MB across four runs of the 512 MB container (216 MB in a local run with no container limit). The high end of that spread, not the low end, is why this Actor declares 512 MB and not 256. - Measured on the platform across repeated runs: a 50-row run takes 3.4–8.4 s; the whole 10,408-row register takes 19–33 s end to end, including delivery.
- What a full run costs you in Apify platform usage — the honest number: $0.053, of which $0.052 is Apify's own dataset-write charge for 10,408 items and only $0.0008–0.0014 is compute. Platform usage is separate from this Actor's per-row price; both appear on your bill.
- No anti-bot of any kind. Apify's datacenter proxies returned HTTP 200 on 5 of 5 register fetches and 20 of 20 detail pages. Residential proxies are unnecessary here and would only add cost.
- The parser asserts on DiBK's response envelope and on the per-record keys. If the register's shape ever changes, the run fails loudly and charges nothing rather than delivering a mis-mapped dataset.
⚠️ Honest limits — please read before you buy
- No phone. No e-mail. 0 of 10,408 rows, on both. This is a firm-qualification and
renewal-timing dataset, not a dialable call list. Join on
organizational_numberif you need contact data. - Website on 17.8% only. 1,854 of 10,408.
- No county (fylke) field. DiBK publishes postal code, postal town and country — nothing else
geographic. This Actor does not invent a county from postcode ranges; use
postalTownsorpostalCodePrefixes. - Currently-valid approvals only. The register is a snapshot of who holds approval today. Every lapsed approval has already vanished. Monitor mode exists precisely because of this — but it can only see changes from the day you start running it.
likely_sole_proprietorshipis a heuristic. It flags the 816 names carrying no company-form suffix (AS, ASA, ANS, DA, NUF, KOMMUNE …). A trade name such as "TARALDSEN ELEKTRO" is also flagged and is not a person.company_form_suffixshows the raw evidence.- Personal data. Norwegian sole proprietorships are listed under the individual owner's own
name, and their registered business address is frequently a home address. This is a statutory
public register, but those rows are personal data about natural persons. Use
excludeSoleProprietorshipsif that matters for your use case. - Detail enrichment is deliberately slow. DiBK publishes a limit of 300 requests per
5 minutes per IP (= 1 request/second) for every endpoint and answers HTTP 429 above it. The
default rate is exactly that published number, from a single pinned proxy session. So: 400 firms
≈ 7 minutes, 2,000 ≈ 33 minutes, all 10,408 ≈ 2.9 hours. The right workflow is filter first,
then enrich the slice.
rotateDetailProxySessionswill spread the load over 20 sessions if you want it faster — that is your call, and it is labelled for what it is. - Rate-limited or failed detail fetches are never billed, and the row still arrives with
detail_complete: falseanddetail_skipped_reasonnaming the cause.
📜 Data source, licence and site policy
Data is published by Direktoratet for byggkvalitet (DiBK) — the Norwegian Building Authority —
at sgregister.dibk.no, openly and without registration.
Licence: Creative Commons Navngivelse 4.0 Internasjonal (CC BY 4.0). Commercial reuse is permitted. Attribution is a condition, not a courtesy, and DiBK specifies the form. Every single row this Actor returns therefore carries:
"data_source": "Datakilde: Direktoratet for byggkvalitet (hentet 2026-09-08)","data_licence": "CC BY 4.0 — https://creativecommons.org/licenses/by/4.0/deed.no"
Keep that attribution on anything you publish from this data.
robots.txt: https://sgregister.dibk.no/robots.txt is 99 bytes — a single comment line
pointing at robotstxt.org documentation, with zero rules. There is no Disallow here.
What DiBK asks, verbatim from its own API documentation (our translation follows):
«APIet er åpent og gratis tilgjengelig for alle som ønsker å bruke det. Systemeiere trenger ikke å registrere sin bruk hos Direktoratet for byggkvalitet. Vi oppfordrer dog brukere til å ikke lagre data fra registeret permanent, men heller bruke APIet til direkte eller regelmessige oppslag.»
"The API is open and freely available to anyone who wishes to use it. System owners do not need to register their use with DiBK. We do, however, encourage users not to store data from the register permanently, but rather to use the API for direct or regular lookups."
We quote that in full because this Actor writes register data into a dataset you keep, and Monitor
mode deliberately builds a dated archive. It is an encouragement ("oppfordrer"), not a licence
condition, and it does not override CC BY 4.0 — but you should know it is there and decide for
yourself. If direct lookups suit you better, run the Actor with organizationalNumbers against a
short list: it costs one request either way and returns a live answer.
This Actor sends one request per run for the register itself, negotiates compression so it moves 0.6 MB instead of 20 MB, and throttles optional detail fetches to DiBK's own published rate.
❓ FAQ
What exactly is "sentral godkjenning"? A national approval scheme run by DiBK. A firm holding it has documented the education, practice and quality-system requirements for named functions and subject areas at a given tiltaksklasse. It is voluntary but widely used as a qualification filter by developers and public buyers.
What is a "tiltaksklasse"?
The complexity tier of the work a firm is approved for: 1 (simple) to 3 (the most demanding). A firm
can hold different classes in different subject areas; max_tiltaksklasse is its highest. Measured
distribution: 5,258 firms top out at 1, 3,157 at 2, and 1,993 at 3.
Do I get phone numbers or e-mail addresses?
No — and neither does anyone else. Both fields are null on all 10,408 rows in DiBK's data. You get
the organisation number on every row, which is the key to Brreg and to any Norwegian enrichment
provider.
How do I get company size?
Turn on "Fetch employee count + approval start date". It reads each firm's own register page,
where Antall lønnsmottakere lives. It costs one extra request per firm at 1 request/second, so
filter first and enrich the slice.
Why is a full enrichment run so slow? Because DiBK publishes a 300-requests-per-5-minutes-per-IP limit and we respect it by default. All 10,408 firms would take about 2.9 hours. Almost nobody needs that; a filtered slice of a few hundred takes minutes.
Can I check whether one specific contractor is approved?
Yes — put its organisation numbers in organizationalNumbers. Numbers that are not in the register
come back as rows with approved: false and in_register: false, which is the answer. No extra
requests are made: the register is downloaded either way.
How do I find firms whose approval is about to expire?
Set expiringWithinDays (90 gives ~509 firms; 365 gives ~2,719), or use
approvalExpiresBefore / approvalExpiresAfter for an exact window, and sort by
approval_expiry.
Does the monitor really remember between runs?
Yes. State lives in a named key-value store (sentral-godkjenning-monitor-state), not the default
one — an unnamed store is wiped after every run, which would report every firm as new every time.
All five change types are covered by an end-to-end test before every release.
Why does the first monitor run return nothing? It records the baseline. There is nothing to compare against yet, so it returns no rows — and it is not charged as a monitor run. The next run reports real changes.
Can I run several separate watchlists?
Yes. Give each one a different monitorProfile; each keeps its own snapshot and its own run history.
Can I export to CSV / Excel / Google Sheets?
Yes — Apify exports every dataset to CSV, XLSX, JSON, XML and HTML, or via API. If you want a narrow
CSV, set includeApprovalAreas: false: the flat approval_areas_text, functions,
subject_areas, approval_area_count and max_tiltaksklasse columns stay.
Do I need a proxy?
No. The register is a public, keyless GET with no anti-bot. The default is Apify Proxy on auto
because it is polite and cheap here (~0.6 MB per run); you can turn it off entirely.
Does it need residential proxies? No. Datacenter proxies returned HTTP 200 on 5 of 5 register fetches and 20 of 20 detail pages. Residential would only add cost.
What happens if DiBK changes the API? The Actor asserts on the response envelope and on the keys of every record. On a shape change it fails loudly with an explanation and charges nothing, rather than quietly delivering a mis-mapped dataset. DiBK's published spec already disagrees with its live deployment in two places, so this is not hypothetical.
Is scraping this legal?
The data is an open public register published under CC BY 4.0, which explicitly permits commercial
reuse with attribution — and this Actor puts that attribution on every row. robots.txt carries no
rules. See the section above for DiBK's own note about permanent storage, quoted in full, and make
your own call on it.
🔗 Related Actors
Looking for other national registers and contractor directories? Browse the rest of the ScrapersDelight store profile.