# Employer Posting Evidence Verifier (`jyotirawattech/employer-posting-verifier`) Actor

Point-in-time evidence for supported employer-published job posts, with explicit unknowns.

- **URL**: https://apify.com/jyotirawattech/employer-posting-verifier.md
- **Developed by:** [Jyoti Rawat](https://apify.com/jyotirawattech) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## 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

## Employer Posting Verifier — local prototype

An original, evidence-first checker for **employer-published Greenhouse job-board posts**. It produces a small JSON record for each link: observed board status, stated location, short exact excerpts about work model and sponsorship, source, and check time. It does not assess a candidate, recommend a job, or submit an application.

**State:** local prototype, tested with fictional fixtures. As of the 29 September 2026 local validation, no live source run, Apify deployment, paid customer, or revenue had been established. A public repository by itself does not establish any of those outcomes.

### Try the fictional example

Python 3.9 or newer is sufficient; the runtime has no third-party dependencies. From this directory:

```sh
python3 -m employer_posting_verifier --fixture fixtures/fictional_batch.json
python3 -m unittest discover -s tests -v
```

The fixture contains five fictional cases: a published post, an ID absent from a complete board list, an incomplete board response, an inaccessible board, and conflicting source responses. All example companies and content are invented. Fixture mode makes **no network requests**.

The GitHub Actions workflow checks the core, Actor wrapper, and fixture CLI, and parses the Actor JSON schemas on Python 3.11 and 3.13. Those checks use fictional data and do not run live source checks.

The package also has standard Python packaging metadata in `setup.cfg`. An isolated editable install may be made with `python3 -m pip install -e .`. The module command above needs no installation.

### Supported input and result

The local core API is:

```python
from pathlib import Path
from employer_posting_verifier import FixtureClient, verify_batch

client = FixtureClient.from_path(Path("fixtures/fictional_batch.json"))
results = verify_batch(urls, client)
```

`verify_batch` accepts 1–20 URLs. The only supported formats in v0.1 are:

- `https://boards.greenhouse.io/{board_token}/jobs/{numeric_post_id}`
- `https://job-boards.greenhouse.io/{board_token}/jobs/{numeric_post_id}`

Custom employer domains and other applicant-tracking systems need separately reviewed adapters. Query strings, fragments, credentials in URLs, unusual ports, local addresses, and unsupported hosts are rejected before a request. Invalid inputs are identified by `input_index`; their original text is not echoed because it might contain secrets.

Each result has these fields:

| Field | Meaning |
| --- | --- |
| `input_index`, `input_url`, `canonical_url` | Position and accepted canonical URL; rejected inputs display `[rejected]`. |
| `provider`, `source_job_id` | Source family and stable board/post identifier. |
| `status`, `status_reason`, `status_source_url` | Observation and its exact basis. |
| `title`, `location` | Published fields or `Unknown`. |
| `work_model`, `sponsorship_wording` | Short excerpts of explicit source wording or `Unknown`; no eligibility inference. |
| `checked_at` | UTC observation time. |
| `error_code`, `duplicate_of` | Stable failure reason or the first input index reused for a duplicate. |

The CLI wraps the result list with `schema_version` and `mode`.

#### Status semantics

- **Open:** the post ID appeared in a complete successful response listing currently published jobs on that board. This does not guarantee that applications are being accepted or that a candidate is eligible.
- **Closed:** reserved for a future check that has reliable prior evidence the same post was Open and then observes an explicit closure. v0.1 does not emit this status.
- **Unknown:** source evidence was incomplete, inaccessible, malformed, conflicting, represented only a general-interest prospect post, or the ID was absent from the current published-jobs list. Absence does not prove the ID was ever published.

The tool records source wording. For example, “Visa sponsorship is not available” is shown as an excerpt; the tool does not convert missing language into a yes/no sponsorship decision. Greenhouse's [Job Board API documentation](https://docs.greenhouse.io/job-board.html) describes the public GET endpoint and published-job list used for this observation.

### Live mode boundary

The core has an optional `LiveGreenhouseClient`. No live request is made by default. A future authorized run must explicitly use `--live --source-rights-reviewed` (or instantiate `LiveGreenhouseClient(source_rights_reviewed=True)`). That flag records an operator acknowledgement; it is **not** evidence of legal permission or an approval to collect customer data.

Live requests are restricted to `boards-api.greenhouse.io`. The client checks all DNS answers for public addresses, connects to a vetted IP while validating the Greenhouse TLS certificate, blocks redirects, spaces requests by at least half a second, and limits input count, time, and response size. It sends no login, cookie, or candidate data. A failed request yields `Unknown`; it never becomes a false `Closed` result. Live execution and commercial use still need a source-rights decision, reliability review, and the workspace's action-time approvals.

Public GET access is not a blanket commercial republication license. The tool keeps only short relevant excerpts in its output and does not save full job descriptions. It does not use LinkedIn-derived pilot rows, CVs, the career application tracker, Apify logs, OAuth settings, or copied third-party code. [LinkedIn's User Agreement](https://www.linkedin.com/legal/user-agreement) restricts scraping and commercial use of its data; that source is outside this tool.

### Limits and next gates

- Only Greenhouse board posts with a numeric public post ID are supported.
- The output is an observation at `checked_at`, not continuous monitoring.
- Work-model and sponsorship excerpts can be missing or ambiguous. A human must read the linked employer source for consequential decisions.
- No source-rights clearance, buyer willingness to pay, Actor pricing, payout setup, hosted operation, or support promise has been established.
- A public release needs a license choice, independent staged-file review for secrets and private data, reproducible checks, and exact publication approval.

The architecture decision and rollback plan are in [ARCHITECTURE_DECISION.md](ARCHITECTURE_DECISION.md).

### Apify Actor candidate

The same core is packaged as an Apify Actor in `.actor/`, `apify_actor/`, and `Dockerfile`. Its **default run is the five-row fictional demonstration**. Every demo row says no employer website was checked. The Actor wrapper requires Python 3.11 or newer and `requirements-actor.txt`; the standalone core above still works with Python 3.9 and no SDK.

For a local SDK run after installing the requirements, use `python -m apify_actor`. Apify's local storage appears under `storage/`, which is excluded from Git. Wrapper tests use a fake Actor and do not contact an account:

```sh
python -m unittest apify_actor.test_main -v
```

Live Actor mode requires `mode=live_greenhouse`, 1–20 supported URLs, a `sourceRightsReviewed=true` input, the owner switch `ALLOW_LIVE_SOURCE_CHECKS=1`, and `ALLOWED_GREENHOUSE_BOARDS` containing each reviewed board token. An unlisted board is rejected before any request. The Actor processes and stores one result before fetching the next, and stops when its pay-per-event budget is exhausted. Duplicate inputs reuse cached source responses.

The intended paid event is `verified-result` for a live `Open` result. First-time absence and other `Unknown` rows do not request that custom charge. Apify can separately bill synthetic start and dataset-item events from its Console configuration. In pay-per-event mode, the Actor requires the owner-set `APIFY_DATASET_EVENT_REMOVED=1` switch and blocks a positive flat-priced dataset-item event. SDK 4.0.2 does not expose tier-priced events in that price map, so the exact Console configuration, source rights, and buyer evidence must still be reviewed before any paid deployment.

# Actor input Schema

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

The default uses fictional data and makes no website requests. Live mode is owner-gated.

## `urls` (type: `array`):

One to 20 public HTTPS Greenhouse board job URLs for live mode only; no candidate or private URLs.

## `sourceRightsReviewed` (type: `boolean`):

Confirm that you have reviewed the relevant source terms for this live batch. The Actor owner must also enable live checks.

## Actor input object example

```json
{
  "mode": "fictional_demo",
  "sourceRightsReviewed": false
}
```

# Actor output Schema

## `results` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("jyotirawattech/employer-posting-verifier").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("jyotirawattech/employer-posting-verifier").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 '{}' |
apify call jyotirawattech/employer-posting-verifier --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jyotirawattech/employer-posting-verifier"
        }
    }
}
```

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/3YNfDnCK7ktHlXaad/builds/vDDl3EbYnridJh3Ss/openapi.json
