# Arbeitnow Jobs Scraper - Germany & Visa Sponsorship (`dami_studio/arbeitnow-jobs-scraper`) Actor

Scrape Arbeitnow job listings across Germany and Europe: title, company, location, remote flag, the board's own visa-sponsorship flag, job types, tags, full description, listing date and apply URL. Optional lookups add the real publication date, expiry and the employer's own application link.

- **URL**: https://apify.com/dami\_studio/arbeitnow-jobs-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.90 / 1,000 job 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?

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

## Arbeitnow Jobs Scraper

Pulls job postings from Arbeitnow, the German job board whose whole pitch is visa sponsorship and
English-speaking roles. You get the title, company, location, the board's remote flag, its
visa-sponsorship flag, job types, tags, the full description and a link that goes straight to the
employer's application page.

The visa flag is the reason most people come here, so start with the honest version of it: it is
Arbeitnow's own flag, not our inference. When this was written the board was flagging **242
postings** for sponsorship out of roughly 7,500 live listings. Every row this actor returns carries
`visaSponsorship` as `true` or `false`, and you can filter down to only the sponsored ones.

### What this actually is

Arbeitnow is a job board, not an archive. It holds **about 7,500 postings covering roughly the last
week** and refreshes hourly. That shapes everything below:

- You can pull the newest 50 postings in about five seconds.
- You can pull 2,000 of them in about five minutes.
- You cannot pull last month. It is not there to pull.

Most of the postings are in Germany. Austria, Switzerland, the Netherlands and the rest of the EU
show up, and the occasional fully-remote role from further out, but if you are looking for a
Germany-and-neighbours feed this is the right board and if you are looking for US jobs it is not.

### What a row looks like

A real one, trimmed:

```json
{
  "jobId": "infrastructure-engineer-deployments-munich-392732",
  "title": "Infrastructure Engineer - Deployments (d/f/m)",
  "companyName": "Arculus",
  "companyUrl": "https://www.arbeitnow.com/jobs/companies/arculus",
  "jobUrl": "https://www.arbeitnow.com/jobs/companies/arculus/infrastructure-engineer-deployments-munich-392732",
  "applyUrl": "https://www.arbeitnow.com/jobs/companies/arculus/infrastructure-engineer-deployments-munich-392732/apply",
  "employerApplyUrl": "https://arculus.jobs.personio.de/job/2763879",
  "atsProvider": "Personio",
  "location": "Munich",
  "city": "Munich",
  "region": "BAVARIA",
  "countryCode": "DE",
  "isRemote": false,
  "visaSponsorship": true,
  "jobTypes": ["Experienced", "Permanent", "Full time"],
  "employmentType": "FULL_TIME",
  "tags": ["Infrastructure"],
  "salaryMin": null,
  "salaryMax": null,
  "postedAt": "2026-08-21T17:27:58+02:00",
  "listedAt": "2026-09-19T17:09:53.000Z",
  "validThrough": "2026-10-31T19:09:53+01:00",
  "descriptionText": "Responsibilities. As an Infrastructure Engineer, you will be r...",
  "descriptionHtml": "<p>Responsibilities</p>..."
}
```

That is a real row, and look at its two dates: the employer published it on **21 August**, the board
listed it on **19 September**. Twenty-nine days apart. More on that below.

`employerApplyUrl`, `atsProvider`, `employmentType`, `postedAt`, `validThrough` and `postalCode`
only appear when you switch on the two optional lookups described further down. Everything else is
there on every row.

### Two dates, and they are not the same date

This one trips people up, so it gets its own heading.

- **`listedAt`** is when Arbeitnow picked the posting up.
- **`postedAt`** is when the employer actually published it.

They can be weeks apart. One posting measured here was published on 30 July and listed on 19
September, a gap of seven weeks. If you are deduplicating against your own database or reporting on
hiring velocity, `postedAt` is the one you want, and it only arrives when **Read each job page** is
on.

The **Listed within** filter works on `listedAt`, because that is the only date available before
the job pages are read.

### Input

Set `maxItems` on its own and you get the newest postings. Add anything else and you narrow it.

