# Arbeidsplassen NAV Job Scraper — Norway Job Leads (`scrapersdelight/arbeidsplassen-jobs-scraper`) Actor

Scrape Norway's national NAV job board arbeidsplassen.nav.no: title, employer, county, occupation, dates and NAV's own AI summary, plus org number, apply link, ATS vendor and named contact e-mail/phone from the ad page. 12,400+ live ads, ~550 new per day. No login.

- **URL**: https://apify.com/scrapersdelight/arbeidsplassen-jobs-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** Jobs, Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 per job ad returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## 🇳🇴 Arbeidsplassen (NAV) Job Scraper — Norway's national vacancy board

Scrape **[arbeidsplassen.nav.no](https://arbeidsplassen.nav.no/stillinger)**, the public job board run by **NAV** (the Norwegian Labour and Welfare Administration) — the one register where Norwegian vacancies from FINN, from every major applicant-tracking system, and from employers typing straight into NAV all land in the same place.

One row per ad, up to **76 fields**: employer, **Norwegian organisation number**, county and municipality, occupation, employment type, dates — plus, from the ad page, the **apply link**, the **ATS vendor behind it**, and the **named contact person with their direct e-mail and phone**.

### What does this actor do?

- 📋 **Sweeps the whole live board** — **13,475 live ads** measured 2026-09-04 08:10 UTC (12,471 six hours earlier the same day — the board moves), and it is a *flow*, not a stock: **4,385 ads published in the last 7 days** (~626/day, ~19,000/month).
- 🏢 **Puts an org number on the row** — the join key to Brreg, Norway's company register, so every lead can be enriched with revenue, employee count and directors.
- 🔌 **Names the ATS** — derived from the apply-link hostname: `webcruiter`, `visma-talent`, `jobbnorge`, `teamtailor`, `recman`, `easycruit`, `finn.no`… This is the competitive-displacement list, and `onlyDirectPosted` gives you the opposite: employers hiring with **no recruiting software at all**.
- ☎️ **Named contact, not a careers inbox** — NAV's ads carry a real person: name, job title, direct phone, direct e-mail.
- 🤖 **Free AI enrichment** — NAV runs its own model over every ad and publishes the result: a one-paragraph `short_summary` and a skill-tag list. Both come back at ~100% fill, at no extra cost.
- 🧭 **63 input options**, and every filter that NAV's own API silently ignores (municipality, occupation sub-category, summer job, city, postal code) is applied over the rows here instead of pretending to work.
- 🔁 **Incremental monitoring** — give a run a `stateKey` and it remembers every ad UUID it has delivered, so the next run only returns (and only bills) genuinely new vacancies.

### Who buys this data

| Buyer | The row they pay for |
| --- | --- |
| **ATS / recruiting-software vendors** (Webcruiter, Visma, Teamtailor, Jobbnorge, ReachMee) | `ats_vendor` = the competitor's install base, with a named contact and a live hiring need. `onlyDirectPosted: true` = the greenfield list. |
| **Staffing & recruitment agencies** | An employer who is hiring *right now*, filtered to their county and occupation, with the deadline and the person to ring. |
| **B2B sellers into growing companies** (payroll, onboarding, insurance, workwear, IT, office) | A new vacancy is a growth signal; `org_number` makes it a CRM-ready account. |
| **Labour-market analysts and salary benchmarks** | STYRK08 / ESCO / JANZZ occupation codes, county, sector, extent and education requirements on 12,000+ live ads. |
| **International recruiters / relocation** | `workLanguage: ["Engelsk"]` — 2,717 live ads where Norwegian is not required. |

### Quick start

Every nurse vacancy in Oslo and Viken, with the org number and the contact person:

```jsonc
{
  "occupationCategories": ["Helse og sosial"],
  "occupationSubCategories": ["Sykepleier"],
  "counties": ["OSLO", "AKERSHUS"],
  "fetchDetails": true,
  "requireOrgnr": true,
  "maxItems": 500
}
```

Competitive displacement — everyone in Norway running Webcruiter:

```jsonc
{ "fetchDetails": true, "atsVendorInclude": ["webcruiter"], "maxItems": 1000 }
```

The greenfield list — employers hiring with no third-party ATS at all:

```jsonc
{ "fetchDetails": true, "onlyDirectPosted": true, "requireContactEmail": true, "maxItems": 500 }
```

A daily monitor that only ever bills you for new ads:

```jsonc
{ "counties": ["VESTLAND"], "publishedWithin": "now-3d", "stateKey": "vestland-daily", "maxItems": 0 }
```

Or just paste a search URL out of NAV's own site — including the municipality and occupation filters their API ignores:

```jsonc
{ "startUrls": ["https://arbeidsplassen.nav.no/stillinger?county=VESTLAND&municipal=VESTLAND.BERGEN&q=utvikler"] }
```

### Output

```json
{
  "uuid": "07823086-c85c-4c8e-b73f-1b0dedbd772a",
  "ad_url": "https://arbeidsplassen.nav.no/stillinger/stilling/07823086-c85c-4c8e-b73f-1b0dedbd772a",
  "title": "Mulighetsrom til å påvirke veksten til Payments.",
  "job_title": "Junior Sales Executive Payments",
  "employer_name": "SVEA FINANS AS",
  "org_number": "980130169",
  "employer_sector": "Privat",
  "employer_homepage": "https://www.svea.com/no/nb/start/",

  "county": "OSLO", "municipal": "OSLO", "city": "OSLO",
  "postal_code": "0663", "address": "Innspurten 15", "country": "NORGE",

  "occupation_level1": "Salg og service",
  "occupation_level2": "Salg, butikk- og varehandel",
  "category_styrk08": "Salgs- og markedssjefer",

  "engagement_type": "Fast", "extent": "Heltid", "position_count": 1,
  "published": "2026-09-04T07:48:21+02:00",
  "expires": "2026-09-12T00:00:00+02:00",
  "application_due": "2026-09-12", "application_due_asap": false,

  "apply_url": "https://www.finn.no/job-apply/475024396/apply",
  "apply_host": "finn.no", "ats_vendor": "finn.no", "is_direct_posted": false,

  "contact_name": "Morten Leknes",
  "contact_title": "Avdelingsleder",
  "contact_email": "morten.leknes@example.no",
  "contact_phone": "(+47) 90920664",

  "short_summary": "Stillingen innebærer løsningsorientert salg, prospektering, og kundekontakt…",
  "ai_tags": "Betalingsløsninger; Forretningsutvikling; Kundeservice; SMB; Salg",
  "ad_text": "…the full ad body, HTML stripped…",
  "detail_fetched": true
}
```

#### Measured field fill

Validated offline against live bytes on **2026-09-04**. These are the numbers from the actual runs, not estimates.

**Listing sweep — 1,950 ads, 1,950 unique, no ad-page enrichment:**

| Field | Fill |
| --- | --- |
| `uuid`, `ad_url`, `title`, `employer_name`, `business_name` | **100%** |
| `status`, `source`, `medium`, `reference`, `published`, `expires` | **100%** |
| `county`, `country`, `locations`, `locations_count` | **100%** |
| `occupation_level1`, `occupation_level2`, `occupations` | **100%** |
| `short_summary` (NAV's AI summary), `ai_tags`, `ai_under18_reason` | **100%** |
| `experience`, `need_drivers_license`, `under18` | **100%** |
| `categories`, `category_styrk08`, `education` | 99.9% |
| `search_tags` | 99.8% |
| `application_due_raw` | 98.3% |
| `municipal` | 97.3% |
| `work_language` | 97.2% |
| `city`, `postal_code` | 93.2% |
| `job_title` | 80.1% |
| `address` | 77.3% |
| `application_due` (a parsed date) | 72.0% |
| `keywords` | 53.7% |
| `remote` (on the listing; the ad page fills it to 100%) | 26.1% |

Of those 1,950 ads, **26% carry no fixed deadline at all** — NAV's deadline field says *Snarest* ("as soon as possible"), which this actor exposes as `application_due_asap: true` rather than throwing away. Source mix on the same sample: `IMPORTAPI` (pushed by an ATS) 60%, `FINN` 24%, `Stillingsregistrering` (typed straight into NAV by the employer) 16%, `AMEDIA` 0.4%.

**With `fetchDetails: true` — two independent 150-ad runs, ad page fetched for each, 75 fields on the row.**
Column A is the offline validation run; column B is a run **on the Apify platform** the same day against a
different slice of the board. Both are shown because the ad-page fields genuinely vary with which ads you pull —
quote the lower number when you plan a campaign, not the higher one.

| Field | A (offline) | B (platform) | Note |
| --- | --- | --- | --- |
| `detail_fetched` | **100%** | **100%** | the ad page parsed on 150/150 in both runs |
| `ad_text`, `position_count`, `engagement_type`, `extent`, `updated`, `remote` | **100%** | **100%** | all six are absent from the search API |
| `ai_is_summer_job`, `ai_work_experience` | 100% | 100% | NAV's own classification |
| `categories`, `category_styrk08` | 100% | 99.3% | |
| **`org_number`** | **100%** | **90.0%** | the Brreg join key |
| `employer_sector` | 98.7% | 97.3% | |
| `is_direct_posted` | 96.0% | 97.3% | null only when the ad has no apply route at all |
| `apply_url`, `apply_host`, `ats_vendor` | 89.3% | 93.3% | the rest apply by e-mail or have no link |
| `search_tags` | 100% | 90.7% | |
| `job_title` | — | 86.0% | |
| `application_due` (a parsed date) | 71.3% | 76.0% | the remainder are "Snarest" → `application_due_asap` |
| `employer_homepage` | 71.3% | 72.7% | |
| `contacts_count` / `contact_name` | 65.3% / 63.3% | 67.3% / 64.7% | **not every ad names a person** |
| `employer_description` | 59.3% | 57.3% | |
| **`contact_phone`** | **54.0%** | **58.0%** | |
| `contact_title` | 53.3% | 56.7% | |
| **`contact_email`** | **34.0%** | **25.3%** | plan on **one ad in four**, not one in three |
| `source_url` | 36.0% | 34.0% | link to the original posting off-NAV |
| `start_date` | 26.0% | 26.0% | |
| `work_hours` / `work_days` | 21.3% / 18.7% | 17.3% / 13.3% | |
| `has_superrask_soknad` | 10.7% | 6.7% | the flag is simply absent on most ads |
| `apply_email` | 6.7% | 4.0% | |
| `employer_facebook` / `employer_linkedin` | 6.7% / 1.3% | 3.3% / 0.7% | employers rarely fill these in |
| `employer_twitter`, `job_percentage` | **0%** | **0%** | the fields exist on NAV's object and are essentially never populated — reported honestly rather than quietly dropped |

**Be honest with yourself about the contact fields.** About two thirds of NAV's ads name a person, a bit over half publish a direct phone, and **a quarter to a third publish an e-mail** (34.0% and 25.3% on the two 150-ad samples above). If your workflow needs one on every row, set `requireContactEmail: true` and pay only for the rows that have it — the actor drops the rest **before** billing.

### Reliability, measured

| Test | Result |
| --- | --- |
| Transport ladder (all through Apify proxies) | direct 200 · **datacenter 200** · residential-NO 200 · residential-US 200 · residential-any 200 — the cheapest rung works, and it is not geo-blocked |
| Sustained search (varied filters and page offsets) | 56 / 57 usable = **98.2%** |
| Sustained ad-page fetch | 20 / 20 = **100%** |
| Contiguous paging, crawled the way the actor crawls | 20 pages × 100 from `from=0` to `1900` → **2,000 fetched, 2,000 unique, 0 duplicates, 0.00% loss** |
| Dedupe proof (offline) | the same search fed twice → **714 rows / 714 unique**; with `deduplicate:false` → 1,428 rows / 714 unique |
| Dedupe proof (**on Apify**, billing checked) | the same NAV search URL passed twice → **9 rows delivered, `job-scraped` charged 9**; the same run with `deduplicate:false` → 18 delivered, 18 charged. A duplicate ad is never billed twice unless you explicitly ask for it |
| Two different inputs → two different result sets | `county=TROMS` vs `county=ROGALAND`: 60 + 60 rows, **overlap 0**; `sykepleier` vs `elektriker`: overlap 0 |
| Ad-page parse over a 150-ad enrichment run | **150 / 150 parsed** |
| Live platform runs | **19 runs, 19 SUCCEEDED, 0 failed and 0 timed out** (13 on build 0.1.1, 6 on 0.1.2), worst wall-clock **123.9 s** against Apify's 300 s limit; `chargedEventCounts` equals the delivered row count on every single one |
| Offline validation suite | **48 checks, 48 pass** — every filter asserted against the rows it returned, plus dedupe, incremental state, the empty-input badge preflight and the zero-match clean exit |

The only residual failure mode is not NAV: the Apify proxy tunnel answers `595` on roughly 2-3% of calls. Every request is wrapped in a retry with a fresh proxy session, which converts it to ~100%.

### The three things about NAV's API that break naive scrapers

They are all in the code with the measurement next to them, and they are worth knowing if you are evaluating other actors on this site:

1. **`size` is honoured at 100 and nowhere else.** 1, 5, 10, 20, 25, 30, 40, 50, 75, 99, 101 and 200 all silently return **25** rows. A pagination loop written as `from += size` with `size=50` therefore skips every other page — it loses **half the corpus** and still finishes green.
2. **The result window is `from + size ≤ 10000`.** `from=9900` is fine; `from=9901` returns HTTP 400 *"Pagination depth exceeds maximum allowed window"*. The board runs ~12,500–13,500 ads (12,471 and 13,475 measured six hours apart on 2026-09-04), so **a full sweep is impossible in one query**. This actor slices by county (largest slice: OSLO, 2,185) and then by occupation if a slice is still too deep, sweeps the ads located abroad through the country facet (measured today: 35 ads across 16 countries, DANMARK 5 / SVERIGE 6 / ISLAND 6 / SVEITS 4 / …) — they have no Norwegian county and would otherwise vanish — and deduplicates on ad UUID, because NAV counts a multi-county ad once per county (the county slices sum to 13,008 against a 12,460 corpus).
3. **Five filter parameters are accepted and silently ignored.** `municipal` / `municipals` / `municipality` / `kommune`, `occupationLevel2`, `summerJob`, `postcode` and `city` never move the hit count, in any value shape. This actor does not pretend otherwise: those are applied over the rows after fetching, including when they appear in a `startUrls` link pasted from NAV's own UI.

### Server-side vs client-side filters

**Applied by NAV (cheap, narrows the fetch):** keyword, counties, countries, occupation categories, published-within (only `today` / `3 days` / `7 days` exist), sort, employment type, full/part-time, sector, education, experience, working language, remote, driving licence, under-18, quick-apply.

**Applied by this actor over the rows:** municipalities, occupation sub-categories, cities, postal codes and prefixes, exact published/expiry/deadline date ranges, title include/exclude, ad-text keywords, employer include/exclude, staffing-agency blocklist, ad source, ATS vendor, direct-posted-only, summer jobs, position count, and the lead-quality gates (`requireOrgnr`, `requireContactEmail`, `requireContactPhone`, `requireApplyLink`).

Filters that can only be decided from the ad page **refuse to run** without `fetchDetails` rather than silently doing nothing.

### Pricing

Pay-per-event, no subscription, no platform-usage surcharge.

| Event | Price | When it fires |
| --- | --- | --- |
| `job-scraped` | **$1.20 / 1,000 ads** | once per unique ad delivered — after deduplication and after your filters, so you never pay for a row you did not receive |
| `job-detail-enriched` | **$2.50 / 1,000 ads** | only with `fetchDetails: true`, and only for rows that really came back enriched |

A full sweep of the live board with everything on is **13,475 × $0.0037 ≈ $50**; keeping it fresh on new ads only (~19,000 ads/month at the measured 4,385-per-week rate) is about **$70/month**. Listing-only, the whole board is **$16**.

Rows are charged through Apify's budget-aware push, so if you cap a run's spend you receive exactly the rows you paid for — never more delivered than billed, and never a duplicate billed twice.

### Input reference — the options that matter

| Input | What it does |
| --- | --- |
| `query` | free-text keyword, Norwegian (`sykepleier`, `utvikler`, `elektriker`) |
| `counties` / `municipalities` | county is server-side and cheap; municipality is client-side because NAV's API ignores it |
| `occupationCategories` / `occupationSubCategories` | NAV's two-level occupation taxonomy |
| `startUrls` | paste searches straight from NAV's website; `municipal=` and `occupationLevel2=` are translated |
| `adUrls` | skip the search, fetch named ads by URL or UUID |
| `fetchDetails` | the second surface: org number, apply link, ATS vendor, contact person, ad text |
| `requireOrgnr` / `requireContactEmail` / `requireContactPhone` / `requireApplyLink` | lead-quality gates — filtered rows are never billed |
| `atsVendorInclude` / `atsVendorExclude` / `onlyDirectPosted` | the recruiting-stack angle |
| `excludeStaffingAgencies` | drops bemanning / vikarbyrå / rekruttering employers |
| `stateKey` | incremental monitoring in a named key-value store |
| `maxItems` | the hard cap on rows delivered **and billed**; `0` = the whole board |
| `flattenOutput` | flat CRM-ready rows (default) vs nested arrays |
| `includeRawSource` | attaches NAV's untouched `_source` and `adData` objects |

### FAQ

**Is this the same as scraping FINN.no?**
No. FINN is Norway's commercial classifieds site; arbeidsplassen.nav.no is the state board. A large share of NAV's ads are syndicated *from* FINN (`source: "FINN"`), but NAV also carries ads pushed by ATS vendors and ads typed directly into NAV by small employers who never advertise anywhere else. `sourceExclude: ["FINN"]` gives you the non-FINN half.

**Do I need a NAV API token?**
No. This actor reads the site's own public search endpoint and its public ad pages. NAV *also* offers a registered, token-gated ad feed for high-volume integrators; if you have one, it is a different product and this actor does not use it.

**How fresh is the data?**
NAV publishes continuously — 98 ads on the morning this was measured, 2,109 in the previous 3 days. `publishedWithin: "now/d"` plus a `stateKey` gives you a same-day feed.

**Why does the ad page cost extra?**
Because it is a second HTTP request per ad. The search API simply does not contain the org number, the apply link or the contact person — measured 0.0% fill on all of them across 402 listing rows. If you only need the vacancy itself, leave `fetchDetails` off and pay the listing rate.

**Will it return every ad on the board?**
Yes, with `maxItems: 0` and `autoSliceCorpus: true` (the default). The run log prints NAV's own reported total next to the delivered-unique count and flags the delta, so you can see the coverage rather than trust it.

**What happens if my filters match nothing?**
The run finishes **successfully** with zero rows and a log line telling you so. It only fails when it genuinely could not read NAV — a broken run and an empty one must not look the same.

**Can I get the raw NAV objects?**
`includeRawSource: true` attaches `raw_source` (the Elasticsearch `_source`) and `raw_detail` (NAV's own `adData`). `includeEmbeddingVector: true` adds the 256-float `compositeAdVector` NAV publishes for semantic matching.

**Can it run on a schedule?**
Yes — set a `stateKey`, schedule it, and each run delivers and bills only ads it has never delivered before.

### Legal & fair use

- `https://arbeidsplassen.nav.no/robots.txt` is `User-agent: *` / `Disallow:` — an **explicit allow-all**, plus a sitemap. This actor reads only public pages and the site's own unauthenticated search endpoint. No login, no token, no signature, no CAPTCHA, and nothing that circumvents an access control.
- Requests are proxied, retried politely and rate-limited by `requestDelayMs` and `maxConcurrency`. Leave the defaults unless you have a reason.
- **Personal data.** `contact_name`, `contact_email` and `contact_phone` are real individuals' work contact details, published by their employer in a job ad. Norway is in the EEA, so the **GDPR applies**: you need a lawful basis to process them, and the obligations around information, retention and erasure are yours, not this actor's. If you do not need them, leave `fetchDetails` off or drop the columns.
- Fair use, respect NAV's terms, and check your own compliance position before you build a marketing list on top of this.

***

*Built and measured 2026-09-04 against the live site. Every number on this page came from a run, not from a guess.*

# Actor input Schema

## `query` (type: `string`):

Free-text search across the ad (Norwegian). Examples: `sykepleier`, `utvikler`, `elektriker`, `logistikk`. Leave empty to sweep the whole live board (12,400+ ads). Sent to NAV as the `q` parameter and matched server-side.

## `counties` (type: `array`):

Norwegian counties to keep. Several counties are OR-ed together server-side (measured: OSLO + ROGALAND = 3,274 ads). Leave empty for the whole country.

## `countries` (type: `array`):

Ads placed on NAV for jobs OUTSIDE Norway have no Norwegian county, so they are only reachable through this filter. Measured live: DANMARK 5, ISLAND 6, SVERIGE 6, FINLAND 2, POLEN 2 — 24 in total. Use the Norwegian country name in capitals.

## `occupationCategories` (type: `array`):

NAV's top-level occupation taxonomy, OR-ed server-side. Live sizes: Helse og sosial 4,525 · Salg og service · Kontor og økonomi · Håndverkere · Industri og produksjon · Bygg og anlegg · IT · Transport og lager · Utdanning · Reiseliv og mat.

## `publishedWithin` (type: `string`):

NAV only honours THREE relative windows server-side. For an exact date range use `Published after` / `Published before` below, which this actor applies client-side.

## `sortBy` (type: `string`):

Server-side ordering. `published` (newest first) is the right choice for lead work and for incremental monitoring; `relevance` only makes sense with a search keyword.

## `engagementTypes` (type: `array`):

Contract type, OR-ed server-side. Live sizes: Fast 8,487 · Vikariat 1,964 · Annet 769 · Engasjement 586 · Sesong 265 · Prosjekt 226 · Åremål 113 · Lærling 17.

## `extent` (type: `array`):

Live sizes: Heltid 8,499 · Deltid 4,264 · Ikke oppgitt 116. Repeating the value OR-s server-side (measured Heltid+Deltid = 12,353).

## `sector` (type: `array`):

Private vs public employer. Live sizes: Privat 7,288 · Offentlig 4,749 · Ikke oppgitt 20. Public-sector ads are the ones that most often carry a named contact person with a direct phone number.

## `education` (type: `array`):

OR-ed server-side. Live sizes: Ingen krav 5,337 · Bachelor 4,523 · Fagbrev 3,138 · Master 2,232 · Fagskole 1,275 · Videregående 801 · Forskningsgrad 142.

## `experience` (type: `array`):

OR-ed server-side. Live sizes: ingen krav til arbeidserfaring 8,379 · noe arbeidserfaring 3,312 · mye arbeidserfaring 780.

## `workLanguage` (type: `array`):

OR-ed server-side. Live sizes: Norsk 11,597 · Engelsk 2,717 · Skandinavisk 1,478 · Samisk 113. `Engelsk` is the fastest way to a list of Norwegian employers hiring without Norwegian — the standard relocation / international-recruiter cut.

## `remote` (type: `array`):

OR-ed server-side. Live sizes: Ikke oppgitt 7,011 · Ingen mulighet for hjemmekontor 4,478 · Delvis hjemmekontor 909 · Kun hjemmekontor 73.

## `needDriversLicense` (type: `string`):

Server-side. Live sizes: false 9,172 · true 3,258 · Ikke oppgitt 41.

## `under18` (type: `string`):

NAV's own classification of whether the role is open to applicants under 18. Server-side. Live sizes: true 1,179 · false 11,292.

## `hasSuperraskSoknad` (type: `string`):

Ads that accept NAV's one-click application. Server-side. Live: 966 true / 819 false (the rest do not carry the flag at all).

## `startUrls` (type: `array`):

Paste `arbeidsplassen.nav.no/stillinger?...` URLs straight out of your browser — build the search in NAV's own UI, copy the address bar, drop it here. Every filter in the query string is translated, including `municipal=` and `occupationLevel2=`, which NAV's API ignores and this actor therefore applies client-side. Each URL is swept as its own search.

## `adUrls` (type: `array`):

Skip the search entirely and fetch these ads directly. Accepts full `https://arbeidsplassen.nav.no/stillinger/stilling/<uuid>` URLs or bare UUIDs. These are always fetched from the ad page, so they always carry the detail fields (org number, apply link, contact person) and are billed for both events.

## `municipalities` (type: `array`):

CLIENT-SIDE filter — NAV's API silently ignores every municipality parameter (measured: `municipal`, `municipals`, `municipality`, `kommune` in every value shape leave the result count unchanged), so this actor fetches the county and filters the rows itself. Accepts `BERGEN` or NAV's own `VESTLAND.BERGEN` key. Pair it with the matching county to keep the fetch small.

## `occupationSubCategories` (type: `array`):

CLIENT-SIDE filter — `occupationLevel2` is silently ignored by NAV's API. Examples: `Sykepleier` (1,068 live), `Helsefagarbeider`, `Utvikling`, `Elektriker/elektro`. Accepts `Sykepleier` or `Helse og sosial.Sykepleier`.

## `cities` (type: `array`):

Client-side filter on the ad's city (`OSLO`, `BERGEN`, `TRONDHEIM`). Case-insensitive, matches any of the ad's locations.

## `postalCodes` (type: `array`):

Client-side filter on the exact 4-digit Norwegian postal code of any of the ad's locations.

## `postalCodePrefix` (type: `array`):

Client-side. Keep ads whose postal code starts with any of these, e.g. `05` for central Oslo, `50` for Bergen.

## `publishedAfter` (type: `string`):

Client-side, exact date or datetime (`2026-09-01` or an ISO timestamp). NAV only exposes three relative windows server-side, so any exact range is applied here after fetching.

## `publishedBefore` (type: `string`):

Client-side, exact date or datetime.

## `expiresAfter` (type: `string`):

Client-side. Keep ads still live after this date.

## `expiresBefore` (type: `string`):

Client-side. Keep ads that expire before this date.

## `applicationDueBefore` (type: `string`):

Client-side urgency filter — 'every ad closing in the next 10 days'. NAV's deadline field is free text and really does carry three shapes (measured over 400 live ads: `Snarest` 91, `dd.mm.yyyy` 76, bare ISO `2026-09-13T00:00:00` 233); this actor parses both date shapes into `application_due` and flags `Snarest`/`Fortløpende` as `application_due_asap`, which passes this filter because as-soon-as-possible is more urgent than any date. Ads with an unparseable deadline are dropped.

## `titleIncludeAny` (type: `array`):

Client-side, case-insensitive substring match against the ad title and job title. Use it to tighten a broad occupation sweep.

## `titleExcludeAny` (type: `array`):

Client-side, case-insensitive. Drops ads whose title or job title contains any of these.

## `descriptionIncludeAny` (type: `array`):

Client-side keyword match against the FULL ad body. Requires `Fetch ad details`, because the ad body only exists on the ad page.

## `employerInclude` (type: `array`):

Client-side, case-insensitive substring match on the employer / business name. Use it for named-account monitoring ('tell me every time Equinor posts').

## `employerExclude` (type: `array`):

Client-side blocklist on the employer / business name.

## `excludeStaffingAgencies` (type: `boolean`):

Convenience blocklist on top of `employerExclude`: drops employers whose name matches the Norwegian staffing/recruitment vocabulary (bemanning, vikarbyrå, rekruttering, personell, manpower, adecco, randstad, jobzone, academic work, hays, workshop bemanning …). Staffing ads are re-posts of somebody else's vacancy, so they are noise for most lead use-cases and the whole point for a few.

## `sourceInclude` (type: `array`):

Client-side filter on NAV's `source` field: `FINN` (re-published from finn.no), `IMPORTAPI` (pushed by an ATS), `Stillingsregistrering` (typed straight into NAV by the employer), `AMEDIA`, `DIR`. `Stillingsregistrering` is the closest thing to a 'small employer with no recruiting stack' segment.

## `sourceExclude` (type: `array`):

Client-side. Common use: exclude `FINN` to drop everything syndicated from finn.no.

## `atsVendorInclude` (type: `array`):

Client-side filter on the applicant-tracking system the employer runs, derived from the apply-link hostname (webcruiter, visma-talent, jobbnorge, teamtailor, recman, easycruit, talentech, workday, greenhouse, ansettr, finn.no …). This is the competitive-displacement list an ATS vendor buys. Requires `Fetch ad details`.

## `atsVendorExclude` (type: `array`):

Client-side. Requires `Fetch ad details`. Use it to strip out your own customers before handing the list to sales.

## `onlyDirectPosted` (type: `boolean`):

Client-side. Keeps only ads whose apply link is NOT a known applicant-tracking system or job-board redirect (finn.no included) — i.e. the employer applies through their own site or a plain email. That is the 'no recruiting software yet' segment. Requires `Fetch ad details`.

## `isSummerJob` (type: `boolean`):

Client-side filter on NAV's own AI summer-job classification (`aiIsSummerJob`). NAV's API exposes a summerJob facet but silently ignores `summerJob` as a query parameter, and the flag is not on the listing row — so this requires `Fetch ad details`. Live corpus: 120 summer jobs of 12,471 — under 1%, so expect the run to fetch a lot of ad pages before it finds them. You are still only billed for the ones delivered.

## `minPositionCount` (type: `integer`):

Client-side. Keep ads advertising at least this many positions — a volume-hiring signal (the live board advertises 25,014 positions across 12,471 ads). Requires `Fetch ad details`. 0 = no minimum.

## `maxPositionCount` (type: `integer`):

Client-side. Requires `Fetch ad details`. 0 = no maximum.

## `requireOrgnr` (type: `boolean`):

Lead-quality gate. The Norwegian organisation number is the join key to Brreg (the company register), so a row without it cannot be enriched with revenue, employees or the board. Measured fill: 75% of ads. Requires `Fetch ad details`.

## `requireContactEmail` (type: `boolean`):

Lead-quality gate on the named contact person's e-mail. Measured fill: 35% of ads — do not expect one on every row. Requires `Fetch ad details`. Personal data: see the README.

## `requireContactPhone` (type: `boolean`):

Lead-quality gate. Measured fill: 35% of ads. Requires `Fetch ad details`.

## `requireApplyLink` (type: `boolean`):

Drops ads with no way to apply. Measured fill: 90% have a URL or an e-mail. Requires `Fetch ad details`.

## `fetchDetails` (type: `boolean`):

Opens each ad page as a second request and adds the fields that do NOT exist on the search API at all: organisation number, apply URL / apply e-mail, derived ATS vendor, named contact with e-mail and phone, employer homepage and description, employment type, sector, position count, start date and the full ad body. Measured fill on the ad page: employer 100%, positions 100%, ad text 100%, org number 75%, apply link 75% (90% counting e-mail), contact name 55%, contact e-mail 35%, contact phone 35%. Billed as a second event. Note that any filter which can only be decided on the ad page (ATS vendor, summer job, org number, contact e-mail…) means every candidate has to be fetched before it can be judged — you are only billed for the rows that survive, but a rare filter will spend real run time.

## `maxItems` (type: `integer`):

Hard cap on the rows delivered and billed, across every search and slice. 0 = no cap (a full sweep of the live board is ~12,400 ads).

## `autoSliceCorpus` (type: `boolean`):

NAV's Elasticsearch refuses `from + size > 10000` (measured: `from=9900` is fine, `from=9901` returns HTTP 400 'Pagination depth exceeds maximum allowed window'). The live board is ~12,470 ads, so a full sweep is impossible in one query. With this on, the actor slices the search by county (largest slice OSLO = 2,185) and then by occupation if a slice is still too deep, sweeps abroad-located ads through the country facet, and deduplicates on ad UUID. Turn it off and the run stops at 10,000.

## `includeAdTextPlain` (type: `boolean`):

Adds `ad_text` — NAV's ad body with the HTML stripped. Only present when `Fetch ad details` is on.

## `includeAdTextHtml` (type: `boolean`):

Adds `ad_text_html`, the raw markup. Off by default because it can be 15 KB per row.

## `includeAiTags` (type: `boolean`):

NAV runs its own model over every ad and publishes the result for free: a one-paragraph `short_summary` (100% fill), skill tags (`ai_tags`, 99.8% fill) and an under-18 assessment. Keeping this on is the cheapest value density in the row.

## `includeEmbeddingVector` (type: `boolean`):

Adds `embedding_vector`, the 256-float `compositeAdVector` NAV publishes on the ad page for semantic matching. Off by default: it multiplies the size of every row. Requires `Fetch ad details`.

## `includeRawSource` (type: `boolean`):

Attaches `raw_source` (the search API's `_source`) and, when details are fetched, `raw_detail` (NAV's own `adData` object). For pipelines that would rather map the fields themselves.

## `flattenOutput` (type: `boolean`):

On (default): one flat row per ad, list fields joined with `; ` — the shape a CRM or a spreadsheet wants. Off: locations, occupations, categories, contacts, education, experience and languages come back as arrays and objects.

## `deduplicate` (type: `boolean`):

On (default): each ad is delivered and billed at most once, even though NAV's county facet counts multi-county ads twice (county slices sum to 13,008 against a 12,460 corpus). Turn it off only if you want each of several search URLs to return its own copy; ads are never duplicated inside one search either way.

## `onlyNewSince` (type: `string`):

ISO date or datetime. Drops anything published earlier — the simple form of monitoring. For a running watch that remembers what it has already seen, use `State key` instead.

## `stateKey` (type: `string`):

Give this run a name (e.g. `oslo-nurses`) and the actor stores every ad UUID it has delivered under that name in a NAMED key-value store. On the next run with the same key, ads already seen are dropped BEFORE delivery, so you only ever pay for genuinely new vacancies. Leave empty for a one-off scrape.

## `maxConcurrency` (type: `integer`):

How many ad pages to fetch in parallel when `Fetch ad details` is on. 8 is a courteous default against NAV; raise it only if you are sweeping tens of thousands of ads.

## `maxRequestRetries` (type: `integer`):

Keep this at 3 or more. NAV itself is stable (measured 20/20 on ad pages and 56/57 on searches), but the Apify proxy tunnel returns a transient `595` on roughly 2-3% of calls; the retry converts that to ~100%.

## `requestTimeoutSecs` (type: `integer`):

Per-request timeout. NAV answers a 100-row search in ~1.2 s through a datacenter proxy.

## `requestDelayMs` (type: `integer`):

Politeness pause between paged search requests.

## `proxyConfiguration` (type: `object`):

Apify DATACENTER proxies are enough — measured through the full escalation ladder: direct 200, datacenter 200, residential NO 200, residential US 200. NAV is not geo-blocked, so no country pin is needed. A fresh proxy session is minted on every retry.

## Actor input object example

```json
{
  "query": "",
  "counties": [],
  "countries": [],
  "occupationCategories": [],
  "publishedWithin": "now-7d",
  "sortBy": "published",
  "engagementTypes": [],
  "extent": [],
  "sector": [],
  "education": [],
  "experience": [],
  "workLanguage": [],
  "remote": [],
  "needDriversLicense": "",
  "under18": "",
  "hasSuperraskSoknad": "",
  "startUrls": [],
  "adUrls": [],
  "municipalities": [],
  "occupationSubCategories": [],
  "cities": [],
  "postalCodes": [],
  "postalCodePrefix": [],
  "publishedAfter": "",
  "publishedBefore": "",
  "expiresAfter": "",
  "expiresBefore": "",
  "applicationDueBefore": "",
  "titleIncludeAny": [],
  "titleExcludeAny": [],
  "descriptionIncludeAny": [],
  "employerInclude": [],
  "employerExclude": [],
  "excludeStaffingAgencies": false,
  "sourceInclude": [],
  "sourceExclude": [],
  "atsVendorInclude": [],
  "atsVendorExclude": [],
  "onlyDirectPosted": false,
  "isSummerJob": false,
  "minPositionCount": 0,
  "maxPositionCount": 0,
  "requireOrgnr": false,
  "requireContactEmail": false,
  "requireContactPhone": false,
  "requireApplyLink": false,
  "fetchDetails": false,
  "maxItems": 25,
  "autoSliceCorpus": true,
  "includeAdTextPlain": true,
  "includeAdTextHtml": false,
  "includeAiTags": true,
  "includeEmbeddingVector": false,
  "includeRawSource": false,
  "flattenOutput": true,
  "deduplicate": true,
  "onlyNewSince": "",
  "stateKey": "",
  "maxConcurrency": 8,
  "maxRequestRetries": 3,
  "requestTimeoutSecs": 45,
  "requestDelayMs": 200,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `jobs` (type: `string`):

Title, employer, county, municipality, occupation, employment type, dates and NAV's AI summary — plus organisation number, apply link, ATS vendor and named contact person when ad details are fetched.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "publishedWithin": "now-7d",
    "sortBy": "published",
    "maxItems": 25,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/arbeidsplassen-jobs-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "publishedWithin": "now-7d",
    "sortBy": "published",
    "maxItems": 25,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/arbeidsplassen-jobs-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "publishedWithin": "now-7d",
  "sortBy": "published",
  "maxItems": 25,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call scrapersdelight/arbeidsplassen-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/arbeidsplassen-jobs-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/EGfa1RY9jZo9tbsUV/builds/euwCIlG9hTJ3yTUNd/openapi.json
