# Job Board Snapshot Change Detector (`zinin/job-board-snapshot-change-detector`) Actor

Compare two buyer-authorized factual job-board snapshots and report observed additions, removals and changed fields.

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

## Pricing

from $0.85 / 1,000 observed job-board change delivereds

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?

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 Board Snapshot Change Detector

Compare two buyer-authorized inline job-board snapshots and receive deterministic observed `ADDED`, `REMOVED`, and `CHANGED` rows plus one comparison summary. The Actor never fetches a supplied URL and never interprets a difference as proof of hiring, closure, growth, or vacancy status.

![Two inline snapshots become bounded observed change rows and a free summary](https://api.apify.com/v2/key-value-stores/gaiLxhfvTUaqNaWuc/records/store-snapshot-diff-workflow.png)

### What you get

- **Explicit change rows.** Each row identifies one stable `postingKey`, the change type, selected changed fields, before/after facts when requested, and deterministic fingerprints.
- **One free comparison summary.** Counts, snapshot metadata, truncation status, scope notices, and a report fingerprint follow the change rows.
- **Reproducible output.** Normalized snapshots, field selection, byte-order sorting, and fingerprints produce the same result for the same factual input.
- **A closed factual contract.** Descriptions, application contents, candidate data, credentials, arbitrary prose, and unknown fields are rejected.
- **Delivery-first billing.** The complete ordered Dataset batch is stored before one `result-found` is confirmed for each delivered `CHANGE` row; the summary is not billed.

Use it to create auditable change feeds from recurring exports you are authorized to process, trigger a downstream review when selected facts change, or retain deterministic snapshot-to-snapshot evidence.

### Who it is for

- Recruiting operations teams comparing authorized weekly careers exports.
- Marketplace and job-board operators monitoring their own publication snapshots.
- Analysts who need exact observed differences without a labour-market or hiring inference.
- Automation builders feeding normalized change rows into Make, Zapier, n8n, a warehouse, or an alerting rule.

### Quick start

Run the public Task **Job Board Snapshot Changes — Synthetic Sample** (Task name `compare-synthetic-job-board-snapshots`, Task ID `bFzRlx83jNhhRadXJ`). Its explicitly synthetic snapshots deliver one added, one removed, and one changed row followed by one free summary. It performs no network request and uses no private data.

For your own comparison, replace both snapshots while keeping stable posting keys across observation times:

```json
{
  "previous": {
    "snapshotId": "authorized-export-previous",
    "observedAt": "2026-08-31T09:00:00.000Z",
    "sourceLabel": "Buyer-authorized weekly export",
    "postings": [
      {
        "postingKey": "role-101",
        "title": "Software Engineer",
        "department": "Engineering",
        "location": "Tbilisi",
        "workplaceType": "OFFICE"
      }
    ]
  },
  "current": {
    "snapshotId": "authorized-export-current",
    "observedAt": "2026-09-01T09:00:00.000Z",
    "sourceLabel": "Buyer-authorized weekly export",
    "postings": [
      {
        "postingKey": "role-101",
        "title": "Software Engineer",
        "department": "Engineering",
        "location": "Remote",
        "workplaceType": "REMOTE"
      }
    ]
  },
  "changeTypes": ["ADDED", "REMOVED", "CHANGED"],
  "changedFields": ["title", "department", "location", "workplaceType"],
  "includeBeforeAfter": true,
  "maxChanges": 100,
  "inputLabel": "weekly-authorized-diff",
  "acknowledgeObservedDiffOnly": true
}
```

This input produces one `CHANGED` row because `location` and `workplaceType` differ. A posting missing from the earlier snapshot is `ADDED`; one missing from the later snapshot is `REMOVED`.

### Input reference

| Field | Required | Limits | Meaning |
| --- | --- | --- | --- |
| `previous` | Yes | Closed object | Earlier snapshot with `snapshotId`, `observedAt`, and 0–1,000 `postings`. |
| `current` | Yes | Closed object | Later snapshot; its `observedAt` must be later and its ID must differ. |
| `changeTypes` | Yes | Unique subset of `ADDED`, `REMOVED`, `CHANGED` | Select which detected rows may be delivered. |
| `changedFields` | Yes | 0–10 unique values | Empty means every supported factual field; otherwise compare the selected fields only. |
| `includeBeforeAfter` | Yes | Boolean | Include selected changed facts, or return only field names and fingerprints. |
| `maxChanges` | Yes | 1–2,000 | Deterministic delivered-row ceiling; summary always discloses truncation. |
| `inputLabel` | No | Up to 80 safe characters | Non-sensitive correlation label copied only to the summary. |
| `acknowledgeObservedDiffOnly` | Yes | Must be `true` | Confirms that observed differences are not outcome or completeness claims. |

Each snapshot accepts an optional `sourceLabel` and closed posting rows. Every posting requires `postingKey` and `title`. Optional factual fields are `department`, `division`, `location`, `employmentType`, `workplaceType`, `compensation`, `publishedAt`, `deadlineAt`, and `canonicalUrl`.

Compensation, when supplied, is one complete object with integer-minor-unit `minimum` and `maximum`, uppercase three-letter `currency`, and `period` from `HOUR`, `DAY`, `WEEK`, `MONTH`, or `YEAR`. The Actor performs no currency, tax, purchasing-power, or period conversion. `canonicalUrl` is an inert HTTPS output fact and is never fetched.

### How it works

1. The Actor validates the entire closed input and its 2,097,152-byte ceiling before Dataset or billing access.
2. It normalizes safe text and strict UTC timestamps, rejects duplicate posting keys within either snapshot, and requires the current observation time to be later.
3. Exact `postingKey` identity joins rows. Missing earlier/current keys become `ADDED`/`REMOVED`; selected factual differences become `CHANGED`.
4. Change types follow the fixed order `ADDED`, `REMOVED`, `CHANGED`; posting keys use deterministic byte ordering.
5. The Actor applies `maxChanges`, builds the summary, checks the complete output against an 8,388,608-byte ceiling, and writes one ordered Dataset batch.
6. Only after that write succeeds does the Actor confirm one named `result-found` per delivered `CHANGE` row. The summary remains free.

There is no crawler, source API, proxy, browser, geocoder, classifier, external Dataset, or model call.

### Output Dataset

![Three paid change rows and one free summary cross a delivery-first billing boundary](https://api.apify.com/v2/key-value-stores/gaiLxhfvTUaqNaWuc/records/store-change-dataset-contract.png)

The default Dataset contains zero or more `CHANGE` rows followed by exactly one `COMPARISON_SUMMARY`. A change example from the synthetic public Task is:

```json
{
  "schemaVersion": "1.0",
  "rowType": "CHANGE",
  "comparisonId": "e052eae82389689eff3c1a6aca70dba5eea197dbe1499d236de75de95008e65a",
  "position": 1,
  "changeType": "ADDED",
  "postingKey": "synthetic-added",
  "previousFingerprint": null,
  "currentFingerprint": "e13b4fc996a11b0881b6b2d13cd2fefe9527f4e9e8fb5a26ba7a61f053d68b20",
  "changedFieldNames": ["title", "department", "location"],
  "before": null,
  "after": {
    "title": "Brand Designer",
    "department": "Marketing",
    "location": "Tbilisi"
  },
  "previousSnapshotId": "synthetic-jobs-previous",
  "currentSnapshotId": "synthetic-jobs-current",
  "observedDiffOnly": true
}
```

The final summary reports both detected and delivered counts for all three change types, unchanged rows, selected fields/types, previous/current snapshot fingerprints, truncation, rights/scope notices, and one deterministic `reportFingerprint`. With no selected differences, a successful run returns only this free summary and charges zero `result-found` events.

### Row interpretation

- `comparisonId` binds both normalized snapshot identities, observation times, fingerprints, field/type selections, before/after mode, and row limit.
- `previousFingerprint` and `currentFingerprint` identify the full normalized posting facts on each side; one is `null` for added or removed rows.
- `changedFieldNames` lists selected facts present on an added/removed row or facts whose normalized values differ on a changed row.
- `before` and `after` include only those selected facts when `includeBeforeAfter=true`; fingerprints remain available when it is false.
- `observedDiffOnly=true` is a scope statement, not a claim that a source posting opened, closed, filled, expired, or remained available.

### Pricing

Pay Per Event has only the automatic Actor Start event and the primary repeatable `result-found` event. One `result-found` is one complete `CHANGE` row already present in the stored Dataset batch. The comparison summary is free.

| Tier | Actor Start | Each delivered change | Three-change Task total |
| --- | ---: | ---: | ---: |
| FREE | $0.009000 | $0.001000 | $0.012000 |
| BRONZE | $0.008550 | $0.000950 | $0.011400 |
| SILVER | $0.008100 | $0.000900 | $0.010800 |
| GOLD | $0.007650 | $0.000850 | $0.010200 |
| PLATINUM | $0.007380 | $0.000820 | $0.009840 |
| DIAMOND | $0.007200 | $0.000800 | $0.009600 |

The public Task sets `maxChanges=3` and caps FREE-tier spend at exactly `$0.012`: one start plus three changes. A valid zero-change comparison still has the automatic start charge but no `result-found`. Invalid input, pricing/budget mismatch, or failed Dataset delivery creates no `result-found`.

### API and automation

Start the public synthetic Task and wait for a terminal status:

```bash
curl -X POST \
  'https://api.apify.com/v2/actor-tasks/bFzRlx83jNhhRadXJ/runs?token=APIFY_TOKEN&waitForFinish=180'
```

Read `defaultDatasetId` from the terminal Run, then fetch `/v2/datasets/DATASET_ID/items?clean=true`. In Make, Zapier, or n8n, branch on `SUCCEEDED` before consuming rows and route by `rowType` and `changeType`. Use the summary's `comparisonId`, counts, and `truncated` flag as the control record.

Before retrying a client timeout, inspect the original Run and Dataset. A successful batch may already have produced paid changes even if the client did not receive the terminal response.

### Source rights and attribution

This Actor reads only facts supplied inline by the buyer. You must own them or have authorization to process them and remain responsible for the original source's terms, retention rules, and downstream use. `sourceLabel` and `canonicalUrl` are provenance facts only; they imply no affiliation and grant no licence to source-board or employer content.

The Actor deliberately accepts bounded publication facts rather than descriptions, requirements prose, application-routing data, candidate records, or credentials. It does not discover a source, bypass access controls, scrape a website, or validate the buyer's completeness declaration.

### Security and data handling

- Runtime permission is `LIMITED_PERMISSIONS`; memory is 128 MB and timeout is 180 seconds.
- Input rejects unknown keys, accessors, unsafe control/format characters, malformed identifiers, invalid dates, duplicate keys, unsafe URLs, formula-leading text, non-finite numbers, and incomplete compensation ranges.
- The Actor makes zero network requests and accepts no token, cookie, proxy, external Dataset ID, Run ID, or arbitrary endpoint.
- Output uses closed Dataset schemas and contains only normalized facts, fingerprints, counts, and explicit scope/rights notices.
- Apify retains the original run INPUT in the run key-value store according to account storage and retention settings. `includeBeforeAfter=false` reduces Dataset disclosure but does not erase stored input.
- No automatic retry occurs after an uncertain paid delivery. Inspect the original Run before starting another comparison.

### Hard limits

| Boundary | Limit |
| --- | ---: |
| Memory / timeout | 128 MB / 180 seconds |
| Encoded input | 2,097,152 bytes |
| Postings per snapshot | 1,000 |
| Delivered changes | 2,000 |
| Encoded complete output batch | 8,388,608 bytes |
| Network, proxy, browser, model calls | 0 |

Snapshot IDs are 1–64 safe ASCII characters; posting keys are 1–160; titles are at most 300; source labels and the optional input label are at most 80. The Actor rejects the complete run when a hard bound is exceeded instead of returning a silently incomplete claim. Only explicit `maxChanges` truncation succeeds, and the summary discloses it.

### Limits and interpretation

- A missing posting means only that its key is absent from the supplied current snapshot. It does not prove closure, deletion, expiry, or a hiring outcome.
- An added posting is not proof of company growth, and a changed fact is not proof that a person or source intentionally edited it.
- Snapshot completeness, observation timing, identity stability, and factual accuracy remain buyer/source responsibilities.
- The Actor performs exact field comparison after normalization; it does not infer semantic equivalence, title seniority, remote status, location identity, or compensation comparability.
- `maxChanges` can omit detected rows from delivery. Always check `selectedChangeCount`, `deliveredChangeCount`, and `truncated` in the summary.
- Fingerprints support repeatability and change detection; they are not signatures from the source or evidence of legal authenticity.

### Troubleshooting

**Why did the run report `input_invalid`?**

Confirm that the snapshots have different IDs, the current `observedAt` is later, posting keys are unique per snapshot, required selections are present, and `acknowledgeObservedDiffOnly=true`. Remove unknown fields and unsafe text.

**Why is an expected change missing?**

Check `changeTypes`, `changedFields`, stable posting keys, and `maxChanges`. If a field is not selected, its difference does not create a `CHANGED` row. Read the summary's detected/delivered counts and `truncated` flag.

**Why are `before` and `after` null?**

They are intentionally withheld when `includeBeforeAfter=false`, and one side is naturally null for added or removed rows. Fingerprints and changed field names remain.

**Why did the run report `budget_insufficient` or `pricing_misconfigured`?**

The cap could not fund the maximum selected change rows, or active pricing did not match the closed paid-delivery contract. Increase the cap for the intended `maxChanges` or inspect the Actor's current event pricing.

**Why is there only a summary row?**

No selected difference was delivered. The summary remains useful and free; only the automatic Actor Start event applies.

### FAQ

**Does this Actor scrape job boards?** No. It compares only the two inline snapshots supplied in the run input.

**Can it prove that a vacancy closed or opened?** No. It reports observed differences between supplied snapshots without interpreting the cause.

**Is every Dataset row billed?** No. Only `CHANGE` rows receive `result-found`; the one `COMPARISON_SUMMARY` row is free.

**Can I compare only location and workplace mode?** Yes. Put `location` and `workplaceType` in `changedFields`; added/removed rows still reflect the selected facts.

**Are canonical URLs fetched?** No. Valid HTTPS URLs remain inert factual values used only in comparison and output.

**Can zero changes succeed?** Yes. The Dataset contains one free summary with zero delivered changes and no `result-found` charge.

# Actor input Schema

## `previous` (type: `object`):

Earlier buyer-authorized factual snapshot with up to 1,000 closed posting rows.

## `current` (type: `object`):

Later buyer-authorized factual snapshot with up to 1,000 closed posting rows.

## `changeTypes` (type: `array`):

Choose one or more literal differences to deliver.

## `changedFields` (type: `array`):

Leave empty to compare every supported factual field, or select an exact subset.

## `includeBeforeAfter` (type: `boolean`):

Include only the selected changed factual values; disable to return fingerprints and field names only.

## `maxChanges` (type: `integer`):

Retain the deterministic first 1–2,000 selected changes; summary reports truncation.

## `inputLabel` (type: `string`):

Optional non-sensitive label copied only to the summary.

## `acknowledgeObservedDiffOnly` (type: `boolean`):

Required: differences do not prove hiring, role closure, growth or snapshot completeness.

## Actor input object example

```json
{
  "previous": {
    "snapshotId": "jobs-ge-2026-08-31",
    "observedAt": "2026-08-31T09:00:00.000Z",
    "sourceLabel": "Authorized weekly export",
    "postings": [
      {
        "postingKey": "job-removed",
        "title": "Marketing Analyst",
        "department": "Marketing",
        "location": "Tbilisi",
        "canonicalUrl": "https://jobs.example.com/job-removed"
      },
      {
        "postingKey": "job-changed",
        "title": "Software Engineer",
        "department": "Engineering",
        "location": "Tbilisi",
        "workplaceType": "OFFICE",
        "canonicalUrl": "https://jobs.example.com/job-changed"
      },
      {
        "postingKey": "job-same",
        "title": "Product Manager",
        "department": "Product",
        "location": "Tbilisi",
        "canonicalUrl": "https://jobs.example.com/job-same"
      }
    ]
  },
  "current": {
    "snapshotId": "jobs-ge-2026-09-01",
    "observedAt": "2026-09-01T09:00:00.000Z",
    "sourceLabel": "Authorized weekly export",
    "postings": [
      {
        "postingKey": "job-added",
        "title": "Brand Designer",
        "department": "Marketing",
        "location": "Tbilisi",
        "canonicalUrl": "https://jobs.example.com/job-added"
      },
      {
        "postingKey": "job-changed",
        "title": "Software Engineer",
        "department": "Engineering",
        "location": "Remote",
        "workplaceType": "REMOTE",
        "canonicalUrl": "https://jobs.example.com/job-changed"
      },
      {
        "postingKey": "job-same",
        "title": "Product Manager",
        "department": "Product",
        "location": "Tbilisi",
        "canonicalUrl": "https://jobs.example.com/job-same"
      }
    ]
  },
  "changeTypes": [
    "ADDED",
    "REMOVED",
    "CHANGED"
  ],
  "changedFields": [],
  "includeBeforeAfter": true,
  "maxChanges": 2000,
  "inputLabel": "weekly-job-board-diff",
  "acknowledgeObservedDiffOnly": true
}
```

# Actor output Schema

## `changes` (type: `string`):

Up to 2,000 deterministic changes plus one uncharged comparison summary.

# 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 = {
    "previous": {
        "snapshotId": "jobs-ge-2026-08-31",
        "observedAt": "2026-08-31T09:00:00.000Z",
        "sourceLabel": "Authorized weekly export",
        "postings": [
            {
                "postingKey": "job-removed",
                "title": "Marketing Analyst",
                "department": "Marketing",
                "location": "Tbilisi",
                "canonicalUrl": "https://jobs.example.com/job-removed"
            },
            {
                "postingKey": "job-changed",
                "title": "Software Engineer",
                "department": "Engineering",
                "location": "Tbilisi",
                "workplaceType": "OFFICE",
                "canonicalUrl": "https://jobs.example.com/job-changed"
            },
            {
                "postingKey": "job-same",
                "title": "Product Manager",
                "department": "Product",
                "location": "Tbilisi",
                "canonicalUrl": "https://jobs.example.com/job-same"
            }
        ]
    },
    "current": {
        "snapshotId": "jobs-ge-2026-09-01",
        "observedAt": "2026-09-01T09:00:00.000Z",
        "sourceLabel": "Authorized weekly export",
        "postings": [
            {
                "postingKey": "job-added",
                "title": "Brand Designer",
                "department": "Marketing",
                "location": "Tbilisi",
                "canonicalUrl": "https://jobs.example.com/job-added"
            },
            {
                "postingKey": "job-changed",
                "title": "Software Engineer",
                "department": "Engineering",
                "location": "Remote",
                "workplaceType": "REMOTE",
                "canonicalUrl": "https://jobs.example.com/job-changed"
            },
            {
                "postingKey": "job-same",
                "title": "Product Manager",
                "department": "Product",
                "location": "Tbilisi",
                "canonicalUrl": "https://jobs.example.com/job-same"
            }
        ]
    },
    "changeTypes": [
        "ADDED",
        "REMOVED",
        "CHANGED"
    ],
    "changedFields": [],
    "includeBeforeAfter": true,
    "maxChanges": 2000,
    "inputLabel": "weekly-job-board-diff",
    "acknowledgeObservedDiffOnly": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("zinin/job-board-snapshot-change-detector").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 = {
    "previous": {
        "snapshotId": "jobs-ge-2026-08-31",
        "observedAt": "2026-08-31T09:00:00.000Z",
        "sourceLabel": "Authorized weekly export",
        "postings": [
            {
                "postingKey": "job-removed",
                "title": "Marketing Analyst",
                "department": "Marketing",
                "location": "Tbilisi",
                "canonicalUrl": "https://jobs.example.com/job-removed",
            },
            {
                "postingKey": "job-changed",
                "title": "Software Engineer",
                "department": "Engineering",
                "location": "Tbilisi",
                "workplaceType": "OFFICE",
                "canonicalUrl": "https://jobs.example.com/job-changed",
            },
            {
                "postingKey": "job-same",
                "title": "Product Manager",
                "department": "Product",
                "location": "Tbilisi",
                "canonicalUrl": "https://jobs.example.com/job-same",
            },
        ],
    },
    "current": {
        "snapshotId": "jobs-ge-2026-09-01",
        "observedAt": "2026-09-01T09:00:00.000Z",
        "sourceLabel": "Authorized weekly export",
        "postings": [
            {
                "postingKey": "job-added",
                "title": "Brand Designer",
                "department": "Marketing",
                "location": "Tbilisi",
                "canonicalUrl": "https://jobs.example.com/job-added",
            },
            {
                "postingKey": "job-changed",
                "title": "Software Engineer",
                "department": "Engineering",
                "location": "Remote",
                "workplaceType": "REMOTE",
                "canonicalUrl": "https://jobs.example.com/job-changed",
            },
            {
                "postingKey": "job-same",
                "title": "Product Manager",
                "department": "Product",
                "location": "Tbilisi",
                "canonicalUrl": "https://jobs.example.com/job-same",
            },
        ],
    },
    "changeTypes": [
        "ADDED",
        "REMOVED",
        "CHANGED",
    ],
    "changedFields": [],
    "includeBeforeAfter": True,
    "maxChanges": 2000,
    "inputLabel": "weekly-job-board-diff",
    "acknowledgeObservedDiffOnly": True,
}

# Run the Actor and wait for it to finish
run = client.actor("zinin/job-board-snapshot-change-detector").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 '{
  "previous": {
    "snapshotId": "jobs-ge-2026-08-31",
    "observedAt": "2026-08-31T09:00:00.000Z",
    "sourceLabel": "Authorized weekly export",
    "postings": [
      {
        "postingKey": "job-removed",
        "title": "Marketing Analyst",
        "department": "Marketing",
        "location": "Tbilisi",
        "canonicalUrl": "https://jobs.example.com/job-removed"
      },
      {
        "postingKey": "job-changed",
        "title": "Software Engineer",
        "department": "Engineering",
        "location": "Tbilisi",
        "workplaceType": "OFFICE",
        "canonicalUrl": "https://jobs.example.com/job-changed"
      },
      {
        "postingKey": "job-same",
        "title": "Product Manager",
        "department": "Product",
        "location": "Tbilisi",
        "canonicalUrl": "https://jobs.example.com/job-same"
      }
    ]
  },
  "current": {
    "snapshotId": "jobs-ge-2026-09-01",
    "observedAt": "2026-09-01T09:00:00.000Z",
    "sourceLabel": "Authorized weekly export",
    "postings": [
      {
        "postingKey": "job-added",
        "title": "Brand Designer",
        "department": "Marketing",
        "location": "Tbilisi",
        "canonicalUrl": "https://jobs.example.com/job-added"
      },
      {
        "postingKey": "job-changed",
        "title": "Software Engineer",
        "department": "Engineering",
        "location": "Remote",
        "workplaceType": "REMOTE",
        "canonicalUrl": "https://jobs.example.com/job-changed"
      },
      {
        "postingKey": "job-same",
        "title": "Product Manager",
        "department": "Product",
        "location": "Tbilisi",
        "canonicalUrl": "https://jobs.example.com/job-same"
      }
    ]
  },
  "changeTypes": [
    "ADDED",
    "REMOVED",
    "CHANGED"
  ],
  "changedFields": [],
  "includeBeforeAfter": true,
  "maxChanges": 2000,
  "inputLabel": "weekly-job-board-diff",
  "acknowledgeObservedDiffOnly": true
}' |
apify call zinin/job-board-snapshot-change-detector --silent --output-dataset

```

## MCP server setup

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

```

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/MaTd0IvW7XlY9pYko/builds/lKY6YWzhWWwCWZdYV/openapi.json
