VDAB Job Scraper - Belgium Flanders Vacancies & Employers avatar

VDAB Job Scraper - Belgium Flanders Vacancies & Employers

Pricing

from $0.50 / 1,000 per job returneds

Go to Apify Store
VDAB Job Scraper - Belgium Flanders Vacancies & Employers

VDAB Job Scraper - Belgium Flanders Vacancies & Employers

From $0.50 per 1,000 vacancies. Scrape VDAB, Flanders' public employment service: all 207,809 live Belgian vacancies by keyword, city, radius, region, job domain, contract, diploma, licence. Optional detail adds employer website, named hiring contact, apply/ATS URL. New-vacancy monitor + alerts.

Pricing

from $0.50 / 1,000 per job returneds

Rating

0.0

(0)

Developer

Scrapers Delight

Scrapers Delight

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

21 hours ago

Last modified

Share

🇧🇪 VDAB Job Scraper — Belgium / Flanders vacancies, employers & hiring contacts

Scrape VDABVlaamse Dienst voor Arbeidsbemiddeling en Beroepsopleiding, Flanders' public employment service and by far the biggest job board in Belgium — into clean CSV, JSON or Excel.

207,809 live vacancies — measured on 2026-09-06, and every other number on this page was re-measured the same day — filtered by keyword, city + radius, geo point, VDAB region, job domain, occupation, contract type, working hours, shift pattern, diploma, experience, driving licence and freshness. Turn on detail enrichment and every row also carries the employer's website, the named hiring contact, the direct apply/ATS URL and the street address — the fields that turn a job listing into a sales lead.

From $0.50 per 1,000 vacancies. No login, no API key, no browser. VDAB's own public JSON endpoints, over plain HTTP.


🎯 What does VDAB Job Scraper do?

🔎 Searches like the site doesEvery filter on vdab.be's own search page is a real server-side filter here — verified one by one against live result counts.
🧾 One row per vacancyDe-duplicated on VDAB's own vacancy id before anything is billed.
🏢 Employer intelligenceEmployer legal name, website + host, city, description, and whether the poster is the direct employer or a staffing agency.
👤 Named hiring contactcontactPersonName was present on 100% of the 135 enriched vacancies in our 2026-09-06 sample.
🔗 Apply URL + ATS vendorThe apply link leaks the employer's recruiting-tech vendor (go4jobs.be, lagom.jobs, …) in applyAtsHost — a technographic signal on 76% of rows pooled over 135 enriched vacancies (37–100% depending on the employer mix your query selects).
🗺️ Full geographyWork address, work city, and VDAB's own region code + label (e.g. BE231A / Regio Aalst).
📄 Full textDescription, requirements and offer as plain text, and optionally HTML and Markdown.
🧩 Beats the 20,000-row API ceilingVDAB's search cannot page past ~20,000 results. Adaptive facet slicing splits an oversized query along VDAB's own live facet counts until every slice fits — see below.
🔔 New-vacancy monitorRemembers every vacancy id across scheduled runs and emits only what is new, with Slack / email / webhook alerts.
🛠️ Shortage-occupation flagshortageOccupation marks Flanders' official knelpuntberoepen — the roles employers cannot fill, and therefore the ones that convert.

🧠 The one thing that makes this Actor different: the 20,000-row ceiling

Measured live, 2026-09-06, page size 100:

RequestRowsFirst idLast id
pagina 0 (offset 0)1007344553174071619
pagina 150 (offset 15,000)1007377713474469830
pagina 198 (offset 19,800)1007424588674065079
pagina 199 (offset 19,900)1007395909974500276
pagina 200 (offset 20,000)1007395909974500276
pagina 205 (offset 20,500)1007395909974500276
pagina 260 (offset 26,000)1007395909974500276

Past offset ~19,900 VDAB replays one identical page forever. A naive scraper keeps paging, keeps "finding" rows, and — if it does not de-duplicate — keeps charging you for them. It will also quietly tell you a 207,809-vacancy corpus is 20,000 vacancies.