```json
{
  "maxItems": 200,
  "visaSponsorshipOnly": true,
  "searchQueries": ["software engineer", "data analyst"],
  "location": "Berlin",
  "postedWithin": "week",
  "includeJobDetails": true,
  "resolveApplyUrl": true
}
```

| Field | What it does |
|---|---|
| `maxItems` | How many postings to return. |
| `searchQueries` | Words that must appear in the title, company, location, tags or description. Every word in a phrase has to match. |
| `visaSponsorshipOnly` | Only postings the board flags for sponsorship. |
| `remoteOnly` | Only postings the board marks remote. Read the caveat below first. |
| `location` | Case-insensitive substring of the location text. |
| `jobTypes` | Partial match against the board's own type labels. |
| `postedWithin` | 24 hours, 3 days, a week or 2 weeks, measured on `listedAt`. |
| `maxPagesScanned` | How deep to dig when a filter is set. Default 10 pages, about 1,300 postings. |
| `includeDescription` | The posting text, on by default. Costs nothing extra. |
| `includeJobDetails` | Reads each job page for the real publication date, expiry, employment type, postal code and salary. |
| `resolveApplyUrl` | Follows the apply link to the employer's own page and records the ATS. |

#### Why some filters are ours and one is the board's

Arbeitnow's feed accepts exactly one filter: visa sponsorship. We checked the rest. Keyword,
location, remote, job type and tags were each sent and compared against a control fetched in the
same moment, and they come back byte-for-byte identical to the unfiltered feed. The board ignores
them.

So `visaSponsorshipOnly` is applied by Arbeitnow, and every other filter is applied here, after the
postings are read. **Postings dropped by our filters are never charged.** You pay for rows you
receive, not rows we looked at.

That is also why `maxPagesScanned` exists. A keyword that matches nothing would otherwise send the
run through all 7,500 postings to hand you an empty result. The default stops at 10 pages, the
newest ~1,300 postings, which is plenty for any keyword people actually search for. Raise it when
you are hunting something rare.

### The two optional lookups

Both are off by default, because both add one request per posting.

**Read each job page** (`includeJobDetails`) pulls the structured data off the posting's own page.
On a random sample of 14 postings it filled in:

| Field | Hit rate |
|---|---|
| `postedAt` (real publication date) | 14 of 14 |
| `validThrough` (expiry) | 14 of 14 |
| `employmentType` (normalised) | 14 of 14 |
| `postalCode` | 4 of 14 |
| `salaryMin` / `salaryMax` | **1 of 14** |

Read that salary row twice before you plan around it. Roughly one posting in fourteen publishes a
number. Most German employers do not print salaries, and no scraper can produce a figure the
employer never wrote down. When it is there you get the currency, the period and the range; the
rest of the time those fields are `null`, and `null` means "not published", not "zero".

**Follow the apply link** (`resolveApplyUrl`) follows Arbeitnow's apply route one hop and records
where it lands: `employerApplyUrl` plus `atsProvider` (Greenhouse, Lever, Personio, Workday and so
on). This resolved 15 of 15 on the last test run. It is the field you want if you are building an
application pipeline rather than a list.

### What this does not do

- **It does not reach back beyond the board.** Roughly a week, roughly 7,500 postings. No archive.
- **It does not invent a salary.** See the table above.
- **The remote flag is the board's, and it is sparse.** Around 5 postings in 100 carry it. It is
  also sometimes plain wrong in the employer's favour: one posting titled "AI Compiler Engineer"
  with the location "Remote-US, Canada" has the board's remote flag set to `false`. If remote
  matters to you, treat `isRemote` as a hint and read the location and the description too.
- **`location` is free text.** Employers type it themselves, so it ranges from `Stuttgart` to
  `Berlin, Berlin, Germany` to `Hamburg, Gotenstraße` to an empty string. We split out `city`,
  `region` and `countryCode` where the text supports it and leave them `null` where it does not,
  rather than guessing. Turning on **Read each job page** replaces the guess with the address
  Arbeitnow publishes.
