Sentral Godkjenning Scraper - Norway Approved Contractors avatar

Sentral Godkjenning Scraper - Norway Approved Contractors

Pricing

from $3.00 / 1,000 per firm returneds

Go to Apify Store
Sentral Godkjenning Scraper - Norway Approved Contractors

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

Scrapers Delight

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

4 days ago

Last modified

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 null on 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 enterprisesThe complete register, not a sample
🔢 35,726 approval-area rowsAverage 3.4 per firm, maximum 30
🏅 Tiltaksklasse 1 / 2 / 35,258 · 3,157 · 1,993 — the big-project shortlist
📅 Approval expiry on every row509 expire within 90 days, 2,719 within a year
🧰 4 functions × 36 subject areasUtførende 9,070 firms · Prosjekterende 4,705 · Ansvarlig søker 2,898 · Uavhengig kontrollerende 690
📍 1,209 postal townsOslo 1,070 · Trondheim 205 · Stavanger 159 · Sandnes 152 · Kristiansand S 151 · Bergen 125
🛡️ Insurance + apprentice flagsLiability 43.1% · industrial-injury 41.8% · approved training enterprise 43.7%
🌐 Website1,854 firms (17.8%)
👷 Employee count (optional)Only on the detail page — never in the API
🔔 Approval-change monitorNew / 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

FieldFillNotes
organizational_number100% (10,408)9 digits, joins to Brreg. No duplicates in the register.
name100%
approved100% trueThe register lists currently-valid approvals only.
approval_period_to100%ISO date. Range 2026-09-08 → 2029-09-07.
days_until_expiry · renewal_window100% (derived)expired / 0-30_days / 31-90_days / 91-180_days / 181-365_days / over_365_days
approval_certificate_url100%DiBK's official PDF certificate.
business_country100%NO 10,387 · SE 4 · DK 4 · FI 4 · LT 4 · PL 2 · LV 2 · HU 1
business_postal_code · business_postal_town99.8% (10,388)
business_address_line_198.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_tiltaksklasse100% (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_approved43.7% trueApproved to take apprentices (lærebedrift).
liability_insurance43.1% true
industrial_injury_insurance41.8% true
www17.8% (1,854)Say it plainly: fewer than one in five.
postal_address_line_114.8% (1,542)Separate postal address when it differs.
likely_sole_proprietorship · company_form_suffix7.8% flagged (816)Heuristic — see honest limits.
phone0% (0 of 10,408)The register carries none. The column exists and is null.
email0% (0 of 10,408)Same.
employee_count_raw / _min / _maxdetail only20/20 on the sampled pages. Sometimes a band.
approval_period_fromdetail only20/20 on the sampled pages.
data_source · data_licence100%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.

FilterWhat it does
Company-name searchCase- and diacritic-insensitive substring; "tomrer" finds "TØMRER"
Organisation numbersCompliance lookup; not-found numbers come back as approved: false rows
FunctionsUtførende / Prosjekterende / Ansvarlig søker / Uavhengig kontrollerende
Subject areasAll 36, each labelled with its real firm count. Or pass DiBK's 46 short codes.
Minimum tiltaksklasse1–3; 3 gives the 1,993-firm key-account list
Minimum approval areasBreadth-of-capability proxy
Expiring within N days / before / afterThe dated renewal trigger
Postal towns · postal-code prefixes · country"tromso" matches all 64 TROMSØ firms
Insurance / apprentice-approval flags
Only firms with a websiteCuts to the 1,854 that publish one
Exclude probable sole proprietorshipsDrops 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" }
  1. 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.
  2. Every run after that re-reads the register and delivers one row per change:
change_typeMeaning
new_approvalThe firm was not in the register at the previous snapshot
renewedThe approval end date moved later
approval_shortenedThe approval end date moved earlier
areas_changedApproval areas, their codes or the tiltaksklasse changed
lapsedThe 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 lapsed firm 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 maxFirms cap or your maxTotalChargeUsd did 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 own changeTypes/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 changes with exactly one monitor-run-completed charge. Proven locally with mutated state: three snapshot entries changed and two phantom firms injected, and the next run classified all five cases correctly — 1 new_approval, 1 renewed, 1 approval_shortened, 1 areas_changed, 2 lapsed — and wrote them to the history record.


💵 Pricing

Pay per event. No monthly rental, no platform-usage surcharge, no charge for starting a run.

EventPriceFires 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

JobRowsCost
Press Start (the 50-row prefill)50$0.15
Oslo, tiltaksklasse 3, by expiry387$1.16
Carpentry expiring within 120 days252$0.76
Everything with a website + tiltaksklasse ≥ 21,318$3.95
The entire register10,408$31.22
The entire register + employee count on every firm10,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

  1. 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_number if you need contact data.
  2. Website on 17.8% only. 1,854 of 10,408.
  3. 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 postalTowns or postalCodePrefixes.
  4. 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.
  5. likely_sole_proprietorship is 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_suffix shows the raw evidence.
  6. 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 excludeSoleProprietorships if that matters for your use case.
  7. 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. rotateDetailProxySessions will spread the load over 20 sessions if you want it faster — that is your call, and it is labelled for what it is.
  8. Rate-limited or failed detail fetches are never billed, and the row still arrives with detail_complete: false and detail_skipped_reason naming 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.


Looking for other national registers and contractor directories? Browse the rest of the ScrapersDelight store profile.