# Teamtailor Jobs Scraper: Nordic & EU Career Sites (`arman-bd/teamtailor-jobs-scraper`) Actor

Scrape any Teamtailor careers site. role title, department, location, remote status, employment type and full description. No login, no proxy, no browser.

- **URL**: https://apify.com/arman-bd/teamtailor-jobs-scraper.md
- **Developed by:** [Arman Hossain](https://apify.com/arman-bd) (community)
- **Categories:** Jobs, Lead generation, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.74 / 1,000 job scrapeds

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/platform/actors/running/actors-in-store#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

## Teamtailor Jobs Scraper: Nordic & EU Career Sites

![Teamtailor Jobs: Every open role on a Teamtailor career site, department, location, remote status, employment type and full descriptions](https://api.apify.com/v2/key-value-stores/ZQOcNAOHrIgTacAmy/records/teamtailor-jobs-scraper.jpg)

**Teamtailor Jobs Scraper** pulls every open role from any **Teamtailor** career site, title, department, location, remote status, employment type, publish date, apply link and the full plain-text description.

Teamtailor powers the careers pages of 13,000+ employers, with unusually deep coverage of Nordic and wider European companies that never appear on US-centric boards. This Actor reads the public career site directly: **no browser, no proxies, no login, no API token.**

**Agent skill: [SKILL.md](https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/teamtailor-jobs-scraper.md)**

```
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/teamtailor-jobs-scraper.md
```

### What you get

| Output field | Meaning |
|---|---|
| `company` | Company name as Teamtailor publishes it |
| `jobId` | Teamtailor's numeric job ID, stable across runs |
| `title` | Job title |
| `department` | Team the role sits in, when the career site shows one |
| `location` | Location as displayed, falling back to the feed's structured address |
| `remoteStatus` | Hybrid, Fully Remote, On-site, when the employer filled the field in |
| `employmentType` | Schema.org value, e.g. `FULL_TIME`, `PART_TIME`, `CONTRACTOR` |
| `publishedAt` | Publish timestamp with timezone offset |
| `applyUrl` | Direct link to the job page |
| `bodyPlain` | Full description as clean plain text, HTML stripped, entities decoded |
| `scrapedAt` | Run timestamp |

A `RUN_SUMMARY` record in the key-value store holds per-run counts, the filters used, and any career site that failed.

### Common use cases

- **Map Nordic tech hiring.** Teamtailor's customer base is the best single source for Swedish, Norwegian, Danish and Finnish employers.
- **Build a Europe-focused job aggregator.** One scheduled run refreshes every listing you syndicate.
- **Track employer-brand pages for a competitor set.** Department plus publish date shows where headcount is going.
- **Recruiting intelligence.** See which teams a competitor is opening and in which cities.
- **Lead generation.** A company hiring for a role is a company buying for it.

### Quick start

Two career sites, everything they have:

```json
{
 "companies": ["polestar", "instabee"]
}
```

Engineering roles in Stockholm, capped:

```json
{
 "companies": ["polestar", "https://career.teamtailor.com/jobs"],
 "searchTerms": ["engineer", "developer"],
 "locationFilter": ["stockholm"],
 "maxJobsPerCompany": 50,
 "fetchJobDetails": true
}
```

Fast, lightweight sweep for change detection:

```json
{
 "companies": ["nordicwellness"],
 "fetchJobDetails": false
}
```

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `companies` | array | - | **Required.** Teamtailor subdomains (`polestar`) or full career-site URLs. Mixed input is fine, URLs are normalised automatically. |
| `searchTerms` | array | `[]` | Keep only titles containing one of these terms (case-insensitive). Empty = all. |
| `locationFilter` | array | `[]` | Keep only locations containing one of these terms. Empty = all. |
| `maxJobsPerCompany` | integer | `0` | Cap saved jobs per company **after** filtering, and stop listing pagination early. `0` = no limit. |
| `fetchJobDetails` | boolean | `true` | Fetch each saved job's own page for employment type and remote status. One extra request per saved job. |

Both filters combine with AND: a job must match at least one term in *each* non-empty filter. Set `maxJobsPerCompany` before turning `fetchJobDetails` on for a large board, the cap is applied first, so it also caps the number of detail requests.

### Output example

```json
{
 "company": "Polestar",
 "jobId": 8176726,
 "title": "Product Quality Expert",
 "department": "Quality & Logistics",
 "location": "Shanghai, China",
 "remoteStatus": null,
 "employmentType": "FULL_TIME",
 "publishedAt": "2026-08-06T03:51:46+02:00",
 "applyUrl": "https://polestar.teamtailor.com/jobs/8176726-product-quality-expert",
 "bodyPlain": "We are Polestar. We are detail obsessed, performance focused …",
 "scrapedAt": "2026-08-06T12:00:00.000Z"
}
```

### Finding a career-site subdomain

Open a company's careers page and look at the URL:

| URL you see | Subdomain |
|---|---|
| `polestar.teamtailor.com` | `polestar` |
| `polestar.teamtailor.com/jobs` | `polestar` |
| `career.teamtailor.com/jobs/8124573-…` | `career` |

You can paste the whole URL, the Actor extracts the subdomain itself. Some employers front their career site with a custom domain (`jobb.nordicwellness.se`); pass the `*.teamtailor.com` subdomain and the Actor follows the redirect to the branded domain automatically.

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/arman-bd~teamtailor-jobs-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
 "companies": ["polestar"],
 "searchTerms": ["engineer"],
 "maxJobsPerCompany": 25
 }'
```

### JavaScript example

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

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('arman-bd/teamtailor-jobs-scraper').call({
 companies: ['polestar', 'instabee'],
 searchTerms: ['engineer'],
 locationFilter: ['stockholm'],
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const job of items) console.log(`${job.company}, ${job.title} (${job.location})`);
```

### Notes

- **Bad career sites don't kill the run.** An unknown subdomain returns HTTP 404 and is recorded in `RUN_SUMMARY.failures`; the Actor only errors out if *every* site fails.
- **Transient errors are retried.** 429 and 5xx get three attempts with exponential backoff.
- **Boards larger than 100 roles are still complete.** Teamtailor's JSON feed is capped at 100 items server-side and exposes no cursor, so the paginated career listing is what enumerates the board; the feed is merged in for descriptions and structured addresses. A 104-role site returns 104 rows, not 100.
- **Descriptions come from whichever source has them.** Jobs inside the feed take their body from it for free; jobs past the 100-item cap take it from their own page when `fetchJobDetails` is on.
- **Remote status is read from the listing first.** The job page labels the field in English only, so on localised career sites the listing card is the reliable source.
- **Descriptions are decoded twice.** Recruiters paste already-encoded markup into Teamtailor's editor, so entities are decoded before tags are stripped and again afterwards. You get real text, not `&lt;p&gt;`.
- **Large payloads.** Career sites with long, image-heavy descriptions push the feed past 7 MB. It is fetched once per company and indexed by job ID, so memory stays flat regardless of how many companies you pass.
- **Public data only.** No authentication, no personal data, no access-control bypass.

### FAQ

**Do I need a Teamtailor API token?** No. Teamtailor's official API does require one, but the public career site does not, and that is what this Actor reads.

**Do I need a proxy?** No. Proxy configuration is not required to run this Actor.

**Why is `department` null?** Departments are optional and some career sites use a custom listing layout that omits them. Location always comes through, from the feed's structured address if not from the card.

**Why is `remoteStatus` null?** The field is optional in Teamtailor and most employers leave it unset. When it is set, it appears as `Hybrid`, `Fully Remote` or `On-site`.

**Does it include salary?** Only if the company writes it into the description, Teamtailor's public data has no structured salary field, and this Actor does not invent one.

**How many companies can I pass at once?** There is no hard cap. Each company costs one feed request plus one listing request per 20 jobs, so dozens per run is normal. Turn `fetchJobDetails` off to make large sweeps much cheaper.

**Can I get only new or changed jobs?** Run on a schedule and diff on `jobId` plus `publishedAt`.

**Can I integrate it with something else?** Yes, Apify API, client libraries, webhooks, scheduled runs, dataset exports (JSON/CSV/Excel) or MCP. Output is structured JSON.

# Actor input Schema

## `companies` (type: `array`):

Teamtailor subdomains or career-site URLs. The subdomain is the company slug in the career-site URL. for https://polestar.teamtailor.com the subdomain is 'polestar'. Full URLs are accepted and normalized automatically.

## `searchTerms` (type: `array`):

Keep only jobs whose title contains at least one of these terms (case-insensitive). Leave empty to keep every job.

## `locationFilter` (type: `array`):

Keep only jobs whose location contains at least one of these terms (case-insensitive). Leave empty for all locations.

## `maxJobsPerCompany` (type: `integer`):

Cap the number of jobs saved per company after filtering. Also stops listing pagination early. Set 0 for no limit.

## `fetchJobDetails` (type: `boolean`):

Fetch every saved job's own page to fill in employment type and remote status, which Teamtailor's feed does not publish. Costs one extra request per job. turn it off for a fast listing-only sweep.

## Actor input object example

```json
{
  "companies": [
    "polestar",
    "https://career.teamtailor.com/jobs"
  ],
  "searchTerms": [
    "engineer",
    "designer"
  ],
  "locationFilter": [
    "stockholm",
    "copenhagen"
  ],
  "maxJobsPerCompany": 0,
  "fetchJobDetails": true
}
```

# Actor output Schema

## `items` (type: `string`):

Every record the run produced.

## `runsummary` (type: `string`):

The RUN\_SUMMARY record from the run's key-value store.

# 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 = {
    "companies": [
        "polestar",
        "instabee"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("arman-bd/teamtailor-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 = { "companies": [
        "polestar",
        "instabee",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("arman-bd/teamtailor-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 '{
  "companies": [
    "polestar",
    "instabee"
  ]
}' |
apify call arman-bd/teamtailor-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,arman-bd/teamtailor-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/HWxgT8r6tNSX6ZuQQ/builds/MyVmMjbQ4tVSr6wFm/openapi.json