- **The visa list is the current one.** It covers the postings the board is flagging now, not
  everything it has ever flagged. Older sponsored roles that have aged out of the feed are gone.
- **It does not log in anywhere**, and it does not need a key from you.
- **It does not filter by salary band**, because the underlying data is too thin to filter on.

### Speed and size, measured

All on real runs, at the default memory:

| Run | Rows | Time |
|---|---|---|
| Newest postings | 60 | 5 seconds |
| Keyword + both lookups on | 15 | 14 seconds |
| Visa-sponsorship only | 10 | 4 seconds |
| Large pull | 2,000 | 5 minutes |

A run that matches nothing finishes in about 22 seconds and returns a diagnostic row explaining
where the postings went: how many were read, how many each filter dropped, and whether it stopped
at the page limit. That row is free.

### What you pay for

**One charge per job returned.** That is the whole model.

Free, always:

- the sample row you get from an empty input
- every diagnostic row: no matches, a rate limit, a failure
- every posting a filter drops before it reaches you
- the description, whether you keep it or not
- both optional lookups add no charge of their own

The current rate is on the Pricing tab of this listing.

### FAQ

**Does Arbeitnow have a public API?**
Yes, and this actor uses it. It is free, it needs no key, and it is updated hourly. What it does not
give you is search, salary, the real publication date, an expiry date, a postal code or the
employer's application URL, which is most of what this actor adds on top of it.

**Can I get only jobs that sponsor a visa?**
Yes. Set `visaSponsorshipOnly` and you get the board's own sponsorship list. Leave it off and every
row still tells you `true` or `false`, so you can sort or split them yourself.

**Can I get only English-speaking jobs?**
Not as a filter. The board does not expose one. Most sponsored roles are advertised in English, so
`visaSponsorshipOnly` gets you close. Otherwise use `searchQueries` against the description text.

**Why did I get fewer jobs than I asked for?**
Either the board ran out of matching postings, or the run hit `maxPagesScanned`. The log says which,
in plain words, and so does the status message.

**Does it return duplicates?**
No. Postings are deduplicated by their slug within a run.

**How fresh is this?**
Arbeitnow refreshes hourly and the newest posting is usually under an hour old. The feed is ordered
newest first, so page one is the last few hours of listings.

**Can I run it on a schedule?**
Yes. A daily run with `postedWithin: "day"` gives you the last 24 hours and stops reading as soon as
it is past the window, so it does not re-read the whole board every morning.

**Do I need a proxy?**
No. Leave the proxy settings alone. The actor already paces itself, because this is a free public feed that asks not to be hammered.

**What happens if Arbeitnow rate-limits the run?**
It backs off, waits and tries again. If it still cannot read the feed, you get a
diagnostic row saying so and nothing is charged.

# Actor input Schema

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

Stop after this many postings. Set it on its own and you get the newest postings on the board; add any filter below and you get the newest that match.

Arbeitnow carries roughly the last week of listings, about 7,500 postings when this was measured, so asking for more than the board holds simply returns everything it has.

## `searchQueries` (type: `array`):

Words that must appear in the posting - title, company, location, tags or the description text. A posting matches if it contains every word in one of your phrases, so "data engineer" needs both words but "python" needs only the one. German and English both work, since the board carries both.

Worth knowing: Arbeitnow's feed has no search parameter, so this filter runs on our side after the postings are read. Anything that does not match is dropped and never charged - you only pay for the postings you get back.

## `visaSponsorshipOnly` (type: `boolean`):

Return only the postings Arbeitnow flags as offering visa sponsorship. This is the board's own flag, not a guess of ours, and it is the list behind its "Visa Sponsorship Jobs in Germany" page - 242 postings when this was last measured.

Leave it off and you still get the flag: every row carries visaSponsorship as true or false either way.

## `remoteOnly` (type: `boolean`):

Keep only postings Arbeitnow marks as remote. That flag is sparse - around 5 in 100 postings on a typical page - because most of this board is on-site work in Germany.

## `location` (type: `string`):

Keep only postings whose location text contains this, case-insensitive. "Berlin", "München", "Hamburg".

