WKO Firmen A-Z Scraper - Austrian Company Leads
Pricing
$2.50 / 1,000 per company row returneds
WKO Firmen A-Z Scraper - Austrian Company Leads
From $2.50 per 1,000 companies, no start fee. Austria's WKO chamber directory firmen.wko.at: name, address, email, phone, mobile, website, plus GISA trade licences and Firmenbuch numbers per company. Measured fill: email on 32.4% of rows, address on 100%. Paste any trade x region list URL.
Pricing
$2.50 / 1,000 per company row returneds
Rating
0.0
(0)
Developer
Scrapers Delight
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
WKO Firmen A-Z Scraper — Austrian company leads
Reads company result lists from firmen.wko.at, the Wirtschaftskammer Österreich's public Firmen A-Z directory, and returns one row per company location with whatever contact details that company published, plus — optionally — the GISA trade licences off its own company page.
$2.50 per 1,000 companies. No run-start fee. Rows your filters remove are never delivered and never charged. A company that turns up in two of your lists is charged once.
Read this before you buy: 64.9% of these companies publish no contact details
This is a chamber-of-commerce register, not a lead database. WKO shows an email or a phone number only for members who opted in, and most did not. Counted on 185 companies across 7 trades on 2026-09-16 — by parsing the bytes and counting, not by estimating:
| field | filled | % |
|---|---|---|
companyName, companyId, locationId | 185 / 185 | 100% |
streetAddress, postalCode, city, fullAddress | 185 / 185 | 100% |
detailUrl, companyUrl | 185 / 185 | 100% |
email | 60 / 185 | 32.4% |
phone (landline) | 45 / 185 | 24.3% |
mobilePhone | 38 / 185 | 20.5% |
| a phone of either kind | 63 / 185 | 34.1% |
website | 53 / 185 | 28.6% |
businessDescription (the trading name under the company name) | 55 / 185 | 29.7% |
| an email OR a phone | 65 / 185 | 35.1% |
| neither an email nor a phone | 120 / 185 | 64.9% |
statusBadges (any badge at all) | 8 / 185 | 4.3% |
isDormant (aktuell ruhend gemeldet) | 3 / 185 | 1.6% |
certificateCount (N Zertifikat) | 4 / 185 | 2.2% |
Empty means genuinely absent: when a company publishes nothing, the whole <div class="contact-info">
block is missing from the page. Not one row in 185 carried a blank, a dash or an "N/A" pretending
to be a value.
It varies enormously by trade, and that is the lever you have. Same day, same parser:
| list | companies read | with an email |
|---|---|---|
werbeagentur / tirol | 30 | 25 (83.3%) |
tischler / wien | 30 | 11 (36.7%) |
freizeitbetrieb / burgenland | 30 | 8 (26.7%) |
elektrotechnik / wien | 30 | 7 (23.3%) |
fliesenleger / steiermark | 30 | 7 (23.3%) |
gerüstverleiher / linz-stadt_bezirk | 5 | 1 (20.0%) |
gasthaus / wien-23-bezirk-liesing | 30 | 1 (3.3%) |
Professional services publish; hospitality does not. Pick your trade accordingly, and set "Only companies with… an email" so you are never charged for a row you cannot act on.
Where an email does exist it is a good one: of the 60 addresses, 49 distinct domains and only 9 free-mail (gmail/gmx/aon and friends) — 85% are on the company's own domain.
The company page does NOT rescue the missing emails. Tested here directly: for 23 companies whose list row and company page were both read, the company page supplied an email for 0 of them and agreed with the list on email presence 23 out of 23. There is no second surface. (A separate earlier probe reached the same conclusion on 44 more companies.)
What the company page DOES add
Turn on "Also open each company page" and every row you are charged for also carries its regulatory identity. Counted on 80 company pages:
| field | filled | % |
|---|---|---|
gln — GLN of the public administration | 80 / 80 | 100% |
gisaNumbers — GISA trade-register numbers | 80 / 80 | 100% |
tradeBranches — Berufszweig | 80 / 80 | 100% |
tradeLicences — the full licence records | 80 / 80 | 100% |
commercialRegisterNumber + commercialRegisterCourt — Firmenbuch | 41 / 80 | 51.2% |
foundedYear | 23 / 80 | 28.7% |
vatId — UID-Nummer (ATU…) | 17 / 80 | 21.3% |
legalForm — Rechtsform | 10 / 80 | 12.5% |
Sole traders have no Firmenbuch entry at all, which is most of the gap — those fields come back
null, never as an invented blank.
Each licence in tradeLicences (109 licences over those 80 companies, 1.36 per company, up to 7):
| sub-field | filled | % |
|---|---|---|
tradeGroup — the Fachgruppe, e.g. "LI Tischler und Holzgestalter" | 109 / 109 | 100% |
wording — the full Gewerbewortlaut, the licence text itself | 109 / 109 | 100% |
branch, gisaNumber, authority | 106 / 109 | 97.2% |
since — licence start date, as an ISO date | 107 / 109 | 98.2% |
managingDirector — gewerberechtliche Geschäftsführung | 42 / 109 | 38.5% |
Enrichment costs about 42 KB and one extra request per company, and it runs only on rows that will actually be delivered. The price per company does not change.
What was cut, and why
Fields WKO publishes that this Actor deliberately does not emit:
BICandIBAN. WKO prints a company's bank details as part of its statutory media disclosure. Present on 2 of 23 pages in the first sample. They have no lead-gen use and shipping a column of Austrian bank accounts is not something to do by accident, so they are dropped.Geschäftsbezeichnungfrom the company page. It is the same string as the list page'sbusinessDescription, which is filled more often (29.7% vs 17.4%), so carrying both would be two columns of one fact.- Fax. There is no fax number anywhere on this surface. Across 185 company rows the markup contains exactly five kinds of contact link — landline, mobile, email, website, and the Google Maps route link — and no fax. The field is not offered rather than offered and always empty.
What one row looks like
{"companyId": "9f6b0c29-41cb-4b41-8b05-44231ffd0806","locationId": 9,"companyName": "Podersdorf Tourismus- und Freizeit- betriebsgesellschaft m.b.H.","businessDescription": null,"email": "info@podersdorfamsee.at","phone": "02177 2227","mobilePhone": null,"website": "https://www.podersdorfamsee.at/","streetAddress": "Campingplatz","postalCode": "7141","city": "Podersdorf am See","fullAddress": "Campingplatz, 7141 Podersdorf am See","statusBadges": [],"isDormant": false,"certificateCount": null,"companyUrl": "https://firmen.wko.at/firma/?firmaid=9f6b0c29-41cb-4b41-8b05-44231ffd0806","detailUrl": "https://firmen.wko.at/podersdorf-tourismus-…/burgenland/?firmaid=…&standortid=9","sourceUrl": "https://firmen.wko.at/freizeitbetrieb/burgenland/?firmenname=","scrapedAt": "2026-09-17T01:29:40.033Z","detailFetched": true,"gln": "9110015841909","commercialRegisterNumber": "146696f","commercialRegisterCourt": "Landesgericht Eisenstadt","legalForm": null,"vatId": null,"foundedYear": null,"licenceCount": 1,"gisaNumbers": ["10345894"],"tradeBranches": ["Gewerblicher Sportbetrieb - Bahnengolf"],"tradeLicences": [{"tradeGroup": "FG Freizeit- und Sportbetriebe","wording": "Kleingolfanlage","branch": "Gewerblicher Sportbetrieb - Bahnengolf","gisaNumber": "10345894","managingDirector": "Mag. (FH) Rene Lentsch","authority": "Bezirkshauptmannschaft Neusiedl am See","since": "1997-01-20"}]}
locationId matters: a company with several sites appears once per site, and the pair
companyId + locationId is the key this Actor de-duplicates on.
How to point it at what you want
WKO lists are always trade × region. Open a trade on firmen.wko.at, pick a region from the site's own list, and copy the address bar:
https://firmen.wko.at/tischler/wien/?firmenname=https://firmen.wko.at/werbeagentur/tirol/?firmenname=https://firmen.wko.at/gasthaus/wien-23-bezirk-liesing/?firmenname=
Paste as many as you like into Search result URLs. Or fill in Trade once and list several Regions — umlauts can be typed plainly, they are percent-encoded for you.
Do not invent combinations. An unreal trade × region does not return an empty list, it returns
HTTP 404 (/baumeister/oberoesterreich/ → 404, verified). The Actor reports that as
"that combination does not exist" and charges nothing for it — it never reports it as an empty
trade.
Every list page prints its exact size in the browser tab — 361 Unternehmen gefunden — so you can
size a run before you pay for it. Above a thousand WKO stops counting and says Über 1000; the
Actor records that as null, not as 1000.
Cost: shallow lists are far cheaper than deep ones
This is the one thing worth understanding before a big run. WKO has no page parameter — ?seite=2
answers HTTP 200 with page 1's rows, verbatim — so the only pagination is its "Mehr laden" button,
and every click re-renders the entire cumulative list, not the ten new rows. Measured end to end:
| companies from one list | total bytes | per company |
|---|---|---|
| 30 | 572 KB | 19 KB |
| ~200 | ~11 MB | ~55 KB |
| ~500 | ~49 MB | ~97 KB |
Ten regions of 50 cost about a third of what one region of 500 costs for the same 500 rows, and
finish sooner because they run in parallel. That is why Max companies per list defaults to 200.
RUN_SUMMARY.transport reports the bytes and the KB-per-delivered-row your run actually spent.
Rate limits, and what the Actor does about them
firmen.wko.at sits behind a rate-limiting WAF that answers Access Denied / Signature ID: 0 —
sometimes under an HTTP 200, which is why the Actor checks for that page rather than trusting the
status code. Measured ladder: 30 requests at 1.5 s spacing gave 27 clean 200s; the same 30 at full
speed gave 18 blocks, the first at request 11; sustained parallel load escalated to a 393-second
block of the whole egress IP.
So: each list walks on its own proxy session at 1.5 s between clicks, several lists run in parallel, and a block is retried on the same session after 4 s, 12 s and 40 s — a WKO block does not consume the ViewState, so waiting resumes the walk, whereas rotating the IP silently ends it.
Company pages are guarded by a JS interstitial (HTTP 429, ~1.1 KB). It is not a CAPTCHA and needs no
browser: one plain JSON POST of the company's own id clears it. Measured on 24 company pages over
Apify's own egress — 23 returned HTTP 200, 9 of them after that handshake, and the single miss was a
proxy-side UPSTREAM504, not a block.
Apify IPs are not pre-blocked. Verified on 2026-09-16 from Apify egress: RESIDENTIAL pinned to
Austria, plain RESIDENTIAL and datacenter (auto) each returned HTTP 200 with a complete result
panel, and a full paging walk plus the interstitial handshake both completed over RESIDENTIAL+AT.
The default is RESIDENTIAL+AT because this site blocks by IP and datacenter addresses are shared
with every other Apify user; datacenter is cheaper and you can switch to it.
Honest limits
- 64.9% of rows have no email and no phone, and the company page does not fix that. If you need a contactable list, use the contact filter and expect roughly a third of the register — heavily weighted toward professional-services trades.
vatId(21.3%),legalForm(12.5%),isDormant(1.6%) andcertificateCount(2.2%) are sparse. They are real and they are counted above; they are not a reason to buy.managingDirectoris a person's name, published by WKO as statutory trade-law disclosure. It is on 38.5% of licences. Treat it as personal data.- A list is a one-way cursor. Paging state lives in the ASP.NET ViewState and
controlsCountFieldis inert (0, 5, 20 and 99 all return the same 20 rows), so there is no page jumping, no starting in the middle, and no resuming a list mid-way after a run ends. - Deep lists are expensive by construction, not by implementation — see the cost table.
Über 1000lists cannot be size-checked. For lists with an exact published count the Actor refuses to report a short walk as complete; on anÜber 1000list there is no number to check against, so that guard cannot run.- No proven ceiling, and no proof there is none. The deepest single list reached in testing was 40 rows by this Actor and 750 by an earlier probe, neither of which met a cap. Whether something stops a list beyond that is untested.
- A truncated paging response cannot be caught by Content-Length, because the deltas arrive chunked with none. The Actor instead parses the length-prefixed delta to a clean boundary and fails the list if the body ends mid-segment.
- Counts here are samples, not the whole register. 185 list rows across 7 trades and 80 company pages. A trade not in that sample may behave differently — check the run summary against your own lists.
Run summary
RUN_SUMMARY in the key-value store reconciles the run:
- every list, with the count WKO itself published beside the number actually parsed and the number delivered, plus its requests, bytes and why it stopped;
- the three kinds of nothing kept apart — a combination that does not exist (404), a list that exists and holds nobody, and a list that could not be finished — and none of them charged;
- rows not delivered, split by reason: contact filter, dormant filter, duplicate across lists, unnamed;
- contact coverage on what was delivered;
- enrichment attempted / succeeded / failed, with the reasons;
deliveredandcharged, which are the same number.
If a list finishes but returns fewer companies than WKO's own title reported, the run fails and says so rather than reporting a partial list as a complete one.
Source and access
https://firmen.wko.at/robots.txt (HTTP 200, 1,279 bytes) begins:
User-agent: *Allow: /Disallow: /*earchComplex.aspxDisallow: /*earchcomplex.aspxDisallow: /Wartung/*.aspxDisallow: /wartung/*.aspxAllow: /wartung/*efault.aspxAllow: /Wartung/*efault.aspx
Neither the result lists nor the company pages are disallowed, and both carry meta robots=index.
Named harvester agents (EmailCollector, EmailSiphon, Teleport, WebZIP, WebBandit) and
Yandex are disallowed by name; this Actor is none of them. Data is published by the Wirtschaftskammer
Österreich; company and personal fields in it are subject to Austrian and EU law, and how you use
them is your responsibility.
Verifying this build
node offline_validate.mjs
250 assertions, no network and no node_modules, run against the raw bytes in fixtures/ — captured
from firmen.wko.at on 2026-09-16 and written in binary, because the ASP.NET delta is a
length-prefixed protocol and a text-mode save shifts every declared length. The field-fill numbers in
this README are re-counted by the validator on the 75 fixture rows, the paging state machine is
driven over the real captured deltas, and the end-of-list, truncation, block, 404 and cap branches
are each exercised.