This Actor handles it three ways:

  1. It stops when the page repeats. The id-set of each page is compared with the previous page's; an exact repeat is the ceiling, and the slice ends there. There is also a hard offset guard.
  2. It slices. Every search response carries VDAB's live per-facet counts. Any query bigger than the window is split recursively along those counts — preferring the axes that are true partitions (experience → contract type → working hours, where every vacancy has exactly one value) before falling back to job domain, education, shift or licence.
  3. It de-duplicates before billing. Slices overlap by design; you are never charged twice.

Measured plan for the whole public corpus, re-run 2026-09-06:

root 207,807 -> 164 slices, largest slice 16,836, slices over the window: 0
15 probe calls to plan

A worked branch from that run:

experience=Beperkte ervaring (60,262)
> contract=Tijdelijke jobs met optie vast (42,348)
> working hours=Voltijds (40,932)
> job domain: 24 branches, largest 16,836 <- fits

📊 Measured field fill rates

Pooled over 135 enriched vacancies across four different queries, all run on 2026-09-06 (nationwide 40 · keyword verpleegkundige 40 · one employer group 25 · Antwerpen +10 km 30 — 135/135 detail calls succeeded):

FieldFillExample
jobId, url, title, employerName100%74494729, Beenhouwer vast contract
employerKind (agency / direct_employer)100%agency
locationLabel, contractLine, experienceLabel100%BEVEREN-KRUIBEKE-ZWIJNDRECHT, Vaste job
shortageOccupation, closed, openings100%true, false, 1
firstPublishedAt, lastModifiedAt100%2026-08-31T11:50:55Z
employerGalleryId, employerLogoUrl56% (17–100%)2662
detail → employerId, employerLegalName, employerCity100%10708373
detail → contactPersonName100%Karen Buys
detail → workRegionCode / workRegionLabel / workCity100%BE256A / Regio Roeselare - Izegem
detail → description100%full plain-text job description
detail → contractType, workingHours, jobDomains, requiredStudies100%onbepaalde duur, voltijds
detail → occupationCodes, occupationProfile, dataSources100%I110201-1
detail → requirements, offer99% (97–100%)
detail → applicationAddress99% (95–100%)
detail → workAddress94% (85–100%)
detail → employerWebsite + employerWebsiteHost88% (67–100%)https://www.go4jobs.be
detail → languages85% (56–93%)Nederlands · Goed (B1)
detail → employerDescription79% (57–100%)
detail → applyUrl + applyAtsHost76% (37–100%)lagom.jobs
detail → externalReference (employer's own ATS req id)63% (7–100%)publications-51775-B
detail → drivingLicences18% (0–42%)CE zware vrachtwagen
duplicates[] (cross-posted twins)7.1% of rows (0–10.6%)pooled over 1,059 rows across nine runs on 2026-09-06
lastPublishedAt1% (0–4%)VDAB almost never fills laatstePublicatieDatum: 1 row of the 135 above, 2 of 50 on the prefill run, 2 of 120 on a newest-first run. Treat this column as effectively empty and use firstPublishedAt / lastModifiedAt.
isDuplicateOf0% unless includeDuplicates is onOnly a cross-posted twin row carries it — it points at the parent vacancy id.

Read the ranges, not just the averages. The optional fields are supplied by the employer, so their fill rate depends heavily on which employers your query happens to selectapplyUrl was 100% on a single agency's postings and 37% on a general Antwerpen sweep. drivingLicences is only populated on roles that actually require one, so it is near-zero on office work and high on transport queries. The fields the lead-gen use case rests on — jobId, title, employerName, locationLabel, contactPersonName, description, workAddress — held at 94–100% on every one of the four samples.

⚠️ There is no salary field

VDAB's API returns no compensation data at all — not a range, not a band, not a "competitive". No scraper can give you Belgian salary from this source. Anything advertising VDAB salary data is inferring it. We would rather tell you than let you find out after you have paid.


🚦 Reliability — measured, not asserted

All of this was re-measured on 2026-09-06, on the platform, through the Apify datacenter proxy:

TestResult
Search calls across 13 platform runs195/195 = 100% (including one run that paged 175 times)
Detail fetches across the same runs745/746 — the one miss is a deliberate 404 probe of a vacancy id that does not exist
Longest single run's detail batch250/250 ok (run FKBo6r4OTvFQKaxLR)
Concurrency-8 detail burst40/40 ok in 13.5 s (run 6VbeINbbClKzXvqTU)
107 direct filter probes — every enum option plus every worked example107/107 = 100%
Prefill health input, cold container to finish4.3 s, 50 rows (run husnhlRd0g7JeoAdv)
TransportApify datacenter proxy, HTTP 200 on every call above

Datacenter proxy is the default because it measures identical to residential and costs less. No browser is used anywhere in this Actor.

maxRequestRetries defaults to 3. Nothing needed a retry on 2026-09-06, but detail fetches were measured at 19/20 first-try on an earlier day, so the retry budget stays.


💸 Pricing

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

EventPriceWhen it fires
Per job returned$0.0005 ($0.50 / 1,000)Each vacancy delivered to your dataset — after de-duplication
Per detail fetch$0.0015Only when includeDetail is on — including single-vacancy URLs
Per scheduled run$0.01Only in monitor mode — and only once VDAB has actually answered a call
Per new vacancy$0.002Only in monitor mode
Per alert delivered$0.002Slack / email / webhook

Worked examples

RunCost
1,000 listing rows$0.50
1,000 rows with full detail$2.00
10,000 sales-lead rows with detail$20.00
The entire 207,809-vacancy corpus, listings only~$104
Daily monitor on one saved search, ~30 new/day~$0.08/day

De-duplication is on by default and it matters. It collapses cross-posted twins, overlapping keyword crawls, overlapping slices and VDAB's own page-replay at the offset ceiling into one billed row per vacancy id. Measured 2026-09-06: feeding the same keyword three times produced 369 raw rows that collapsed to 123 unique vacancies — 246 duplicate charges avoided (run bImyk2xPzvAT526z0, job-scraped: 123). With deduplicateById: false the identical input billed all 369 (run NtFjbwlFIZohebcSd, job-scraped: 369).

A single-vacancy URL costs a detail event only if you asked for detail. VDAB's $.vacatures/{id} endpoint is the only source for a vacancy id, so the Actor reads it either way — but with includeDetail off the detail columns are dropped again before the row is written and no job-detail-enriched event is billed. Measured 2026-09-06 on build 0.1.15: 10 vacancy URLs with includeDetail: falsejob-scraped 10, job-detail-enriched 0 = $0.0050 (run BlNkHJhfYYNcEiteo, 10 rows, one 24-field key set). The same 10 URLs with includeDetail: truejob-scraped 10, job-detail-enriched 10 = $0.0200 (run Bu6KGcdGi4cAx4wyK, 10 rows, one 64-field key set). Build 0.1.14 billed the detail event on both — 4x the listing-only price on an option you had left off; 0.1.15 does not.

Your maxTotalChargeUsd is a hard ceiling, including the monitor events. Before the first row ships, the run reserves the flat $0.01 monitor-run fee (and the one-off email-digest charge) out of your cap, and prices each row at its full cost — $0.0005 row + $0.0015 detail if enabled, plus in monitor mode $0.002 new-vacancy and $0.002 per alert channel — then delivers only what the cap covers. Measured 2026-09-06 (run rsV7kgspboa4b9ctT, maxItems: 600 with includeDetail under a $0.06 cap): 30 rows delivered, all 30 enriched, job-scraped 30 + job-detail-enriched 30 = $0.0600 exactly — the cap, never a cent over, and the log names the 170 enriched rows that were trimmed and not billed. A monitor run needs a cap of at least ~$0.013; below that the run delivers nothing, bills nothing, and says so in its status message rather than billing you over your own limit. Measured 2026-09-07 (run 4k7OMlyD3fB1aEkQ4, monitor mode under a $0.005 cap): 0 rows, every event 0, status "Nothing was delivered: this run's maximum charge cap does not cover the flat $0.0100 monitor fee plus a single row. Nothing was charged."

The $0.01 monitor fee buys a check — a run that never reached VDAB is not charged for one. The fee fires only after at least one VDAB call has come back with a usable response. A monitor run that hit its own run timeout before its first call, that started past VDAB's ~20,000-row offset ceiling, or whose every call died on transport or a 404 checked nothing, so it bills nothing — which matters most on an Apify Schedule, where a run timeout set too low would otherwise bill $0.01 every run forever while delivering nothing. Measured 2026-09-07 on build 0.1.19, three structurally different zero-check paths, all monitor-run-completed: 0:

Zero-check pathRunCharged
32 s run timeout, 0 search calls7aXmotRLK29ZJwtyi$0.0000
startPage: 500 x pageSize: 50, past the offset ceilingeHsyQOxCKbKOny8iO$0.0000
single vacancy URL, HTTP 404YmRmOJaD7YeEd1WOL$0.0000

Build 0.1.18 charged the fee on all three (run nKpJxItZ8tsguEp6g: 0 search calls, 0 rows, monitor-run-completed: 1 = $0.0100) and told the buyer "Nothing was charged." A monitor run that really did check and found nothing new still bills the fee, and now says the number: run aflu6QJB9jY514fpr, 0 rows, monitor-run-completed: 1, status "No newly posted vacancies since the previous monitor run. Charged for this run: $0.0100 (1 x monitor-run-completed)."


🧰 Input — every field, and what it actually does

Each of these was verified against a live result count. Every number below is the vacancy count VDAB returned for that filter on 2026-09-06 — vacancies, the unit you are billed in, not VDAB's facet "job openings" counts, which run about 1.10x higher. VDAB's index turns over daily, so read them as scale, not as a promise.

What to scrape

  • startUrls — paste any vdab.be search URL and its whole query string is applied (trefwoord, locatie, afstand, jobdomein, arbeidscircuit, diplomaNiveau, ervaring, rijbewijs, internationaal, regio, bedrijfId, sort, pagina — comma-separated and repeated parameters both work). A single-vacancy URL scrapes just that vacancy — with the full detail fields when includeDetail is on (billed as a detail fetch), and listing-level fields only, unbilled, when it is off.
  • searchTerms — free-text keywords, one crawl each, merged and de-duplicated.

Where

  • location + radiusKm — city or postcode (Antwerpen +5 km → 12,719; Antwerpen +10 km → 18,227; Hasselt +5 km → 4,210; 9000 +10 km → 16,247).
  • latitude / longitude + radiusKm — geo radius (51.05 / 3.72 @ 25 km → 33,914).
  • regionCodes — VDAB region codes (BE231A2,952). Several codes run as several crawls.

What kind of job

  • jobDomains — 24 domains. Techniek 75,352 · Bouw 36,053 · Productie 35,571 · Logistiek en transport 29,878 · Verkoop 22,844 · Administratie 16,462 · Management 11,866 · Financieel 8,845 · Gezondheid 6,308 · Dienstverlening 6,214 · Onderhoud 4,628 · Horeca en toerisme 4,572 · ICT 3,604 · Marketing 3,061 · Land- en tuinbouw 2,401 · Creatief 2,229 · Onderwijs 2,182 · Human resources 2,004 · Andere 1,372 · Aankoop 1,020 · Juridisch 888 · Overheid 765 · Onderzoek en ontwikkeling 727 · Communicatie 356.
  • occupationIds — exact-occupation UUIDs (see the table below).
  • contractTypes — Tijdelijk met optie vast 112,178 · Vaste jobs 84,226 · Tijdelijke jobs 6,110 · Zelfstandige 2,060 · Flexijob 2,034 · Studentenjobs 1,343 · Dienstencheque 995 · Alternerende opleiding 161 · Werfreserve 158 · Examens overheid 10 · Tewerkstellingsmaatregelen 8.
  • workingHours — Voltijds 195,928 · Deeltijds 12,055.
  • workSchedule — Dagwerk 186,744 · 2-ploegen 14,978 · Weekend 7,405 · Nacht 3,682 · 3-ploegen 2,547 · Volcontinu 1,330 · Onderbroken dienst 614.
  • educationLevels — Geen 87,524 · Secundair 67,127 · Bachelor 43,697 · Master 15,797 · Graduaat/HBO5 10,547 · Lager onderwijs 8,903.
  • specificDiplomaCode — exact study code (600620 Verpleegkunde → 1,552).
  • experienceLevels — Beperkte 60,262 · Min. 2 jaar 58,969 · Niet van belang 51,313 · Geen 28,758 · Min. 5 jaar 10,350.
  • drivingLicences — B 50,688 · CE 4,678 · C 3,792 · BE 574 · D 269 · Geen rijbewijs 149,740.
  • international — Europa 317 · Grensregio 32 · Buiten Europa 4.

Freshness, employer & sorting

  • postedWithinPast week46,969. Today or yesterday3,070 measured at 05:07 UTC; that one climbs steeply through the Belgian working day, so treat it as a floor rather than a constant. This is the monitoring lever.
  • employerId — take the employerGalleryId off any row and get that employer group's whole hiring footprint (3446892 vacancies, all Lagom Engineering entities). The single most useful lead-gen lever in this Actor.
  • sortBySTANDAARD or DATUM (newest first). VDAB's own site shows a relevance option, but its API answers HTTP 400 for it, so it is deliberately not offered here.

How much, how deep

maxItems · pageSize (1–200; >200 is clamped by VDAB) · startPage (0-based) · maxPages · autoSliceOversizedQueries · includeDetail · includeDescriptionHtml · includeDescriptionMarkdown · includeDuplicates · includeRawJobPosting (schema.org JobPosting) · deduplicateById.

maxItems is a hard cap on rows delivered and billed — a run asking for 300 delivers exactly 300 and bills exactly 300 (measured 2026-09-06, run syWYgKnZKyDUUnHL5: 300 rows, 300 unique ids, job-scraped: 300), or every row the query has if that is fewer.

includeDuplicates is the one flag whose extra rows can be stubs. VDAB's dubbels entry carries only the twin's id, title and employer name. Listing-only (includeDetail off) a twin row therefore arrives with 8 of 24 columns filled and locationLabel, contractLine, firstPublishedAt and the rest null — and it is still billed as a row (measured 2026-09-06, run JcNg7xKYYRmad6sIm: 24 twin rows of 250, 8 of 24 columns filled on every one). With includeDetail on, the twin's own detail record is fetched and back-fills those listing columns, so the twin arrives as a full row: run XQhlubnOkQj4SVyrc (2026-09-06) returned 24 twins at 50–60 of 64 columns filled, mean 57.6, with locationLabel, contractLine, firstPublishedAt, lastModifiedAt, openings, closed, employerKind and workScheduleLabels populated on 24 of 24. Only experienceLabel stays null — VDAB's detail record has no experience field at all. Off by default either way.

Monitor & alerts

monitorMode · newOnlySince · alertOnNewJob · webhookUrl · slackWebhookUrl · emailRecipients.

newOnlySince is the one filter VDAB does not apply for us — this Actor applies it client-side to every row VDAB returns. So a cutoff nothing satisfies makes the run page the whole result set before it can report an empty answer, and it is charged nothing for those rows. Measured 2026-09-06 (run Mc821vFibQKRNahpD): newOnlySince: 2027-01-01 on a 3,394-vacancy keyword took 175 search calls and 4 min 15 s to scan 3,391 rows and deliver 0. The run log and status message now name the count it dropped and the cutoff that dropped it, so this never reads as "VDAB has no such jobs". For a server-side freshness filter use postedWithin instead.

Performance

proxyConfiguration · maxConcurrency (1–8) · requestTimeoutSecs · maxRequestRetries.


📌 Example inputs

Nursing leads around Ghent, with employer contact details

{
"searchTerms": ["verpleegkundige"],
"location": "Gent",
"radiusKm": 25,
"includeDetail": true,
"maxItems": 500
}

Every truck-driver vacancy that needs a CE licence, newest first

{
"drivingLicences": ["CE"],
"sortBy": "DATUM",
"includeDetail": true,
"maxItems": 2000
}

One employer's entire hiring footprint

{ "employerId": 3446, "includeDetail": true, "maxItems": 0 }

The whole corpus (adaptive slicing does the work)

{ "maxItems": 0, "pageSize": 200, "autoSliceOversizedQueries": true }

Daily new-vacancy monitor for ICT roles, to Slack

{
"jobDomains": ["JOBCAT10"],
"postedWithin": "1",
"sortBy": "DATUM",
"monitorMode": true,
"alertOnNewJob": true,
"slackWebhookUrl": "https://hooks.slack.com/services/…",
"maxItems": 0
}

🔑 Shortage-occupation IDs (occupationIds)

VDAB's 30 tracked occupations and their live vacancy counts, measured 2026-09-06:

OccupationIDVacancies
Vrachtwagenbestuurder142a1c8a-ded8-4b8e-b445-5da25dec333511,644
Industrieel elektromecanicien953ddb4a-da61-4d28-b46f-c1efb64a5fc08,164
Magazijnmedewerkerbb86bb0c-8062-4cd5-a257-186606056bbe7,880
Winkelverkoperc6eda604-d594-4d41-ab67-3e2ef003641d6,492
Industrieel onderhoudstechnicus410a4483-7305-496a-93f2-613e6e5930fd4,703
Winkelmedewerkerfe24420c-0cf8-4bbc-9fa6-9b1120e6aabf4,196
Insteller-omsteller verspaningfa01c16b-2027-4df2-8c51-feaa80db11293,663
Accountant383e50a3-9d06-4821-b880-c6da89b6e8093,866
Manueel lasser4a95fd9c-8dd5-4e5f-a7cf-4a836cd4691d3,425
Productiemedewerkerff2b9b81-6c31-4528-8b5d-4995788c8cf53,047
Polyvalent administratief medewerkerd70607ef-e4cf-43a9-a340-a1b9e9d8e3f93,206
Productieoperator voedingsindustrieaf332f1f-8134-4799-92e7-00d6f2119e2d2,865
Werfleiderbc1d48d2-763e-477b-b1e8-d0038fdd0a143,116
Elektrotechnicuscfe84de1-a7d1-4b34-924d-764c2cb3c6492,802
Vertegenwoordiger B2B54a6cf77-d3ff-4691-b03d-f9e30b4790ef2,631
Teamleader productie9ef857e6-fc27-41f8-a6dd-2970daa314112,524
Onderhoudsmecanicien personenwagensd0f506b4-0894-41bf-b1a4-e494c892bca32,526
Verpleegkundigecee9ee58-43a9-4300-b6c4-8bf1f7b59c282,498
Monteur installatietechniekenff8e0023-a150-4c4e-8c48-9b635d2cbfa32,565
Wegenwerkera8fc4ca1-3512-4e9c-ad95-13ba3a09c0f12,433
Medewerker ruwbouw1eddda40-3023-4324-834e-c7b0df9e35312,241
Technicus werf-, landbouw- en hefmachinesda50b460-bfac-4eeb-b328-fc7c66ed60882,207
Verantwoordelijke kwaliteitscontrole3d87a629-d78d-4fee-8597-3b5daf4754472,250
Operator montage en assemblage5cb21c1f-a279-4654-ad0c-4419dfc550061,999
Commercieel medewerker binnendienst9cbe7cd0-9ff7-4723-ac13-7610e52bee6a2,007
Projectleider bouw en openbare werkenb11bf6de-c048-4539-90ee-d969c2956c1b1,943
Kraanbestuurder30093422-8571-4ff4-97f9-5a1bab6a2ee91,800
Dakwerkeradf8d084-3819-45d5-a429-0471b3fa44321,866
Plaatser buitenschrijnwerk3911e32d-b57f-49db-a9de-7a0c2e748d911,703
Heftruckchauffeur823d8e67-9c45-4e84-a7aa-f9675585c7fd1,553

🙋 FAQ

Does this need a VDAB login or API key? No. It reads the same public JSON endpoints the vdab.be job-search page itself calls from your browser. No account, no key, no cookies.

Does it use a browser? No. Plain HTTP over Apify's cheapest datacenter proxy. That is why it is fast and cheap.

Can I really get all 207,809 vacancies? Yes, with maxItems: 0 and adaptive slicing on — that is exactly what slicing exists for. What was actually measured 2026-09-06 (run 0pbBGqAi7Tfr9GXk4, a reachability sweep at maxPages: 1, pageSize: 1): the corpus planned into 164 slices in 27 s off 15 probe calls, and every one of the 164 slices was then entered and answered with a row — 179 search calls, 154 unique rows after de-duplication, 4 m 17 s. That proves every slice is reachable; it is not a 207,809-row extraction, and this Actor does not claim one it has not run. At pageSize: 200 a full sweep is roughly 1,040 page calls on top of the planning, so budget about an hour and set the run timeout accordingly (the Actor defaults to 3,600 s and always flushes what it has collected before the timeout hits).

Read the slice counter, and trust it. The run log now reports slices crawled out of slices planned1 of 164 planned query slice(s) crawled (partial sweep) — and warns you by name when the two differ and why (time limit, charge cap, or maxItems reached). Measured 2026-09-06 (run 5Tw54aMLArLR6Xv0A, whole corpus under a $0.05 cap): 100 rows, 18 search calls, "Partial coverage: 1 of 164 planned slice(s) were actually crawled (the run's charge cap was spent). Treat these rows as a sample of the query, not the whole of it." Build 0.1.14 printed

164 query slice(s) crawled
for that same run — the planned count, which reads as a swept corpus.

Why does my run return fewer rows than VDAB's headline number? Three honest reasons. (1) A single un-sliced query cannot exceed ~20,000 — turn slicing on. (2) Slices that must split on job domain, education, shift or licence rather than a true partition can miss the handful of vacancies that carry no value on that axis; the run log warns you by name when this happens. (3) VDAB's index changes while you crawl — it moved by 5 vacancies over the 20 minutes of testing on 2026-09-06 alone. Measured 2026-09-04: re-crawling the same 328-vacancy set through a partition axis recovered 327 of 328 (99.7%), and through the non-partition job domain axis 324 of 328 (98.8%) — that small gap is live index churn between passes, not a parser bug.

Is salary included? No. VDAB's API has no compensation field. See above.

What is the difference between employerName and employerLegalName? employerName is the name on the listing (which for an agency posting is the agency's brand). employerKind tells you whether the poster is the direct employer or a staffing agency — GEWONE_BEDRIJVEN vs INTERMEDIAIREN in VDAB's own vocabulary. employerLegalName, employerWebsite and employerId come from the detail record.

Can I get every vacancy from one company? Yes. Take employerGalleryId from any row and pass it as employerId. Measured: 3446 returns 892 vacancies.

How does monitor mode avoid re-reporting the same vacancy? It keeps its seen-set in a named key-value store (vdab-jobs-monitor-state) that survives across runs. Verified 2026-09-06: run vhignWJ24TLtZVfsm returned 25 new vacancies and remembered their ids; run c6PluVtnp87nwsa1P on the identical input loaded those 25 back, skipped every one of them and returned the next 25 unseenzero id overlap between the two runs. A vacancy is only committed to the seen-set once it has actually been delivered, so a row your charge cap trimmed away is still new next run rather than silently lost.

Does it cover Wallonia and Brussels? No — VDAB is the Flemish service. Le Forem (Wallonia) and Actiris (Brussels) are different sites and belong in different Actors.

What happens if VDAB returns nothing? The run finishes SUCCEEDED with a status message, not FAILED, and the message ends with the run's real charged total read out of the billing counters — Nothing was charged. when that is true, otherwise Charged for this run: $X.XXXX (…). It never promises you a zero it did not bill. The message also says which of the seven possible reasons the run was empty, rather than guessing:

  • your maxTotalChargeUsd did not cover a single row → the message says that and tells you to raise it.
  • you passed a single-vacancy URL and VDAB answered 404 → the message says the vacancy id does not exist or has been taken offline, and does not claim a search came back empty (no search was run).
  • the run hit its own time limit before a row could be delivered → the message says so, names what it had already measured (search calls, vacancies scanned, VDAB's own result count if it got that far) and tells you to raise the run timeout. It never blames your filters for your timeout. Measured 2026-09-06 (run CxaVD3ejx91NXBnId, keyword verpleegkundige under a 32 s run timeout): 0 rows, 0 charged, status "This run hit its 32s time limit before it could deliver a row — this is NOT an empty VDAB result and your filters are not the problem." On build 0.1.14 the identical input said "No VDAB vacancies matched this search (VDAB reported 0 results)" for a keyword VDAB holds 3,394 vacancies for.
  • no search call was ever sent for another reason it can name — the charge cap was spent first, or your startPage x pageSize already starts past VDAB's ~20,000-row offset ceiling. A run that never asked VDAB anything is never reported as VDAB answering nothing.
  • monitor mode found nothing newly posted since the previous run → "No newly posted vacancies since the previous monitor run. Charged for this run: $0.0100 (1 x monitor-run-completed)." This is the one empty run that is billed — the $0.01 bought a real check — and it says so (run aflu6QJB9jY514fpr, 2026-09-07). A monitor run that never reached VDAB is charged nothing; see Pricing above.
  • the search matched plenty but your newOnlySince cutoff dropped every row → the message says so and names the count it scanned, because that is a filter you set, not an empty job board.
  • VDAB's search genuinely matched nothing, across search calls that were actually made and answered → "No VDAB vacancies matched this search (VDAB reported 0 results) across N search call(s)." This is the only message that blames the query, and it is only reachable when a search really ran.

A run where every API call failed for a transport reason does fail loudly — including a vacancy-URL run whose fetches got no response at all — so you never mistake a transport problem for "there are no such jobs".

Which language is the data in? Dutch — VDAB's labels are Dutch and this Actor passes them through rather than inventing translations. Codes (JOBCAT10, BE231A, CE) are language-neutral.


  • This Actor reads public, un-authenticated VDAB endpoints — the same ones vdab.be's own search page calls. It does not log in, does not solve challenges, and does not touch anything behind /mijnvdab/ or any other private path.
  • robots.txt, checked line by line on 2026-09-04. https://www.vdab.be/robots.txt contains no Disallow rule matching /rest/, the path this Actor uses. It does contain Disallow: /api/vindeenjob/, Disallow: /include/vacature/ and Disallow: /vac/this Actor calls none of those. VDAB also publishes Sitemap: https://www.vdab.be/sitemap/vindeenjob/vacatures/index.xml, i.e. it invites crawlers to index exactly these vacancy pages.
  • Request volume is modest and rate-limited (default concurrency 5, measured clean at 8).
  • Personal data is your responsibility. With includeDetail on, rows can contain a named hiring contact. That is personal data under the GDPR. You are the controller for whatever you do with it — have a lawful basis, honour erasure requests, and do not use it for anything a Belgian recruiter would not expect. Turn includeDetail off if you only need the vacancy data.
  • You are responsible for complying with VDAB's terms of use in your jurisdiction and use case.

Built by ScrapersDelight. Found a field VDAB returns that this Actor does not? Open an issue on the Actor page — the API surface is wide and we would rather ship it than have you post-process it.