# Job Alert — New Job Posting Watch (`zinin/job-alert`) Actor

Watch a saved jobs.ch, XING, or Boss.az search through built-in adapters and get only postings that are new since the last check, with no nested Actor run or second Actor charge.

- **URL**: https://apify.com/zinin/job-alert.md
- **Developed by:** [Tim Zinin](https://apify.com/zinin) (community)
- **Categories:** Jobs, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $8.50 / 1,000 change founds

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

## Job Alert — New Job Posting Watch

Watch a saved job search on jobs.ch, XING Jobs (DACH) or Boss.az and get only the
postings that are NEW since your last check, not the same 20 listings you already
scrolled past last week. The three board adapters run directly inside this Actor:
there is no child Actor run, buyer token hand-off, or second Actor charge.

### What you get

- A named "watch" (one board + that board's own search filter) that remembers what it
  has already shown you.
- Every scheduled run reports ONLY postings that were not there last time — title,
  company, location, employment type and the posting URL.
- Choose from 3 live boards: jobs.ch (Switzerland), XING Jobs (DACH region) and Boss.az
  (Azerbaijan).
- Run several independent watches from one Actor (e.g. one on jobs.ch for "marketing"
  in Zurich, one on XING for remote engineering roles) — each has its own memory.
- The very first run for a new watch tells you honestly that it's establishing a
  baseline, not hiding a "no new postings" run as if it found something.
- Runs on Apify: schedule it daily, monitor it, call it from the API, export to
  JSON/CSV/Excel or push straight into your own pipeline.

### How to run it

1. Click **Try for free** — no card needed on the free plan.
2. Pick a **Board to watch**, then paste that board's own filter into **Board filter
   (JSON)** — e.g. `{"keywords":["marketing"]}` for jobs.ch or
   `{"keyword_filter":"marketing"}` for XING/Boss.az. See the Input table below for
   every board's fields.
3. Hit **Start**. The first run creates the baseline; schedule it to run again (daily
   or weekly) to get a stream of only the NEW postings each time.

### Pricing

Pay-per-event. The Actor accepts only Apify's exact six-tier contract: FREE
`$0.005/$0.010`, BRONZE `$0.00475/$0.0095`, SILVER `$0.0045/$0.009`, GOLD
`$0.00425/$0.0085`, PLATINUM `$0.0041/$0.0082`, or DIAMOND
`$0.004/$0.008` per run start/result. A paid result is a NEW posting or a
baseline snapshot from a source-confirmed complete scan. Partial snapshots,
quiet checks, and error rows are free and never change the baseline. A malformed pricing contract, exhausted budget, or
unconfirmed charge fails closed and never advances the baseline.

### Input

| Field | Required | What it does |
|---|---|---|
| `board` | yes | Which board to watch: `jobs-ch-swiss`, `xing-jobs`, `boss-az`. |
| `filter` | yes | Search fields understood by this Actor's in-process adapter for the selected board. See "Board filter fields" below. |
| `watch_name` | no | Name for this watch, so you can run several independent watches. Defaults to a single watch if left empty. |
| `max_items` | no | Max NEW-posting rows delivered (and charged) per run, even if more were found (1-200, default 20). |

Migration: older API clients and saved Tasks may still send `baseline_key`. Runtime accepts that legacy alias only when `watch_name` is absent, preserving the same stored baseline. New integrations should use `watch_name`; an explicitly empty `watch_name` selects the documented fallback and never revives a legacy value.

#### Board filter fields

| Board | Required filter fields | Example |
|---|---|---|
| `jobs-ch-swiss` | `keywords` (array of search terms) | `{"keywords":["marketing"],"location":"Zurich"}` |
| `xing-jobs` | none (walks its own job sitemap) | `{"keyword_filter":"marketing","freshness_days":7}` |
| `boss-az` | none (walks its own job sitemap) | `{"keyword_filter":"marketing","freshness_days":14}` |

Any `max_items`/`max_pages` you set inside `filter` are capped at 10/2 regardless of the
board's own limits, to keep each watch check fast and cheap (`max_pages` only applies to
jobs.ch — the other two boards walk a sitemap and don't page).

```json
{
    "board": "jobs-ch-swiss",
    "filter": { "keywords": ["marketing"], "max_items": 3, "max_pages": 1 },
    "watch_name": "zurich-marketing"
}
```

### Output

Baseline-created row (first run for a watch — real output, live run 30.07.2026):

```json
{
    "baselineKey": "selftest-live-run",
    "board": "jobs-ch-swiss",
    "found": true,
    "isNew": false,
    "baselineCreated": true,
    "url": null,
    "jobId": null,
    "title": null,
    "company": null,
    "location": null,
    "country": null,
    "employmentType": null,
    "postedDate": null,
    "salaryRaw": null,
    "currentJobsCount": 3,
    "newJobsCount": null,
    "sourcePartial": false,
    "partialReason": null,
    "sourceStats": null,
    "error": "",
    "summary": "First check for \"selftest-live-run\" (jobs-ch-swiss) — baseline created with 3 current posting(s) matching the filter. Future runs report only NEW postings against this baseline.",
    "checkedAt": "2026-07-30T10:26:40.213Z"
}
```

New-posting row (real output, live run 30.07.2026 — detected via a controlled re-run
after the baseline's own stored copy of this posting was removed, to prove the diff
catches a real removal-then-reappearance rather than only a lucky live change):

```json
{
    "baselineKey": "selftest-live-run",
    "board": "jobs-ch-swiss",
    "found": true,
    "isNew": true,
    "baselineCreated": false,
    "url": "https://www.jobs.ch/en/vacancies/detail/bfa292a6-5a59-44e9-934b-8106a6454e29/",
    "jobId": "bfa292a6-5a59-44e9-934b-8106a6454e29",
    "title": "Stv. Leiter/in Lager",
    "company": "Plan B Personal & Marketing GmbH",
    "location": "Wil ZH",
    "country": "CH",
    "employmentType": "Permanent position",
    "postedDate": "2026-07-27T06:10:24.000Z",
    "salaryRaw": null,
    "currentJobsCount": null,
    "newJobsCount": 1,
    "sourcePartial": false,
    "partialReason": null,
    "sourceStats": null,
    "error": "",
    "summary": "New posting for \"selftest-live-run\" (jobs-ch-swiss): Stv. Leiter/in Lager — Plan B Personal & Marketing GmbH, Wil ZH.",
    "checkedAt": "2026-07-30T10:27:09.481Z"
}
```

| Field | Meaning |
|---|---|
| `baselineKey` | Which watch this row belongs to. |
| `board` | Which job board this watch scans with its built-in adapter. |
| `found` | `true` for a real result row (new posting or baseline-created); `false` for a notice/error row. |
| `isNew` | `true` only for an actual new-posting row. |
| `baselineCreated` | `true` only on the first-ever run for this watch. |
| `url`, `jobId`, `title`, `company`, `location`, `country`, `employmentType`, `postedDate`, `salaryRaw` | The posting's own fields, straight from the board. |
| `currentJobsCount` | Only set on the baseline-created row: how many postings matched at that moment. |
| `newJobsCount` | Total new postings detected this run (may exceed the rows actually delivered if capped by `max_items` or your remaining budget). |
| `sourcePartial` / `partialReason` / `sourceStats` | `true` on a free partial-evidence notice when the board could not confirm a complete snapshot. jobs.ch reports failed keyword scans; XING and Boss propagate numeric sitemap/detail failure and cap counters. Partial snapshots are never billed or written as a baseline. |
| `error` | Empty string when the check completed cleanly (including "nothing new"); non-empty only on a real problem. |
| `summary` | Human-readable one-liner. |

### Other tools we built

#### Related tools

Related tools for adjacent workflows in jobs and hiring.

| Actor | What it does |
|---|---|
| [Company Hiring Radar](https://apify.com/zinin/company-hiring-radar) | Pair it in the jobs and hiring workflow: Pull every open role a company is hiring for from its public job board (Greenhouse, Lever, Ashby) and turn... |
| [Job Postings Aggregator](https://apify.com/zinin/job-postings-aggregator) | Pair it in the jobs and hiring workflow: Pull every open role from a company's public applicant-tracking system (Greenhouse, Lever, Ashby) and... |
| [Boss.az Azerbaijan Jobs Scraper](https://apify.com/zinin/boss-az) | Pair it in the jobs and hiring workflow: Walk boss.az's own job sitemap and pull public job listings from Azerbaijan: title, employer, location,... |
| [jobs.ch Swiss Jobs Scraper](https://apify.com/zinin/jobs-ch-swiss) | Pair it in the jobs and hiring workflow: Search jobs.ch (Switzerland) by keyword and get public job listings: title, company, location, employment... |
| [Jobs.ge Georgia Jobs Scraper](https://apify.com/zinin/jobs-ge) | Pair it in the jobs and hiring workflow: Search Jobs.ge (Georgia, the country's oldest job board) and get public job listings: title, employer,... |

### FAQ / Limitations

**Can I watch several boards at once?** Not from a single watch — each `watch_name`
watches one board. Run several watches (different `watch_name` values) from the same
Actor, one per board, to cover more than one job market.

**What happens if I change my `filter` for an existing `watch_name`?** The watch keeps
comparing against whatever it last saw, so changing the filter can produce a burst of
"new" postings that simply weren't checked for under the old filter. Use a new
`watch_name` for an intentionally different watch.

If every jobs.ch keyword scan fails, the run returns `source_error` and does not create
or update a baseline. A present but malformed stored baseline is also a fatal integrity
error; it is never treated as a first run.

**What this is NOT.** This is not a job-application or ATS tool — it does not apply to
jobs or contact employers. It surfaces public posting data only.

Found a bug or need a custom watch (e.g. tracking when a posting gets pulled)? Issues on
the Actor's page.

# Actor input Schema

## `board` (type: `string`):

Which job board this Actor scans with its built-in adapter. Each board uses different filter fields; see the README Input section.

## `filter` (type: `object`):

Search fields for the selected in-process board adapter (for example, jobs.ch uses {"keywords":\["marketing"]}; XING/Boss.az use {"keyword\_filter":"marketing"}). See the README table. Adapter max\_items/max\_pages are capped at 10/2 to keep checks bounded.

## `watch_name` (type: `string`):

A name for THIS watch, so you can run several independent watches (different boards and/or filters) from one Actor without one overwriting another's memory of what's already been seen. Each name is scoped to YOUR OWN Apify account. The prefilled value is only there so this Actor's own daily test run has a stable, obviously-a-test name; replace it with your own watch name.

## `max_items` (type: `integer`):

Caps how many NEW-posting rows a single run will deliver and charge for, even if more were found.

## Actor input object example

```json
{
  "board": "jobs-ch-swiss",
  "filter": {
    "keywords": [
      "marketing"
    ],
    "max_items": 3,
    "max_pages": 1
  },
  "watch_name": "apify-daily-test",
  "max_items": 20
}
```

# Actor output Schema

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

API URL for the default dataset items produced by this run.

# 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 = {
    "board": "jobs-ch-swiss",
    "filter": {
        "keywords": [
            "marketing"
        ],
        "max_items": 3,
        "max_pages": 1
    },
    "watch_name": "apify-daily-test"
};

// Run the Actor and wait for it to finish
const run = await client.actor("zinin/job-alert").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 = {
    "board": "jobs-ch-swiss",
    "filter": {
        "keywords": ["marketing"],
        "max_items": 3,
        "max_pages": 1,
    },
    "watch_name": "apify-daily-test",
}

# Run the Actor and wait for it to finish
run = client.actor("zinin/job-alert").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 '{
  "board": "jobs-ch-swiss",
  "filter": {
    "keywords": [
      "marketing"
    ],
    "max_items": 3,
    "max_pages": 1
  },
  "watch_name": "apify-daily-test"
}' |
apify call zinin/job-alert --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,zinin/job-alert"
        }
    }
}

```

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/7LIOCEDpXcp7SQ2cb/builds/rpR4hfeI4rcZcjRwC/openapi.json
