Werk.nl Scraper icon

Werk.nl Scraper

From $1/1K. Scrape job vacancies from werk.nl, the official Dutch government job board by UWV. Uses werk.nl’s vacancy data service for fast, low-cost…

Start using this actor on Apify ↗Need a custom scraper?Contact us ↗

Werk.nl Scraper at a glance

Werk.nl Scraper is a ready-to-run Apify actor from abotapi for collecting jobs data. Its documented output includes title, change type (incremental mode), profession, employer and city, which you can export as JSON, CSV or Excel or read through the Apify API. It costs $1.80 per 1,000 results on the Apify Free plan and from $1.00 on paid plans, with smaller per-run or optional feature charges listed on Apify.

Runs on
Apify cloud: console, API, schedules and integrations
Data type
Jobs, Automation, Lead Generation
Region
Europe
Pricing
$1.80 per 1,000 results (Free plan), from $1.00 on paid plans
Output
Documented below · JSON, CSV, Excel
Inputs
35 options

From $1/1K. Scrape job vacancies from werk.nl, the official Dutch government job board by UWV. Uses werk.nl’s vacancy data service for fast, low-cost runs. Returns 40+ fields per job, including title, salary, full description, recruiter contact, application link, and employer profile.

What Werk.nl Scraper $1💰 Dutch Job Vacancies, Salaries & Contacts can do

  • Two ways to find vacancies. Search by keyword and location with rich filters, or paste specific vacancy detail-page URLs or bare reference numbers.
  • Detail-page data included. With full vacancy details on (the default), get the complete description, salary indication, terms of employment, the full employer profile and the recruiter contact (name, department, phone and e-mail) when werk.nl publishes them.
  • Rich search filters. Narrow by profession, education level, hours per week, working-hours type, driving licence, language, contract type, and country (for EURES cross-border vacancies).
  • Flags and flat fields. Apprenticeship, internship and EURES-priority flags, plus flat convenience fields (jobId, companyName, salaryMin, contactEmail, driversLicenseLabels and more) next to the nested employer and contact objects.
  • Built for recurring monitoring. Incremental mode classifies every vacancy as new, updated or unchanged against your previous run (reappeared and expired need Emit expired on plus a complete scan), so a scheduled run returns only what changed.
  • Handles the site's daily maintenance window. A short maintenance window on werk.nl is detected and retried automatically rather than being read as zero results.

Choose what to collect

These inputs let you configure Werk.nl Scraper $1💰 Dutch Job Vacancies, Salaries & Contacts. Open the actor on Apify to enter your values and review the current options.

Supported input fields
InputWhat it controls
modestringsearch = find vacancies by keyword/location/filters. url = fetch specific vacancy detail pages by URL or reference number.

Choices: search, url

keywordsarraySearch term(s) - job title, profession, keyword(s) or a reference number. One search is run per keyword. Leave empty to list all vacancies (optionally narrowed by the filters below).
querystringSingle search term; used only when Keywords is empty.
locationstringCity or postcode to search in (e.g. 'Amsterdam' or '1011AB'). Leave empty for the whole of the Netherlands.
professionarrayProfession category filter, e.g. 'Developer', 'Software engineer', 'Verpleegkundige'. Values must match werk.nl's profession names.
educationLevelsarrayRequired education level.
educationLevelstringSingle education level by code (1-7) or Dutch label (e.g. MBO, HBO/bachelor). Used only when Education level list is empty.
Show 28 more inputs
More input fields
InputWhat it controls
hoursPerWeekarrayWorking hours per week.
workingHoursarrayOffice hours or otherwise.
drivingLicensearrayRequired driving licence category.
languagearrayRequired language skill.
contractTypearrayType of employment contract.
countryarrayCountry of the work location (for EURES / cross-border vacancies).
sortBystringResult ordering.

Choices: relevance, date, datum, relevantie