The location on Arbeitnow is whatever the employer typed, so it ranges from "Stuttgart" to "Berlin, Berlin, Germany" to an empty string. A plain city name is the reliable way to use this. Postings with no location at all cannot match and are dropped uncharged.

## `jobTypes` (type: `array`):

Keep only postings carrying one of these types. Arbeitnow's types come from the employer and are not a fixed list - the common ones are "Full time", "Part time", "Permanent", "Experienced", "Entry", "Intern", "Working student", "Contract" and "Fixed term", and some employers write theirs in German. Matching is partial and case-insensitive, so "student" catches "Working student".

## `postedWithin` (type: `string`):

Only postings Arbeitnow listed inside this window. Note this is the date the board picked the posting up, which is the only date available before the job page is read - turn on "Read each job page" below if you need the date the employer actually published it. The two can be weeks apart.

## `maxPagesScanned` (type: `integer`):

How many pages of the board to read through while looking for matches. Only applies when you have set a keyword, location, job type, remote or date filter - those are applied on our side, so a search that matches nothing would otherwise walk the whole board and still hand you nothing.

10 pages is roughly the newest 1,300 postings, about a day and a half. That is plenty for any keyword people actually search for - "engineer" alone matches a good share of page one. Raise it when you are after something rare and want the actor to dig further back; the board tops out around 7,500 postings in total, which is about 72 pages. Pulls with no filters ignore this setting and simply read until they have the number you asked for.

## `includeDescription` (type: `boolean`):

Keep the full posting text, as HTML and as plain text. It arrives in the same request as everything else, so this costs nothing extra and changes nothing about the charge - it only makes the rows bigger. Turn it off if you are feeding a spreadsheet.

## `includeJobDetails` (type: `boolean`):

Fetches each posting's own page for the things the feed leaves out: the date the employer published it, the date it expires, a normalised employment type, a postal code, and a salary where one is published.

Be realistic about the salary part. On a random sample of 14 postings, 14 had a real publication date and an expiry, 4 had a postal code, and 1 published a salary. Most German employers do not print a number. This also adds one request per posting, so a large run takes noticeably longer.

## `resolveApplyUrl` (type: `boolean`):

Every row already carries Arbeitnow's apply link. Turn this on and the actor follows it one hop to record where the application actually lands - the employer's own page on Greenhouse, Lever, Personio, Workday and so on - in employerApplyUrl, plus the system's name in atsProvider.

It is one small request per posting and it is what you want if you are building an application pipeline rather than a job list.

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

Optional. Leave this alone unless you need the run to go out through a particular network. Your own proxy servers are used exactly as given.

## Actor input object example

```json
{
  "maxItems": 50,
  "searchQueries": [
    "software engineer",
    "data analyst"
  ],
  "location": "Berlin",
  "jobTypes": [
    "Full time",
    "Permanent"
  ],
  "maxPagesScanned": 10,
  "includeDescription": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Every job posting this run returned.

# 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": 50,
    "searchQueries": [
        "software engineer"
    ],
    "visaSponsorshipOnly": false,
    "remoteOnly": false,
    "postedWithin": "",
    "maxPagesScanned": 10,
    "includeDescription": true,
    "includeJobDetails": false,
    "resolveApplyUrl": false,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/arbeitnow-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 = {
    "maxItems": 50,
    "searchQueries": ["software engineer"],
    "visaSponsorshipOnly": False,
    "remoteOnly": False,
    "postedWithin": "",
    "maxPagesScanned": 10,
    "includeDescription": True,
    "includeJobDetails": False,
    "resolveApplyUrl": False,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/arbeitnow-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 '{
  "maxItems": 50,
  "searchQueries": [
    "software engineer"
  ],
  "visaSponsorshipOnly": false,
  "remoteOnly": false,
  "postedWithin": "",
  "maxPagesScanned": 10,
  "includeDescription": true,
  "includeJobDetails": false,
  "resolveApplyUrl": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call dami_studio/arbeitnow-jobs-scraper --silent --output-dataset

```

## MCP server setup

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