# Pinpoint Jobs Monitor & Scraper (`cliqtomedia/pinpoint-jobs-monitor-scraper`) Actor

Export public Pinpoint job postings with pay details and application links. Compare complete snapshots to find new, changed and removed postings. Get JSON or CSV.

- **URL**: https://apify.com/cliqtomedia/pinpoint-jobs-monitor-scraper.md
- **Developed by:** [Cliqto Media](https://apify.com/cliqtomedia) (community)
- **Categories:** Jobs, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.35 / 1,000 posting saveds

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

Export job postings from public Pinpoint career boards. Compare complete snapshots to see new, changed and removed postings. Use the results for job boards, recruiting research and hiring alerts.

![Pinpoint postings and snapshot monitoring](https://api.apify.com/v2/key-value-stores/QHPhFgVCEyocIIabh/records/hero.png)

### Table of contents

- [What it does](#what-it-does)
- [Data you get](#data-you-get)
- [Quick start](#quick-start)
- [Input](#input)
- [Input examples](#input-examples)
- [Output example](#output-example)
- [Output fields](#output-fields)
- [Price](#price)
- [Use cases](#use-cases)
- [Schedules and monitoring](#schedules-and-monitoring)
- [API and integrations](#api-and-integrations)
- [Limits and partial results](#limits-and-partial-results)
- [Troubleshooting](#troubleshooting)
- [FAQ](#faq)
- [Related Actors](#related-actors)
- [Support](#support)

### What it does

Add one to five public boards on `pinpointhq.com`. The Actor reads their English `postings.json` feeds, cleans the text and saves matching postings. It needs no Pinpoint login, key or proxy.

In monitor mode, give it a complete previous snapshot. It compares publication IDs and content. New and changed postings can go to the dataset. Removed IDs go to the `CHANGES` report.

### Data you get

Each row is one job **publication**. One job can have several publications, with different titles or locations. Keep `board` and `posting_id` together as your row key. `job_id` groups publications for the same vacancy.

Rows include title, department, location, employment type, workplace type, public pay details, plain text and the publication URL. An absent source field is `null`. Employer names and publication dates are `null` because this JSON feed does not supply them.

### Quick start

1. Open **Input**.
2. Keep `careerUrls` as `https://workwithus.pinpointhq.com`, or add your employer's Pinpoint board.
3. Keep `mode` as `scrape` and `maxResults` as `30`.
4. Set a maximum run charge of `$0.20`, then click **Start**.
5. Open **Output** to view postings. Check `RUN_SUMMARY` for board status and saved row count.

The example board returned three postings during testing. Its live jobs can change. Export JSON, CSV or Excel from the dataset.

### Input

All fields are optional. An empty object uses the example board. Defaults below are runtime defaults; the UI prefills the same board. `previousSnapshot` has no default baseline. Unknown keys, wrong types and invalid URLs fail before the source scan.

| Key | Type | Default | Range or format | Effect | Recommendation |
| --- | --- | --- | --- | --- | --- |
| `careerUrls` | array of strings | example board above | 1–5 public HTTPS Pinpoint URLs | Select boards. Duplicate board roots become one request. | Start with one real employer board. |
| `query` | string | `""` | 0–200 characters | All space-separated words must appear in normalized job text, ignoring case. | Use a role or department. |
| `remoteOnly` | boolean | `false` | true / false | Keep remote workplace type or a location with “remote”. | Use for remote job lists. |
| `includeDescription` | boolean | `true` | true / false | Return plain text from description, responsibilities, skills and benefits. | Keep on if you search the description. |
| `maxResults` | integer | `30` | 1–30 | Limit saved matching postings across all boards. | Use 30 for a complete small-board export. |
| `mode` | string | `"scrape"` | `scrape` / `monitor` | Export current postings or compare snapshots. | Start with scrape. |
| `changesOnly` | boolean | `false` | true / false; monitor only | Save and bill only new or changed postings. | Turn on for repeat checks. |
| `previousSnapshot` | object | omitted | Complete SNAPSHOT from the same boards; version 1; up to 1,500 entries | Provide the baseline for monitor mode. | Omit on your first monitor run. |

Accepted URLs are board roots, `/jobs`, `/jobs.rss`, `/jobs.json` and `/postings.json`, with an optional locale prefix. They all select the board's **English** publications feed. Custom domains, login paths, query strings, URL passwords and other sites are not accepted.

Filters are combined. All query words and the remote filter must match. Query searches title, department, location, description, employment type, workplace type and pay text. If `includeDescription=false`, query does not search description. Changing this field does not change a posting's content fingerprint.

Filters and `maxResults` do not reduce the source scan. When the save limit stops output, the result is `partial` and `SNAPSHOT` is null. A filter with no matches is a valid complete run and can still return a full snapshot.

A snapshot is an object with `version:1`, `boards` (normalized roots) and `entries`. Each entry has `board`, `posting_id` and a 64-character SHA-256 `fingerprint`. Copy the complete object from the last accepted run. Do not make up fingerprints or mix board scopes.

### Input examples

#### Export a board

```json
{"careerUrls":["https://workwithus.pinpointhq.com"],"includeDescription":false,"maxResults":30}
```

This returns publication rows. The output example below comes from this board, with description switched off.

#### Filter one role

```json
{"careerUrls":["https://workwithus.pinpointhq.com"],"query":"Founding Legal Counsel","includeDescription":false,"maxResults":30}
```

This returned one posting in testing. It reads the full board and still has the start fee.

#### First monitoring run

```json
{"careerUrls":["https://workwithus.pinpointhq.com"],"mode":"monitor","changesOnly":true}
```

Without a baseline, all matching postings are `new`. For the next run, add the full non-null `SNAPSHOT` object as `previousSnapshot`. If the board has not changed, that repeat run saves zero rows. It still has the start fee.

### Output example

The fields below are real values from the example board. Description is null when switched off. Dates and live availability change.

```json
{
  "job_id": "https://workwithus.pinpointhq.com/jobs/562609",
  "posting_id": "559663",
  "title": "Founding Legal Counsel",
  "company": null,
  "department": "Operations",
  "employment_type": "Full Time",
  "location": "Remote",
  "workplace_type": "remote",
  "compensation": null,
  "reporting_to": "Chief Operating Officer",
  "published_at": null,
  "job_url": "https://workwithus.pinpointhq.com/en/postings/ce6c9e5c-a2d3-42b0-a01e-9edeae315b04",
  "source_feed": "https://workwithus.pinpointhq.com/postings.json",
  "source": "pinpoint",
  "scraped_at": "2026-10-03T23:50:21.151Z",
  "board": "https://workwithus.pinpointhq.com",
  "source_job_id": "562609",
  "employment_type_code": "full_time",
  "compensation_minimum": null,
  "compensation_maximum": null,
  "compensation_currency": null,
  "compensation_frequency": null,
  "deadline_at": null,
  "fingerprint": "dad145f28b5c1017484dc6b4a02cafc85f8f1175e7c7b4c93e18104b0660e8fe",
  "change": "current",
  "description": null
}
```

### Output fields

All fields are present. Null means the feed did not supply a usable value, except for description switched off and the two fields this feed never supplies.

| Field | Type | Can be null? | Example | Meaning |
| --- | --- | --- | --- | --- |
| `job_id` | string | no | https://workwithus.pinpointhq.com/jobs/562609 | Legacy URL for the parent vacancy; groups publications |
| `posting_id` | string | no | 559663 | Source publication ID; use with board for a unique key |
| `title` | string | no | Founding Legal Counsel | Publication title |
| `company` | string | yes | null | Always null: no employer name in this JSON feed |
| `department` | string | yes | Operations | Parent job department name |
| `employment_type` | string | yes | Full Time | Human-readable employment label |
| `location` | string | yes | Remote | Published location label |
| `workplace_type` | string | yes | remote | Source workplace code; no guess from location |
| `compensation` | string | yes | null | Public pay text; hidden pay stays null |
| `reporting_to` | string | yes | Chief Operating Officer | Reporting line published by the employer |
| `published_at` | string | yes | null | Always null: no publication date in this JSON feed |
| `description` | string | yes | Hello 👋 | Clean text from the four public content sections; null when off |
| `job_url` | string | no | https://workwithus.pinpointhq.com/en/postings/ce6c9e5c-a2d3-42b0-a01e-9edeae315b04 | Current publication page and application link |
| `source_feed` | string | no | https://workwithus.pinpointhq.com/postings.json | Public JSON feed URL |
| `source` | string | no | pinpoint | Source name |
| `scraped_at` | string | no | 2026-10-03T23:50:21.151Z | UTC fetch time in ISO 8601 format |
| `board` | string | no | https://workwithus.pinpointhq.com | Normalized board root |
| `source_job_id` | string | no | 562609 | Parent vacancy ID in the source |
| `employment_type_code` | string | yes | full_time | Source employment code |
| `compensation_minimum` | number | yes | null | Visible numeric pay lower bound |
| `compensation_maximum` | number | yes | null | Visible numeric pay upper bound |
| `compensation_currency` | string | yes | GBP | Visible ISO currency code |
| `compensation_frequency` | string | yes | year | Visible pay period |
| `deadline_at` | string | yes | null | Source closing date in ISO 8601 format, if given |
| `fingerprint` | string | no | 64 hex characters | SHA-256 of normalized source content; excludes fetch time |
| `change` | string | no | current / new / changed / unchanged | State in scrape or monitor mode |

#### Reports in the key-value store

| Key | Shape | What to check |
| --- | --- | --- |
| `OUTPUT` | object | `status`, `reason`, `boards`, saved `results` and `snapshotReady`. Results match dataset rows. |
| `RUN_SUMMARY` | object | `status`, `reason`, `rows`, `scannedPostings`, `matchedPostings`, `scanComplete`, `baselineAccepted`, `boards`, `counters`, `elapsedMs`, `sourceCommit`, `mode`, change count and `billing` (SDK pricing model, isPayPerEvent, maximum charge and per-event prices). |
| `SNAPSHOT` | object or null | Full unfiltered posting IDs and fingerprints when baselineAccepted is true. Otherwise null. |
| `CHANGES` | object | Run `status`, first-scan/comparison `baseline`, boolean `removalDetection` and array of board/posting_id/change entries. |

`boards` entries contain the board URL, status, nullable reason, posting count, invalid count and duplicate count. `counters` contains request attempts, retries and response bytes. Counts and elapsedMs are numbers; scan flags are booleans. A reason is null or a stable error/limit code. `sourceCommit` identifies the runtime version.

`CHANGES` covers the full scan, before filters. Removed IDs appear only after every board has been read correctly. A disappeared publication does not prove that its parent vacancy is closed: the employer may replace the publication. A partial scan cannot produce removal alerts or an accepted baseline.

### Price

Pay `$0.03` per run at 256 MB, plus `$0.00135` for each posting saved to the default dataset. The start fee also applies to empty, filtered, unchanged and unavailable-board runs. In `changesOnly` mode, skipped unchanged postings have no result fee. Removed IDs have no separate fee.

| Saved postings | Event cost |
| --- | ---: |
| 0 | $0.03 |
| 1 | $0.03135 |
| 10 | $0.04350 |
| 30 | $0.07050 |
| 100 or 1,000 in one run | Not supported; maximum 30 saved postings per run |

Platform usage is included in this PPE price for Store customers. Private owner runs pay their own platform usage and do not prove an actual customer payment. Apify shows the applied charges in the run. Set a maximum charge before running; `$0.20` is a safe starting cap. The minimum allowed maximum is `$0.04`.

Result fees apply to stored postings, including those saved before a partial scan or stop. A failed or interrupted write may leave a row without a matching report if the acknowledgement is lost. The Actor does not replay that write or promise a transaction across the dataset and reports.

### Use cases

| Task | Input | Useful fields |
| --- | --- | --- |
| Fill a small job board | careerUrls, scrape | title, job_url, location, description |
| Check remote hiring | remoteOnly, query | workplace_type, department, title |
| Track published pay | careerUrls | compensation, compensation_minimum, compensation_maximum |
| Track changes | monitor, previousSnapshot, changesOnly | posting_id, fingerprint, change and CHANGES |

### Schedules and monitoring

You can create an Apify Task and Schedule yourself. Start with a daily check. Pass the last complete snapshot into the next run through your own workflow. This Actor does **not** keep a shared baseline between runs: the schedule alone will not supply `previousSnapshot`.

Keep the old baseline if `baselineAccepted=false` or SNAPSHOT is null. For boards with more matching publications than the output cap, use filters to keep saved output within 30. The snapshot still covers the full feed if the run is complete.

### API and integrations

Start a run with the [Apify run API](https://docs.apify.com/api/v2/actors-runs-post):

```text
POST https://api.apify.com/v2/acts/cliqtomedia~pinpoint-jobs-monitor-scraper/runs?memory=256&timeout=75&maxTotalChargeUsd=0.20
Authorization: Bearer YOUR_APIFY_TOKEN
Content-Type: application/json
```

Use the same JSON input as in Console. Poll the run to a terminal status. Read rows from `defaultDatasetId` and reports from `defaultKeyValueStoreId`. Check RUN_SUMMARY before treating the scan as complete. Keep tokens in your server or automation tool, not a public web page.

Make, Zapier and n8n can run the Actor and read its dataset. An Apify MCP client can call it with the same input; your client must have access to the Actor. These integrations are not created by the Actor. See [Apify integrations](https://docs.apify.com/platform/integrations).

### Limits and partial results

The Actor accepts 5 boards and saves up to 30 postings. A live five-board test read 32 postings and saved 30, then correctly returned partial with no accepted snapshot. Each board feed is limited to 300 publications and 2 MiB. Total response data is limited to 10 MiB; there are at most 15 HTTP attempts. Runtime scan and writes stop after 55 seconds, with a 75-second Cloud timeout and 256 MB memory.

The public [Pinpoint JSON feed](https://developers.pinpointhq.com/docs/jobs-json-endpoint) supplies current public publications. There is no global employer search, custom-domain route, authenticated API, application data or historical archive here. Missing salary stays null. Employer feeds may contain demo or stale content; verify important openings on the source page.

- `complete`: all boards read and all eligible rows saved. Zero matches can be complete.
- `partial`: a board failed, an item was invalid, or a save/time/charge limit stopped output.
- `not_found`: all requested boards returned 404.
- `error`: invalid input, all source reads failed or output failed before a row was saved.

The dataset contains only saved posting rows. Errors and board status are in the reports. A platform SUCCEEDED run can still have `partial` or `not_found` in RUN_SUMMARY. A runtime error has a failed platform status where possible. Resume and resurrection of a run with existing rows are not supported; start a new run to avoid duplicate rows.

### Troubleshooting

| Symptom | Likely cause | Check and action |
| --- | --- | --- |
| Zero rows and complete | Board is empty, query has no match, or monitor found no change | Read scannedPostings, matchedPostings and CHANGES. Try an empty query. |
| not_found | Wrong or removed board | Open the board root and correct careerUrls. |
| partial with MAX_RESULTS | More than maxResults matched | Use a narrower filter or raise the limit, up to 30. Keep the old snapshot. |
| partial with source error | Timeout, access block or changed feed | Read the board reason and retry later. |
| error before scan | Invalid URL, type or snapshot scope | Use the exact input keys and matching board roots. |
| Snapshot is null | Scan or output was partial | Keep your last complete snapshot. |

### FAQ

#### Does it find every Pinpoint employer?

No. Supply known public board URLs. The Actor does not search for employers.

#### Why do two rows have the same job_id?

One vacancy can have multiple publications. Use board plus posting_id to identify a publication.

#### Does “removed” mean the job is closed?

It means that a publication ID disappeared from a complete scan. Verify the parent vacancy before making that claim.

#### Is an unchanged check free?

There is still a $0.03 start fee. No result fee applies to unchanged rows skipped with changesOnly.

#### Can I use a board URL with a locale path?

Yes, for accepted paths. The feed is normalized to English; this Actor does not export other translations.

#### Are my descriptions HTML?

No. They are plain text. Salary and workplace fields remain null when the source does not provide them.

### Related Actors

For other ATS sources, see [Greenhouse Jobs Monitor & Scraper](https://apify.com/cliqtomedia/greenhouse-jobs-monitor-scraper) and [Lever Jobs Scraper](https://apify.com/cliqtomedia/lever-jobs-scraper).

### Support

Use this Actor's Apify Issues tab. Include the run ID, board URL and expected result. Do not send tokens, cookies or private applicant data. For general platform questions, use [Apify support](https://apify.com/contact).

# Actor input Schema

## `careerUrls` (type: `array`):

One to five public Pinpoint boards. Roots, jobs.rss, jobs.json and postings.json URLs are accepted. All use the English postings feed. Custom domains are not supported.

## `query` (type: `string`):

All words must match title, department, location, description or other normalized job text. Up to 200 characters. Filters still read the full feed.

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

Keep roles marked remote or with remote in the location.

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

Return plain text from description, responsibilities, skills and benefits. When off, description is null and query does not search description.

## `maxResults` (type: `integer`):

Save at most 30 matching postings. The full feed is read. A truncated result has partial status and no accepted monitoring snapshot.

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

Scrape exports current postings. Monitor compares posting IDs and content with the supplied previous snapshot.

## `changesOnly` (type: `boolean`):

Monitor mode only. Unchanged postings are not saved or charged. Removed IDs are in CHANGES, not the dataset.

## `previousSnapshot` (type: `object`):

Optional SNAPSHOT JSON from a complete monitor run on exactly the same boards. Omit on the first run. Do not send null snapshots from partial runs. Maximum 1500 entries.

## Actor input object example

```json
{
  "careerUrls": [
    "https://workwithus.pinpointhq.com"
  ],
  "query": "",
  "remoteOnly": false,
  "includeDescription": true,
  "maxResults": 30,
  "mode": "scrape",
  "changesOnly": false
}
```

# Actor output Schema

## `jobs` (type: `string`):

No description

## `output` (type: `string`):

No description

## `summary` (type: `string`):

No description

## `snapshot` (type: `string`):

No description

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

No description

# 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 = {
    "careerUrls": [
        "https://workwithus.pinpointhq.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("cliqtomedia/pinpoint-jobs-monitor-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 = { "careerUrls": ["https://workwithus.pinpointhq.com"] }

# Run the Actor and wait for it to finish
run = client.actor("cliqtomedia/pinpoint-jobs-monitor-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 '{
  "careerUrls": [
    "https://workwithus.pinpointhq.com"
  ]
}' |
apify call cliqtomedia/pinpoint-jobs-monitor-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cliqtomedia/pinpoint-jobs-monitor-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/afNi8QkeWCWVSa3yE/builds/o9buwqfR6oagQsMOs/openapi.json