expandResultsbooleanBroaden the search to include related vacancies when there are few exact matches (werk.nl's 'verruim zoekopdracht').
urlsarrayURL mode only: vacancy detail-page URLs (www.werk.nl/nl/vacatures/), the backend vacancy JSON URL, bare reference numbers, or the vacancy search page (www.werk.nl/nl/vacatures, optionally with ?trefwoord=&plaats=), which runs a search capped by Max vacancies. Left untouched, one example of each shape is scraped. An expired reference is skipped rather than returned as an empty row; an unrecognised URL is skipped with a warning.
startUrlsarrayAlias of the URLs field, for callers that use startUrls. Entries may be plain strings or {"url": ...} objects. Used when URLs is empty or still holds the example links.
fetchDetailsbooleanInclude each vacancy's full detail: complete description, salary, recruiter contact (name/phone/e-mail), application method and complete employer profile. Turn off for a faster, listing-only run.
includeRawDetailbooleanAttach the untouched detail JSON under a 'raw' field on each record (for advanced users who want every backend field).
excludeEmptyFieldsbooleanOmit fields that are empty from each row.
compactbooleanDrop heavy nested fields (HTML description, employer and contact objects, application methods, extracted URLs, raw detail).
descriptionMaxLengthintegerTruncate description fields to this many characters. 0 = full text.
descriptionFormatstringWhich description variants to include in each row.

Choices: all, text, html, markdown

maxItemsintegerMaximum number of vacancies to return across all searches. This is the run's hard cap (default 20). Set to 0 for no limit.
maxPagesPerSearchintegerOptional secondary cap on result pages (20 vacancies each) per keyword. Leave empty (the default) to page freely until Max vacancies is reached, Max vacancies is the binding cap.
resumeFromRunIdstringPaste a previous run ID or dataset ID to continue a large crawl of vacancies without returning or charging for vacancies already collected there. Use this after an interrupted run, or when continuing a large pull across multiple runs. For recurring daily/weekly monitoring of the same search, use Incremental mode below instead.
incrementalModebooleanTurn this on for daily or recurring monitoring. The first run returns all matching vacancies as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED vacancies. Turn on "Emit unchanged" or "Emit expired" only when you also want those vacancies returned (and billed). State is kept separately for each search/URL and filter setup; use State key when you want to name or deliberately share a monitoring campaign. What is compared: every output field of the vacancy except scrapedAt (always excluded) and any field you list in "Ignore fields when detecting changes"; a change in any compared field (including nested employer or contact data) makes the vacancy UPDATED, and changedFields names it. Integral numbers compare equal whether stored as 25 or 25.0. To continue one specific interrupted run instead, use Resume from a previous run above.
stateKeystringOptional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from the search/URL and filter settings, different searches then never mix state with each other.
emitUnchangedbooleanOff by default. Turn on to also return vacancies that have not changed since the last run, marked UNCHANGED. This returns, and bills, extra rows you already have, so leave it off unless you specifically want the full snapshot every run.
emitExpiredbooleanOff by default. Turn on to also return vacancies that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search, not when Max vacancies capped it or when Resume was used. This returns, and bills, extra synthetic rows, so leave it off unless you need expiry tracking.
skipRepostsbooleanDrop a vacancy that repeats one already seen in this run (same organisation, title, city and description under a different reference number). Dropped rows are not charged. Without this, such rows are returned with isRepost = true.
ignoreFieldsForChangesarrayOptional. Field names that must NOT make a vacancy count as UPDATED, for example ["modifiedDate", "employer.name"]. Nested fields use dot notation (a path through a list applies to every element). The fields stay in the output and are simply left out of the comparison and out of changedFields. Changing this list never causes a wave of UPDATED rows: the stored snapshot is re-compared with the current list.
mcpConnectorsarrayOptionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON, the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).
notionParentPageUrlstringURL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.
maxNotifyListingsintegerCap on items written to each connector per run. Does not affect the dataset.

Fields in the output

The documented dataset includes the fields below. Availability can depend on the source page and selected mode.

titleTitle
changeTypeChange type (incremental mode) (text)
professionProfession
organisationEmployer
cityCity
contractTypeContract
minHoursMin h/wk
maxHoursMax h/wk
modifiedDateUpdated
applyUrlApply (link)
urlURL (link)
changedFieldsChanged fields (incremental mode) (array)
Show 21 more fields
firstSeenAtFirst seen (incremental mode) (date)
lastSeenAtLast seen (incremental mode) (date)
referenceNumberReference
contactNameContact name
contactEmailContact email
contactPhoneContact phone
contactDepartmentContact department
contactContact
employerEmployer profile
companyWebsiteEmployer website
salarySalary
salaryIndicationSalary indication
salaryMinSalary min
salaryMaxSalary max
salaryTypeSalary type
termsOfEmploymentTerms of employment
applicationMethodsApplication methods
descriptionDescription
descriptionTextDescription (text)
previousSeenAtPrevious seen (date)
expiredAtExpired (date)

Example output

An example from this actor’s documentation. Values are illustrative; this is not a fresh live result.

