# Platsbanken Scraper (`normdata/platsbanken-scraper`) Actor

Every open job on Sweden's Platsbanken, not just the first 2,100: filter by region, municipality, commuting area, occupation, job title, salary type and more. Full detail with employer org number, recruiter contacts, and coordinates, plus change tracking with Slack, Discord, or Telegram alerts.

- **URL**: https://apify.com/normdata/platsbanken-scraper.md
- **Developed by:** [Norm Data](https://apify.com/normdata) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.30 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

![Norm Data](https://i.ibb.co/rGbhM5Y8/Chat-GPT-Image-Sep-8-2026-02-20-50-PM.png)

## 🇸🇪 Platsbanken Jobs Scraper (Arbetsförmedlingen)

Search **Platsbanken**, Sweden's national job board run by Arbetsförmedlingen, with the same filters the site offers plus a few it doesn't, and get every matching job with full detail: description, employer and organisation number, recruiter contacts, salary type, requirements, exact coordinates, and the application link. No login, no account.

Turn on change tracking and schedule it, and each run tells you which jobs are new, updated, back again, or gone, with an optional alert to Slack, Discord, Telegram, or your own webhook.

### 🎯 Who uses it?

#### 🧑‍💼 Recruitment and staffing agencies

Spot new openings the day they are posted, in the regions and occupations you place, with the recruiter's name and phone number already on the row.

#### 📣 B2B sales teams

Companies that are hiring are companies that are growing. Every row carries the employer's Swedish organisation number, ready to join with company registers and your CRM.

#### 📊 Labour market analysts

Pull the full national job bank, not a 2,100 row slice, and break demand down by region, municipality, occupation, employment type, and working hours.

#### 🔔 Job seekers and career services

Watch a narrow search, such as nurse jobs in Göteborg and its commuting area, and get pinged only when something new appears.

### ✨ What it does

- **Search:** keywords, any of Sweden's 21 regions and 290 municipalities, 21 occupation fields, around 400 occupations, and exact job titles, all multiselect.
- **Commuting area:** widen a municipality to the neighbouring municipalities with the most daily commuters.
- **Job condition filters:** employment type, full-time or part-time, published within any number of days, driving licence required or not, education requirement, salary type (fixed, fixed plus variable, or commission), employment support programmes, no experience required, remote work possible, adapted workplace, trainee, abroad, and no fixed workplace.
- **External listings:** optionally include the ads Arbetsförmedlingen indexes from other job sites.
- **Look up:** resolve known job IDs directly.
- **Change tracking:** remembers each search between runs and tags jobs NEW, UPDATED (with the exact fields that changed), REAPPEARED, or EXPIRED, and flags reposts of an earlier ad.
- **Alerts:** a short summary with links to Slack, Discord, Telegram, or any webhook when something new shows up.

Missing source values are returned as `null`, never invented.

### Why this scraper

- **Every matching job, not the first 2,100.** Broad searches keep going until they reach the whole result set, up to the full national job bank of 40,000+ ads.
- **Recruiter contacts and org numbers on every row.** The named contact with role and phone, union representatives flagged separately, plus emails and phone numbers found in the description.
- **Filters nobody else exposes.** Commuting area, employment support programmes, education requirement, adapted workplace, and a driving licence filter that works both ways.
- **Change tracking that only charges for news.** Unchanged jobs are skipped, so a scheduled watch only writes, and only bills, what actually changed.
- **Stable, clean output.** Lists come back in a fixed order every run, so a job never shows up as "updated" just because the source reshuffled its contacts.

### How it compares

| Capability | This actor | Closest full-featured competitor | Other Platsbanken actors |
|---|:--:|:--:|:--:|
| **Results per search** | **every match** | 2,100 | 2,100 or less |
| Multiselect location and occupation | yes | yes | rare |
| **Commuting area expansion** | **yes** | no | no |
| **Employment support, education, and adapted workplace filters** | **yes** | no | no |
| Driving licence and experience filters | yes | yes | rare |
| **Listings from external job sites** | **yes** | no | no |
| Recruiter contacts and organisation number | yes | yes | some |
| Change tracking with repost detection | yes | yes | no |
| **Exact changed fields on updated jobs** | **yes** | no | no |
| Slack, Discord, Telegram, and webhook alerts | yes | yes | no |
| WhatsApp alerts | no | yes | no |
| Exact job title filter | yes | yes | rare |
| Salary type filter | yes | yes | no |
| Skills filter | no | yes | no |

### 📦 What data you get

| Entity | Useful fields |
| --- | --- |
| Job | Title, full description, occupation, employment type, working hours, duration, conditions, number of positions, salary type and description, workplace model. |
| Employer | Name, organisation number, workplace name, website, logo when the employer has one, and phone when published. |
| Location | Street, post code, city, municipality, region, country, latitude and longitude, every workplace for multi-site ads. |
| Contacts | The primary recruiter's name, role, phone, and email, every listed contact with union representatives flagged, plus emails and phone numbers found in the description. |
| Requirements | Experience, education level, driving licences, own car, languages, skills, and work experience, each marked required or merit. |
| Application | Application link, email, reference, and instructions, with the application deadline. |
| Change tracking | Change type, changed fields, repost flag with the original job ID, first seen, previously seen, and expired dates. |

Every record includes `scraped_at` (UTC). Download your dataset from Apify as CSV, JSON, Excel, or XML.

### 💡 Use cases

#### 🧑‍💼 Nurse openings in Göteborg and its commuting area

```json
{ "mode": "search", "keywords": ["sjuksköterska"], "municipalities": ["Göteborg"], "includeCommutingArea": true, "maxItems": 500 }
```

#### 📣 Every IT job published this week, as a lead list

```json
{ "mode": "search", "occupationFields": ["Data/IT"], "publishedWithinDays": 7 }
```

#### 🔔 A daily watch that alerts Slack on new warehouse jobs in Skåne

```json
{ "mode": "search", "keywords": ["lager"], "regions": ["Skåne län"], "trackChanges": true, "stateKey": "skane-lager", "slackWebhookUrl": "https://hooks.slack.com/services/..." }
```

#### 💼 Commission-based sales roles

```json
{ "mode": "search", "keywords": ["säljare"], "salaryTypes": ["variable"], "maxItems": 200 }
```

#### 🤝 Entry-level jobs open to newcomers

```json
{ "mode": "search", "noExperienceRequired": true, "employmentSupport": ["introductionJob", "startupEmployment"], "drivingLicense": "notRequired" }
```

#### 📊 The full national job bank

```json
{ "mode": "search", "source": "both" }
```

#### 🔎 Resolve known job IDs

```json
{ "mode": "lookup", "jobIds": ["31512287", "31365886"] }
```

### ⚙️ How the input is organised

**Maximum results** sits at the very top, since it applies no matter what you're doing. Leave it empty to collect every matching job. Below it, the form is split into seven numbered sections:

| Section | What it's for |
| --- | --- |
| **1 · What to do** | Search by filters or look up known job IDs. |
| **2 · Where and what** | Keywords, regions, municipalities, the commuting area option, occupation fields, occupations, and exact job titles. |
| **3 · Job conditions** | Employment type, working hours, publish date, driving licence, education, employment support, salary type, and the other job condition filters. |
| **4 · Listings and order** | Platsbanken ads, external job site listings, or both, and the sort order. |
| **5 · Look up** | Known job IDs to resolve directly. |
| **6 · Change tracking** | Turn on tracking, name the watch, and choose whether unchanged and expired jobs are written. |
| **7 · Notifications** | Webhook, Slack, Discord, and Telegram alerts. |

> **Apify Free plan:** every run is limited to a fixed 10-row sample. Upgrade your Apify plan to run your own settings.

### 🛡️ Limits & responsible use

This Actor reads only the public Platsbanken job board. It never signs in and never accesses anything gated behind an account.

Job ads can name a contact person. That is personal data under the GDPR, published by the employer so applicants can reach them. Use it for recruitment and job related contact only, and make sure your own processing has a lawful basis.

External job site listings only carry keyword, location, occupation, and date information, so the other filters can't be applied to them. When you combine those filters with both listing types, the external listings are skipped so every row still matches your filters.

Change tracking marks a job expired only when the run covered every matching job. A run capped by Maximum results never marks anything expired.

A job ID that doesn't resolve in Look up mode writes a row with an `error` field instead of failing the run.

### 📧 Contact

Need a scraper for a different site, or found something wrong with this one? norm.data.scrapers@gmail.com

### Local development

```bash
bun install
bun test
bun run src/main.ts
```

# Changelog

This Actor's version history is a separate document: https://apify.com/normdata/platsbanken-scraper/changelog.md

# Actor input Schema

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

Caps how many jobs this run collects. Leave empty to collect every matching job, even past 2,100 results.

## `mode` (type: `string`):

Search filters Sweden's national job board into a list. Look up resolves specific known job IDs directly.

## `keywords` (type: `array`):

Words to match in the job title, occupation, or description. Several keywords return jobs matching any of them. Swedish works best, e.g. "sjuksköterska", "lärare", "lagerarbetare".

## `regions` (type: `array`):

One or more of Sweden's 21 regions. Combined with municipalities, a job in any of them matches.

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

One or more of Sweden's 290 municipalities.

## `includeCommutingArea` (type: `boolean`):

Also search the 10 municipalities with the most daily commuters to or from each selected municipality, based on official commuting statistics.

## `occupationFields` (type: `array`):

Broad fields such as Data/IT, Hälso- och sjukvård, or Bygg och anläggning.

## `occupationGroups` (type: `array`):

Specific occupations, around 400 official categories.

## `jobTitles` (type: `array`):

Specific job titles in Swedish, e.g. "Undersköterska" or "Mjukvaruutvecklare". A title also covers its official variants, so "Undersköterska" includes the home care and hospital variants.

## `employmentTypes` (type: `array`):

One or more employment forms.

## `workExtent` (type: `string`):

Full-time or part-time only.

## `publishedWithinDays` (type: `integer`):

Only jobs published in the last N days. Any number of days, not just the site's own 1, 7, or 30.

## `drivingLicense` (type: `string`):

Filter on whether the job requires a driving licence.

## `education` (type: `string`):

Whether the occupation usually requires upper secondary education or higher.

## `employmentSupport` (type: `array`):

Jobs open to Sweden's subsidised employment programmes. Selecting both returns jobs in either.

## `salaryTypes` (type: `array`):

Keep only jobs paid this way, e.g. commission-based sales roles. Checked on each job's full detail.

## `noExperienceRequired` (type: `boolean`):

Only jobs that accept applicants without work experience.

## `remoteWork` (type: `boolean`):

Only jobs where the workplace allows remote work in some form.

## `openForAll` (type: `boolean`):

Only employers willing to adapt the workplace for applicants with special needs, a disability, or who are new in Sweden.

## `traineeOnly` (type: `boolean`):

Only trainee programmes.

## `abroadOnly` (type: `boolean`):

Only jobs located outside Sweden.

## `unspecifiedWorkplaceOnly` (type: `boolean`):

Only jobs without a fixed workplace in Sweden, often remote or travelling roles.

## `source` (type: `string`):

Platsbanken ads carry the full detail. External listings are ads Arbetsförmedlingen indexes from other job sites, with a shorter profile and a link to the original.

## `sort` (type: `string`):

Order of results. Past 2,100 results, jobs come back newest first.

## `jobIds` (type: `array`):

Known Platsbanken job IDs, e.g. 31512287, or external listing IDs. Unmatched IDs come back as an error row.

## `trackChanges` (type: `boolean`):

Remember this search between runs and tag every job as NEW, UPDATED, REAPPEARED, or EXPIRED, with the exact fields that changed and repost detection. Unchanged jobs are skipped, so scheduled runs only pay for what is new.

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

Optional name for this tracked search. Leave empty to derive one from the filters. Use the same name across runs, and different names for different watches.

## `emitUnchanged` (type: `boolean`):

Write every job each run, tagged UNCHANGED when nothing changed.

## `emitExpired` (type: `boolean`):

Write a short row for each job that disappeared since the last run. Only happens when the run covered every matching job.

## `webhookUrl` (type: `string`):

POSTs a JSON summary with the new and changed jobs to this https URL after each run.

## `slackWebhookUrl` (type: `string`):

Posts a short summary with links to a Slack channel.

## `discordWebhookUrl` (type: `string`):

Posts a short summary with links to a Discord channel.

## `telegramBotToken` (type: `string`):

Token of your own Telegram bot, used together with the chat ID.

## `telegramChatId` (type: `string`):

Chat or channel the bot posts the summary to.

## Actor input object example

```json
{
  "maxItems": 10,
  "mode": "search",
  "keywords": [
    "utvecklare"
  ],
  "includeCommutingArea": false,
  "workExtent": "any",
  "drivingLicense": "any",
  "education": "any",
  "noExperienceRequired": false,
  "remoteWork": false,
  "openForAll": false,
  "traineeOnly": false,
  "abroadOnly": false,
  "unspecifiedWorkplaceOnly": false,
  "source": "platsbanken",
  "sort": "newest",
  "trackChanges": false,
  "emitUnchanged": false,
  "emitExpired": true
}
```

# Actor output Schema

## `results` (type: `string`):

One dataset row per job matched by search or looked up by ID. With change tracking on, only new, updated, reappeared, and expired jobs are written.

# 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 = {
    "maxItems": 10,
    "mode": "search",
    "keywords": [
        "utvecklare"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("normdata/platsbanken-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 = {
    "maxItems": 10,
    "mode": "search",
    "keywords": ["utvecklare"],
}

# Run the Actor and wait for it to finish
run = client.actor("normdata/platsbanken-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 '{
  "maxItems": 10,
  "mode": "search",
  "keywords": [
    "utvecklare"
  ]
}' |
apify call normdata/platsbanken-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,normdata/platsbanken-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/adMxE0pItlsfGaUpa/builds/GTnlLAfLkoqJMT3JR/openapi.json
