Employer Posting Evidence Verifier avatar

Employer Posting Evidence Verifier

Pricing

Pay per usage

Go to Apify Store
Employer Posting Evidence Verifier

Employer Posting Evidence Verifier

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

Pricing

Pay per usage

Rating

0.0

(0)

Developer

Jyoti Rawat

Jyoti Rawat

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

9 days ago

Last modified

Categories

Share

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:

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:

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:

FieldMeaning
input_index, input_url, canonical_urlPosition and accepted canonical URL; rejected inputs display [rejected].
provider, source_job_idSource family and stable board/post identifier.
status, status_reason, status_source_urlObservation and its exact basis.
title, locationPublished fields or Unknown.
work_model, sponsorship_wordingShort excerpts of explicit source wording or Unknown; no eligibility inference.
checked_atUTC observation time.
error_code, duplicate_ofStable 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 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 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.

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:

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.