{
  "referenceNumber": 10000001,
  "url": "https://www.werk.nl/nl/vacatures/10000001",
  "externalReferenceId": "SMP-2026-0142",
  "source": "JIT",
  "title": "Junior Applicatiebeheerder",
  "profession": "ICT-beheerder",
  "relevanceScore": 87,
  "createdDate": "2026-05-01T00:00:00",
  "modifiedDate": "2026-05-14T00:00:00",
  "expirationDate": "2026-06-30",
  "city": "Zwolle",
  "postcode": "8011AB",
  "workLocationType": "Vaste werklocatie",
  "workLocationTypeCode": 1,
  "contractType": "Vast",
  "contractTypeCode": 1,
  "minHours": 32,
  "maxHours": 40,
  "studyLevel": "MBO",
  "salaryTypeCode": 1,
  "salaryIndication": "€2.800 - €3.400 per maand",
  "termsOfEmployment": "Marktconform salaris, reiskostenvergoeding en 25 vakantiedagen.",
  "functionName": "Applicatiebeheerder",
  "description": "Als Applicatiebeheerder ondersteun je de IT-afdeling bij het beheren en optimaliseren van bedrijfsapplicaties.",
  "descriptionHtml": "<p>Als Applicatiebeheerder ondersteun je de IT-afdeling bij het beheren en optimaliseren van bedrijfsapplicaties.</p>",
  "isApprenticeship": false,
  "isInternship": false,
  "isEuresPriority": false,
  "isByEmployerDirectly": true,
  "organisation": "Sample Employer B.V.",
  "employer": {
    "organizationName": "Sample Employer B.V.",
    "website": "https://www.example.com",
    "sector": "Techniek",
    "address": {
      "street": "Voorbeeldstraat",
      "houseNumber": "12",
      "postcode": "8011AB",
      "city": "Zwolle"
    }
  },
  "contact": {
    "name": "Recruiter Name",
    "department": "HR",
    "phoneNumber": "+31 6 00000000",
    "email": "vacatures@example.com"
  },
  "applicationMethods": [
    {
      "typeCode": 1,
      "type": "Online formulier",
      "url": "https://www.example.com/solliciteren"
    }
  ],
  "applyUrl": "https://www.example.com/solliciteren",
  "scrapedAt": "2026-05-15T09:30:00+00:00"
}

Ways to use this data

  • Recruitment agencies and staffing firms: monitor new vacancies in a niche and see who is hiring (check isAcquisitionNotAppreciated before contacting an employer directly).
  • Job boards and aggregators: mirror UWV's public vacancy feed into your own site, app or newsletter.
  • Labour-market researchers and policy analysts: track salary indications, contract types, hours and professions across regions and time.
  • HR and compliance teams: watch for EURES cross-border, apprenticeship or internship vacancies in a specific sector or employer.

Before you run

Start with a small input and check the resulting records against your expected fields. Source content and actor options can change; the current Apify listing is the reference for availability and pricing.

Questions about this actor

What data does Werk.nl Scraper return?

The documented output for Werk.nl Scraper includes title, change type (incremental mode), profession, employer, city, contract, min h/wk and max h/wk. Availability of each field depends on the source page and the selected mode.

How much does Werk.nl Scraper cost?

It costs $1.80 per 1,000 results on the Apify Free plan and from $1.00 on paid plans, with smaller per-run or optional feature charges listed on Apify. The Apify listing shows the current rate for every plan.

Can I get only new or changed vacancies on a schedule?

Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new and updated vacancies for the same search or URL set (plus reappeared ones, only once an Emit expired pass has marked them gone), and unchanged ones are not billed unless Emit unchanged is on.

Why did my run finish with no vacancies instead of failing?

If every request to werk.nl is refused, or the site's daily maintenance window is active for the whole run, the run still finishes successfully with an empty dataset: it logs an error and sets a status message (in search mode, that no vacancies were returned; in url mode, that some or all references could not be resolved, which can also mean they are simply expired), rather than treating "nothing found" as an error. Run it again in a few minutes, and check that a connection is configured if it persists. A run deliberately fails outright when it cannot make sense of your own input, such as an unreadable resumeFromRunId, or incrementalMode combined with resumeFromRunId while a saved state already exists for that search; an unhandled error elsewhere in the run also fails it.

Does REAPPEARED happen automatically in incremental mode?

No. REAPPEARED is only produced for a vacancy that a prior run with Emit expired on marked as no longer present, after fully scanning the tracked search or URL list to its natural end (not capped, not resumed), and that is then found present again in a later run. On a fresh state key, or on a search that has never had a complete Emit-expired pass, you will only see NEW and UPDATED (plus UNCHANGED if Emit unchanged is on).

Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push a summary of each vacancy into Notion, Linear or Airtable.

Ready to collect with Werk.nl Scraper?

Run it on Apify with your own input and export the results as JSON, CSV or Excel.