Job Posting to Hiring Manager Email avatar

Job Posting to Hiring Manager Email

Pricing

from $17.00 / 1,000 verified hiring contacts

Go to Apify Store
Job Posting to Hiring Manager Email

Job Posting to Hiring Manager Email

Turn a Greenhouse or Lever vacancy into the named hiring owner behind it, with a provider-verified work address from your own BYOK enrichment key. You pay $0.02 only for a verified hiring contact; ambiguous company matches, unsupported boards and empty results are free.

Pricing

from $17.00 / 1,000 verified hiring contacts

Rating

0.0

(0)

Developer

Tim Zinin

Tim Zinin

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Share

Job Posting to Hiring Manager Email: Deterministic Hiring-Owner Contact Resolution for Open Roles

What this Actor does: input, evidence, outcome

Get one relevant hiring-owner email per job posting or company-and-role pair you submit — the single recruiter, HR leader or department owner whose title makes them the deterministically correct person to contact about that vacancy, with the address itself confirmed by your own contact-data provider account rather than guessed from a name-pattern formula.

The billable outcome this Actor is built around is a provider-verified hiring-owner match: one job or company-and-role input resolves to exactly one person, selected by deterministic role relevance to the vacancy, whose work email your own contact-data provider account has itself reported under a positive, non-guessed verification status. That is the only noun this page uses for pricing, listing terminology and Dataset semantics. Everything else the Actor's design can produce — an unresolved applicant-tracking board, a vacancy the board does not carry, an employer identity that cannot be corroborated, no provider key supplied, a provider result marked guessed or unconfirmed, a tie between two equally plausible candidates, the same contact already delivered for another vacancy in the same run, or a run limit reached before coverage completed — is free, never a partial paid result.

What you get

  • Resolve one vacancy to one hiring-owner contact. Submit a direct posting link or a company domain plus the exact job title, and the Actor selects the single most role-relevant recruiter, HR leader or department owner — never a list of "possible" people to sort through yourself.
  • Read two public job boards without any key of ours. Greenhouse and Lever both publish board data over an unauthenticated JSON feed, and both boards' own robots.txt allow automated reading. No login, browser, proxy or CAPTCHA-solving step exists in the job/company half. A third board, Ashby, is excluded by decision — see Evidence and boundaries.
  • Never guess an email pattern. Every billable address is one your own provider account itself reports as identity-matched and positively verified — never pattern synthesis, never an MX/SMTP probe, never a catch-all guess.
  • Tell you exactly why this person, not another. matchReason names the deterministic rule that selected the candidate, and ownerProfileEvidence carries whatever profile evidence your provider returned for them.
  • Get an honest reason when there is no billable match, not silence. Every free row explains exactly which of the eighteen structural reasons applies, with a stable machine error code.
  • Never receive an implicit green light to auto-send outreach. Every row, paid or free, carries safeToAutomate:false and safeForOutreach:false as fixed flags — evidence for human review, never permission for an automated send.

This is a one-job, one-owner, on-demand resolution utility. It is not a bulk company-wide contact harvester, not a people-search database and not a monitor — it accepts a specific vacancy or company-and-role pair and returns at most one contact for it, never a list of everyone who works there.

Who uses it

  • Recruiting and staffing agencies who already have a target vacancy and need the one person at that company who actually owns hiring for it, instead of guessing between a generic careers@ inbox and a cold LinkedIn message.
  • Sales and partnership teams using a company's own hiring activity as a buying signal, turned into a named, provider-verified point of contact for a human-reviewed outreach step.
  • ATS and recruiting-tooling vendors who want a per-posting resolution step, keeping the provider licensing relationship on the buyer's own account rather than baked into vendor infrastructure.
  • Compliance and data-quality reviewers who need a defensible reason to accept or reject a hiring-manager contact already sitting in a CRM, with provider evidence and observation time attached.
  • Developers and workflow builders who want a strict, flat, machine-checkable contract an agent or n8n node can branch on without parsing free text.

Where the Actor sits in your pipeline: trigger, run, action

How to run

  1. Supply one to one hundred job entries: each a direct posting URL (jobUrl) or a company domain plus the exact job title (companyDomain + jobTitle). location/department are optional disambiguation hints; inputRef is an optional opaque correlation string.
  2. Supply your own contact-data provider's name and API key as two secret fields (providerName, providerApiKey) — required together. Omitting both is valid: every job returns the free provider_key_missing outcome rather than an error.
  3. Optionally cap maxConcurrency (1–5, default 1) — local batching only; on a monetized run source work and delivery stay sequential regardless, and Lever's own one-request-per-second floor is enforced independently.
  4. Start the run from the Console, the Apify API, a scheduled Task, or an MCP client.
  5. Read the Dataset for one row per submitted job — present even for a duplicate or a failure, so row count against input count is itself a completeness check.
  6. Read OUTPUT for the run-level summary: submitted, duplicates skipped, delivered, billed, free by category, and whether the run stopped early. The Dataset answers "what did I get"; OUTPUT answers "what did this run charge for" — reconcile the two before acting on any single row.
  7. Route every owner_match row to a human-reviewed next step; safeToAutomate/safeForOutreach are fixed false specifically to keep an automated send out of that path.

A default-input smoke run reads a real, live Greenhouse board and does not require any buyer secret to execute — it simply returns the free provider_key_missing outcome for the vacancy it resolves, because no provider key is supplied by default.

Pricing

Pay only for a delivered result. $0.02 per provider-verified hiring-owner match, plus a flat $0.005 Actor start fee per run. Every other outcome — an unresolved board, a vacancy the board does not carry, an employer identity that cannot be corroborated, a missing or rejected provider key, an unconfirmed or absent provider result, a candidate that fails this Actor's own evidence bar, an ambiguous tie between two candidates, the same contact already delivered for another vacancy in the same run, and any run limit reached before coverage completed — is free. A run of one hundred jobs where only twelve resolve to a provider-verified match bills $0.005 + 12 × $0.02 = $0.245, not $2.00.

This price pays for the vacancy-to-owner resolution only. The person-verification step itself runs on your own contact-data provider account (BYOK) — Hunter, Apollo, Prospeo, Snov.io or RocketReach — called on your own key against your own quota. Your provider bills you directly for that usage, on whatever plan you already hold with them; this Actor never resells contact data, never marks up a provider's fee, and never stores, logs or reuses your provider key beyond the single run you supply it for.

Input contract

jobs is required: one to one hundred objects. Each requires either jobUrl — a direct posting link, 12–2048 characters, matched at runtime against a fixed list of supported board hosts before any request is made, so a link to any other host returns the free ats_not_supported outcome without a network call — or both companyDomain (a bare public hostname, 4–253 characters; schemes, paths, credentials and IP literals rejected by pattern) and jobTitle (2–200 characters, matched exactly after normalization, never fuzzy). location and department are optional disambiguation strings, up to 200 characters, never used to invent a match. inputRef is an optional opaque string up to 200 characters, copied back exactly, including whitespace.

providerName is an optional enum — hunter, apollo, prospeo, snov or rocketreach — and providerApiKey is a secret string field, 8–512 characters, marked isSecret:true. The two fields are mutually required: supplying one without the other is rejected before the run starts. Only Hunter's call pattern is implemented. The other four are accepted as ToS-cleared BYOK candidates, but selecting one today returns the free provider_not_implemented outcome for the whole run rather than a plausible-looking empty result.

maxConcurrency is an integer, 1–5, default 1: local, non-monetized batching only. Runs deduplicate on the vacancy's own identity before any network call, so submitting the same vacancy twice in one run, in slightly different casing, is skipped rather than charged twice; OUTPUT counts these as duplicatesSkipped.

{
"jobs": [
{
"companyDomain": "meridianlabs.example",
"jobTitle": "Senior Backend Engineer",
"location": "Remote - EU",
"department": "Engineering",
"inputRef": "ats-req-77219"
},
{
"jobUrl": "https://jobs.lever.co/meridianlabs/8f2a1c40-91e3-4b7a-8a2f-119d3c88a071",
"companyDomain": "meridianlabs-hq.example",
"inputRef": "ats-req-77223"
}
],
"providerName": "hunter",
"providerApiKey": "REDACTED-BUYER-SUPPLIED-KEY",
"maxConcurrency": 1
}

(meridianlabs.example and meridianlabs-hq.example use the .example reserved domain suffix precisely so neither can be mistaken for a real company; the two example output rows below show why supplying a domain alongside a Lever posting link, as the second job does here, does not always resolve the way a buyer might expect.)

Example output

Every row shares one flat, 36-field schema regardless of outcome. The three examples below are illustrative — every company, person and address is invented — built field-by-field to match exactly what .actor/dataset_schema.json and the runtime actually produce.

Happy path — a provider-verified line-owner match on a Greenhouse board

{
"recordType": "hiring_owner_decision",
"schemaVersion": "1.0",
"entityId": "4a7c91e0f3b6d825ae19c047",
"inputRef": "ats-req-77219",
"atsProvider": "greenhouse",
"atsToken": "meridianlabs",
"jobId": "6142098",
"jobTitle": "Senior Backend Engineer",
"department": "Engineering",
"location": "Remote - EU",
"jobUrl": "https://boards.greenhouse.io/meridianlabs/jobs/6142098",
"companyName": "Meridian Labs",
"companyDomain": "meridianlabs.example",
"resolutionPath": "byok",
"ownerName": "R. Alvarez",
"ownerTitle": "VP Engineering",
"ownerProfileEvidence": { "linkedin": "https://www.linkedin.com/in/r-alvarez-example" },
"email": "r.alvarez@meridianlabs.example",
"matchReason": "line_owner:line_owner_same_function",
"verificationStatus": "owner_match",
"verificationLevel": "provider_verified",
"providerName": "hunter",
"sourceUrl": null,
"sourceEvidence": {
"vacancy": "https://boards.greenhouse.io/meridianlabs/jobs/6142098",
"board": "greenhouse:meridianlabs",
"providerConfidence": 92,
"disclaimer": "The vacancy is published by the board named above. The address carries the verification level stated on this row and nothing beyond it: not mailbox ownership, not consent to be contacted, not current hiring authority."
},
"confidenceScore": 6,
"confidenceBand": "medium",
"recommendedAction": "human_review_before_outreach",
"safeToAutomate": false,
"safeForOutreach": false,
"partial": false,
"partialReason": null,
"errorCode": null,
"error": null,
"retryable": false,
"observedAt": "2026-08-12T21:14:03.000Z",
"found": true
}

Every field here was worked out by the same deterministic rule the runtime itself runs: "VP Engineering" leads with a curated leadership word, carries no rank-down qualifier in front of it, and its function (engineering) matches the vacancy's own department and title — exactly line_owner_same_function, scored at a VP-tier line-owner band. The schema also permits confidenceBand: "none", which the current code never produces — reserved so it can start being returned without a breaking change, the same arrangement as sourceUrl. confidenceBand is a fixed routing bucket per seniority tier, not a statement of certainty about the person (see Field dictionary). recommendedAction is set to the one fixed string this build ever writes, and only on this row.

Free path — a Lever posting whose board never publishes an employer name

{
"recordType": "hiring_owner_decision",
"schemaVersion": "1.0",
"entityId": "1b6e84f0a35c9d7218e4b096",
"inputRef": "ats-req-77223",
"atsProvider": "lever",
"atsToken": "meridianlabs",
"jobId": "8f2a1c40-91e3-4b7a-8a2f-119d3c88a071",
"jobTitle": "Data Platform Lead",
"department": null,
"location": null,
"jobUrl": "https://jobs.lever.co/meridianlabs/8f2a1c40-91e3-4b7a-8a2f-119d3c88a071",
"companyName": null,
"companyDomain": "meridianlabs-hq.example",
"resolutionPath": null,
"ownerName": null,
"ownerTitle": null,
"ownerProfileEvidence": null,
"email": null,
"matchReason": null,
"verificationStatus": "ambiguous_company_match",
"verificationLevel": null,
"providerName": null,
"sourceUrl": null,
"sourceEvidence": null,
"confidenceScore": null,
"confidenceBand": null,
"recommendedAction": null,
"safeToAutomate": false,
"safeForOutreach": false,
"partial": false,
"partialReason": null,
"errorCode": "ambiguous_company_match",
"error": "The buyer supplied meridianlabs-hq.example, but the board publishes no employer name for this vacancy, so meridianlabs-hq.example cannot be corroborated; the employer cannot be resolved without agreement.",
"retryable": false,
"observedAt": "2026-08-12T21:14:04.000Z",
"found": false
}

This is not a hypothetical edge case — it is the ordinary, structural outcome for essentially every Lever posting this Actor reads. Lever's own feed never publishes a company_name field at all, so a buyer-supplied companyDomain can never be corroborated, and a Lever link's own hostedUrl always points back at the board itself, never the employer. Lever postings are read and searched normally, but they cannot currently produce a billable row — a deliberate, disclosed limitation, not a bug.

Partial path — a board response exceeded this run's size cap

{
"recordType": "hiring_owner_decision",
"schemaVersion": "1.0",
"entityId": "9d2f47b1c8e0a5936f7d1284",
"inputRef": "ats-req-77225",
"atsProvider": "greenhouse",
"atsToken": "cascadepoint",
"jobId": null,
"jobTitle": null,
"department": null,
"location": null,
"jobUrl": null,
"companyName": null,
"companyDomain": "cascadepoint-systems.example",
"resolutionPath": null,
"ownerName": null,
"ownerTitle": null,
"ownerProfileEvidence": null,
"email": null,
"matchReason": null,
"verificationStatus": "partial_source_coverage",
"verificationLevel": null,
"providerName": null,
"sourceUrl": null,
"sourceEvidence": null,
"confidenceScore": null,
"confidenceBand": null,
"recommendedAction": null,
"safeToAutomate": false,
"safeForOutreach": false,
"partial": true,
"partialReason": "board_response_truncated",
"errorCode": "partial_source_coverage",
"error": "Board response exceeded 12,000,000 bytes and was truncated; no row is billed from a partial feed",
"retryable": false,
"observedAt": "2026-08-12T21:14:05.000Z",
"found": false
}

The 12,000,000-byte figure is the actual cap this build enforces, set well above the size of any employer board observed during development, so an ordinary large employer board is not truncated on a normal day — this row shows the rarer day one still is. partial_source_coverage covers four structurally different truncation causes, distinguished only by partialReason; see Decision routing.

Field dictionary

FieldMeaningImportant boundary
recordTypeAlways "hiring_owner_decision"Constant discriminator for schema routing
schemaVersionAlways "1.0"Bumped on any breaking Dataset change, never silently
entityIdDeterministic 24-hex-character row identity, derived from the resolved job and candidateThe same input resolves to the same ID across runs, which is what makes replay safety possible
inputRefYour caller reference, copied exactlyNever normalized; whitespace preserved on purpose; null when you did not supply one
atsProvidergreenhouse, lever, or nullNever "ashby", even when errorCode is ats_not_supported — Ashby is excluded by decision, not read at all
atsTokenThe board token or company slug the vacancy was read fromnull whenever no board could be resolved, for example on ambiguous_company_match
jobIdThe vacancy's own ID on its board, always a string regardless of the source's native typenull when the specific job was never resolved, for example on board_not_found
jobTitleThe vacancy title as the board publishes itnull when the job itself was never resolved
departmentThe vacancy's department, when the board publishes one or you supplied oneOn a free row before a job is resolved, this echoes only what you supplied, not the board's own value
locationThe vacancy's location, same rule as departmentSame input-vs-board distinction as department
jobUrlCanonical link to the live vacancy on its boardnull when no specific job was resolved
companyNameThe employer's display name as the board publishes itnull on every Lever row without exception — Lever's own feed never publishes this field
companyDomainThe employer's registrable domain, either your own input or one recovered from the boardPSL-normalized; present even on some free rows, so you can see exactly what was evaluated
submittedCompanyDomainYour companyDomain string EXACTLY as you spelled it; null on jobUrl-only vacanciesWhen it differs from companyDomain, IDNA folded a confusable spelling (e.g. a fullwidth letter) onto the ASCII domain — compare the two before acting
resolutionPathAlways "byok" on a billable row, null on every free rowThis field has no other value it can carry today
ownerNameFull name of the resolved hiring ownernull on every free outcome that never reached a resolved candidate
ownerTitleThe resolved owner's title, from your provider or the boardnull when no owner was resolved
ownerProfileEvidenceSupporting profile evidence for the resolved personAlways an object on a billable row, with at least an explanatory note; null on every free row
emailThe resolved hiring owner's work emailNever a guessed or pattern-generated address; null on every free row
matchReason<role>:<reason> — which family the person belongs to and why they were selectednull when no owner was resolved; the two families are recruiting and line_owner, never mixed
verificationStatusThe row's outcome; mirrors errorCode on every free rowOnly owner_match is billable
verificationLevelAlways "provider_verified" on a billable row, null on every free rowNo other verification level exists in this build
providerNameWhich of your provider accounts produced this rownull on every free row that never resolved an owner
sourceUrlReserved for a first-party evidence tier not built in this versionAlways null today; kept in the schema so it can return without a breaking change if that tier is ever built
sourceEvidenceEvidence bundle behind a billable match: the vacancy link, the board identity and your provider's own confidence, plus a fixed disclaimernull on every free row
confidenceScoreA small deterministic integer, not a percentageSet only on the billable row in this build; every free row carries null, even ones with a rich explanation elsewhere on the row
confidenceBandhigh, medium, low or none, bucketed from confidenceScoreSame rule as confidenceScore: null on every free row in this build
recommendedActionA routing tag for a human queueThis build writes exactly one non-null value, human_review_before_outreach, and only on the billable row — every free row carries null here even though errorCode and error explain the outcome fully
safeToAutomateAlways falseFixed by the schema; this product finds a contact, it never certifies a right to contact them
safeForOutreachAlways falseEvery row requires mandatory human review before any outreach, regardless of resolution path
partialWhether a run limit truncated coverage of this itemA true row can never carry verificationStatus:"owner_match"
partialReasonWhich specific limit truncated coveragenull unless partial:true; four distinct values share the single errorCode:"partial_source_coverage", and a fifth (job_processing_error) pairs with errorCode:"source_error" instead, when an unrelated vacancy's failure left this one unreachable — see Decision routing
errorCodeBounded machine error code, one of eighteen valuesnull only on the billable owner_match row; every free row carries exactly one
errorHuman-readable message accompanying errorCodenull only on the billable row; never contains a raw provider response, a request header or your API key
retryableWhether the caller can expect a different result from retrying this exact item unchangedtrue only for rate_limited; for source_error it depends on the specific upstream failure; false for every other code, including the billable row, which has nothing to retry
observedAtUTC timestamp when this row's outcome was producedShared by every row in one run
foundWhether this row is the one billable outcome classtrue if and only if verificationStatus is owner_match — the row's billing boundary

Evidence and boundaries

What "provider-verified" means here, and what it does not. In this product, verified means your own contact-data provider account's own positive, non-guessed status for this specific person's address, for the company domain this Actor independently resolved and corroborated. It does not mean mailbox ownership, consent, present-day deliverability or willingness to receive outreach — those are outside what a licensed provider record can prove, and this Actor never asserts them beyond what the provider itself reports. That boundary is why safeToAutomate and safeForOutreach are fixed false on every row regardless of provider confidence, and why the paid row's own sourceEvidence.disclaimer repeats the boundary on the data itself, not just on this page.

Why only Hunter is implemented today. All five accepted provider names publish BYOK-compatible terms in principle, but only Hunter's exact call pattern is built and tested against a live account. Selecting Apollo, Prospeo, Snov or RocketReach returns the free provider_not_implemented outcome — an honest "not built yet," not a claim that nobody at the company has an email.

Why a buyer-declared employer domain is never trusted on your word alone. A wrong domain assertion is the one mistake in this product that would sell a real buyer a real stranger's contact under a real company's name. The rule this build enforces: a domain you supply must be corroborated by the resolved vacancy's own published employer name — the whole name spelled out (with or without the suffix carrying its last word, as in sierra.ai for "Sierra AI"), the name's leading word or words with nothing left over but corporate-lineage noise, or a recognized initialism of that name, never merely a text prefix of it. An industry word is not "nothing left over." apple.com does not corroborate "Apple Bank", united.com does not corroborate "United Health", and bank.com corroborates no company whatsoever — those are separate businesses and generic domains, and selling you one under the other's name is the mistake this paragraph exists to prevent. A domain that merely starts with the same letters as the resolved company label — the way five unrelated real companies were once found to pass on nothing but a coincidence of spelling with an unrelated brand — is never accepted either; only the domain-is-a-shortening-of-the-name direction is.

What this costs you, plainly. An employer named <Brand> <ordinary word> — "Ramp Business Corporation", "Snowflake Computing", "Cresta Intelligence", "Scale AI" — reads exactly like "Apple Bank" to any rule working from text, so your domain alone will not corroborate it. Such a vacancy sells when a second, independent party agrees: the board's own posting link resolving to the domain you supplied. Where the domain came from that link in the first place and you supplied none, the link cannot second anything, and the published name has to carry it alone. You will see ambiguous_company_match or company_domain_unresolved in that case — a free row, and an honest one, rather than a paid row asserting a company we cannot stand behind. On top of corroboration, a domain is rejected outright, regardless of what it corroborates, when it is a known job-board aggregator, link shortener or file-hosting subdomain, or when any label in it is an IDN/punycode-encoded homoglyph — a visually deceptive lookalike a person could not reliably tell apart from the real one by eye. Both checks apply identically whether the domain came from you or from a value recovered out of the board's own data.

Why a hiring-owner title has to be recognized, not merely present. The candidate-scoring logic refuses any title containing even one word it does not recognize as seniority, recruiting or function vocabulary — a structural choice, not a claim of vocabulary completeness. A personal name sitting after a real title (Marketing Manager Sarah Head) is refused, because an unrecognized surname occupies exactly the position an unrecognized word does. This costs genuine, unusually worded titles a sale; it never sells a stranger's inbox.

Why seniority is read by position, not by presence. A title scores as senior only when a leadership word governs the phrase in a position English actually uses for leadership — leading it (Director of Marketing) or trailing it (Marketing Director) — and a rank-down qualifier anywhere to its left cancels it: Junior Marketing Manager and Intern Recruiter are never sold as the hiring owner, however senior the last word alone reads. This rank-cancellation rule applies identically to a department-leadership title and to a recruiting-vocabulary title, so a Trainee Talent Partner is refused exactly as a Junior Marketing Manager is.

Why two roles are scored as two separate families. A recruiter, HR leader or talent-acquisition title is relevant to any vacancy at the company. A department leader is relevant only when their own function actually matches the vacancy's — a Sales director is never evidence for an Engineering opening, however senior. The two families are scored on separate rules and never confused.

Why a tie is refused rather than resolved by picking the first result. When two or more candidates score equally and are not clearly the same person, this Actor returns the free ambiguous_owner outcome instead of silently choosing whichever your provider listed first. "The same person" is decided by normalized full name and mailbox together: two people sharing a name but returned under two different addresses are a genuine tie, not a coincidence to average away. A genuine duplicate — same name, same address, returned twice — is correctly recognized as one person and is not a tie.

Why the same contact is billed once even across several vacancies. A second owner-match at the same company domain is deduplicated and reported free whenever it matches an already-billed contact on EITHER identity — the normalized full name or the mailbox, not necessarily both — so one hiring manager owning several open roles is billed once regardless of which spelling of their name or which mailbox your provider happens to return on a later call. The one honest trade: two genuinely different employees who share a normalized full name at the same company are treated as one contact, and the second is never billed — a lost sale, not a wrong one.

Why role recognition is English-only in this build. The recognized vocabulary — recruiting words, leadership words, department-function words — is English. A non-English title, such as 採用担当 or Responsable du Recrutement, resolves to no relevant owner and returns the free no_relevant_owner_returned outcome, never a wrong guess built on an unrecognized word.

Which English titles this build still does not read. The same vocabulary is closed, so some real hiring owners written in English are refused too. no_relevant_owner_returned on one of these means "this build did not read the title", not "this company has no hiring owner":

  • Bare function abbreviations: CTO, CFO, CHRO, CPO on their own. The longhand resolves (Chief Technology Officer does); the abbreviation alone does not, because unlike a CEO these own hiring for one function, and treating three letters as whole-company authority is the wrong-charge direction.
  • Unit- or geography-scoped management: Managing Director, Country Manager, General Manager — scoped to a business unit or a region rather than to a department this Actor's taxonomy aligns to.
  • Roles whose head word is not a title word at all: General Counsel, Chief Architect,
    Chief Scientist
    , Art Director, Chief Operating Officer, and HR Business Partner (where business reads as the sales function before the recruiting one). These fail on the vocabulary before seniority is ever considered.

Every one of these is a free row. The trade throughout this product is a lost sale over a wrong one.

Why a provider candidate that scored and won can still be refused before it is billed. A person your provider returns and this Actor's own scoring selects can still fail this Actor's own evidence bar — an oversized name, a missing address, or an address on a domain other than the resolved employer's. That candidate is refused with the free provider_data_invalid outcome, checked before any row is built, rather than surfacing as a confusing "try again later" failure no retry could ever fix.

Decision routing

Every row's verificationStatus maps to exactly one of the following eighteen outcomes, and mirrors errorCode on every non-billable row. A downstream workflow should switch on this field directly.

verificationStatusBillable?What it meansretryable
owner_matchYesA provider-verified hiring owner was resolved and deliveredfalse
board_not_foundNoThe resolved board token has no board at allfalse
ats_not_supportedNoThe job's board is Ashby (excluded by decision) or any board outside Greenhouse/Leverfalse
job_not_found_on_boardNoThe board resolves, but this specific vacancy (removed, filled, mistyped) is not on itfalse
ambiguous_company_matchNoThe employer's identity for this vacancy could not be corroborated with confidence — a slug probe found no board or an uncorroborated one, a buyer-supplied domain conflicts with the board's own, a buyer-supplied domain is a known aggregator/shortener/homoglyph host, or the domain carries no probeable slug at allfalse
company_domain_unresolvedNoThe vacancy resolved, but the employer's own mail domain was not — most commonly a Lever posting with no buyer-supplied domain, since Lever's own feed never publishes onefalse
provider_key_invalidNoYour providerApiKey was rejected or unauthenticated by the providerfalse
provider_key_missingNoNo providerApiKey was supplied, so no owner lookup was attempted — distinct from no_relevant_owner_returned, which means a lookup ran and found nobody relevantfalse
provider_no_resultNoYour provider account returned no person at all for this companyfalse
provider_not_implementedNoThe provider name is accepted by the input contract but its call pattern is not integrated in this build — every provider except Hunter, todayfalse
provider_unverified_guessNoYour provider's own status marked the address guessed, catch-all or otherwise unconfirmedfalse
provider_data_invalidNoYour provider returned a candidate that won role-relevance scoring but whose name, title or address does not meet this Actor's own evidence bar for a sellable rowfalse
vacancy_data_invalidNoThe vacancy itself carries a field this Actor cannot deliver as described — a value outside the published bounds, or a missing identifier. Reported apart from provider_data_invalid because the provider returned a usable person; the vacancy is what cannot be deliveredfalse
no_relevant_owner_returnedNoYour provider returned one or more people, but none holds a role relevant to hiring for this vacancyfalse
duplicate_owner_contactNoThis company-plus-person identity was already delivered for another vacancy — earlier in this run, or in an earlier attempt at it that was interrupted and resumedfalse
ambiguous_ownerNoTwo or more candidates tied on relevance and are not confidently the same personfalse
partial_source_coverageNoA run limit was hit before full coverage of this item — see the four partialReason values sharing this code below (a fifth pairs with source_error instead)false
rate_limitedNoLever's own published pacing, or your provider's own rate limit, triggered a deliberate backofftrue
source_errorNoAn upstream timeout, malformed response or unexpected failure from a board or your provider. Also carries partial:true for a vacancy the run never got to attempt at all, because a DIFFERENT vacancy earlier in the same run failed unrecoverably — see partialReason belowtrue for that specific case; otherwise depends on the specific failure

partial_source_coverage carries four distinct partialReason values sharing one errorCode: board_response_truncated (the board's own response exceeded this run's size cap), budget_stopped_before_source_read (a spend cap was reached before this vacancy was attempted), budget_stopped_after_source_read (an owner was resolved but the row could not be delivered because the cap was reached in between), and budget_stopped_before_reached (the run stopped before this vacancy was ever picked up). All four are free and non-retryable; only the second and third can occur after your provider account has already been queried. A fifth partialReason value, job_processing_error, pairs with errorCode:"source_error" instead — it marks a vacancy the run never reached because a different vacancy earlier in the same batch failed unrecoverably, distinct from a genuine spend-cap stop; unlike the other four, this one is retryable:true, since a later run may not hit the same unrelated failure.

A workflow only needs a few rules to stay correct: only continue an automated step on found === true and partial === false; never branch on the presence of email alone, since it is null on every free row by design; and never assume recommendedAction or confidenceScore carry routing information on a free row in this build — they are fixed null there, and errorCode plus error are the fields that actually carry the explanation.

Commercial playbooks

Recruiting agency candidate-outreach targeting

A staffing agency with a client's open requisition, or a public competitor posting, resolves the one role-relevant hiring owner before a recruiter reaches out — replacing a guessed first.last@ pattern with a provider-verified contact a human still reviews before sending anything.

Sales signal-to-contact pipeline

A sales team tracking a target account's hiring activity resolves a role that signals budget into a named point of contact, keeping the enrichment step separately priced from whatever detects the signal.

ATS-vendor per-posting resolution add-on

An ATS or recruiting-tooling vendor calls this Actor once per new customer posting, keeping the provider licensing relationship on the customer's own BYOK key rather than the vendor's infrastructure.

CRM hiring-contact audit sweep

A batch of existing CRM contacts — company domain plus job title, inputRef carrying the CRM record ID — gets re-checked against a real provider account. A no_relevant_owner_returned result for a contact the CRM lists as verified is a worklist for review, not an automatic overwrite.

Integration recipes

Scheduled ATS-feed enrichment pattern

  1. A buyer-owned Apify Task watches a company's own board for new postings, through whatever monitor already does that job for you.
  2. Each new posting triggers a single-job run of this Actor with your own provider key.
  3. The workflow routes an owner_match row into a human-reviewed recruiting or sales queue keyed by inputRef.

Event-driven single-lookup pattern

A new-lead or new-posting webhook triggers a single-job run with one jobs entry; if found === true, the record gets a "provider-verified contact available for review" flag, still routed to a human.

Agent/MCP pattern

An agent supplies exactly the fields the Input contract allows, reads found, partial, verificationStatus, safeToAutomate and safeForOutreach before any next step, never converts a free outcome into an affirmative claim, and always routes an owner_match row to a human queue, citing entityId, jobUrl and observedAt in any ticket it opens.

Data-warehouse pattern

Append Dataset rows keyed by entityId, preserving null fields as null rather than coercing them to empty strings, and treat a future schemaVersion bump as an explicit migration, not a silently compatible row shape.

Operating guide

Choosing jobUrl versus companyDomain plus jobTitle. Use jobUrl when you already have the posting link; the board and job identity are parsed directly from it, with no probing. Use companyDomain plus jobTitle when you only know the employer and the role; the Actor probes the registrable-domain slug against both supported boards and uses whichever answers with a board that genuinely corroborates the domain — a best-effort heuristic, not a directory service, so a miss returns the free board_not_found or ambiguous_company_match outcome rather than a guess.

Reading atsProvider before trusting how a row got there. Greenhouse rows often carry the employer's own canonical link, which this Actor can corroborate and use; Lever rows structurally cannot, because Lever's own feed never publishes a company name. A Lever-heavy input set should expect a materially higher free rate on company_domain_unresolved/ambiguous_company_match than a Greenhouse-heavy one — a property of the source, not of this Actor's matching logic.

Understanding why recommendedAction and confidenceScore are null on every free row today. This build writes exactly one recommendedAction value, human_review_before_outreach, only on the billable row; confidenceScore/confidenceBand are likewise computed only when an owner is resolved. A free row's explanation lives in errorCode, error and, where relevant, partialReason.

Understanding deduplication. Two different questions are answered at two different times: the vacancy's own identity is deduplicated before any network request, and a second owner-match at the same company domain is deduplicated again after resolution — this second check matches on EITHER the normalized full name or the mailbox, either one being enough — reported free as duplicate_owner_contact — see Evidence and boundaries for the full reasoning and its one disclosed trade-off (two differently-employed people sharing a name are merged into one billed contact).

Understanding replay safety, and its one real limitation. Every billable delivery is journaled in phases before it ever reaches you: reserved before any board or provider request, a delivery record written before the atomic Dataset write, confirmed only once the platform receipt is in hand. A worker interrupted between the write and its receipt leaves a journaled record, not a hole — the next process recognizes it as already delivered and skips it. That holds when the interruption lands after the platform write resolved. If it lands earlier — the delivery record exists but no row was ever written — the next run does not continue quietly: it cannot tell a charge that happened from one that did not, so it refuses to start and reports the journal entry as ambiguous. Clearing that state is a human action. Plan unattended schedules with that in mind: the failure mode is a stopped schedule, not a silently missing row. The same mechanism also protects a free budget-stop row: a re-run against the same storage replays the same free decision rather than re-attempting the vacancy, even after you raise your spend cap. This is deliberate, not an oversight: retrying that vacancy needs a genuinely new Task run, because loosening the guarantee for a free outcome would touch the identical machinery that keeps a paid charge from ever firing twice.

Understanding the run-wide request budgets. The job/company half of a run is capped at 300 actual HTTP requests and 250 logical source reads (robots policy plus board reads combined) across the whole run; the person half is capped separately at 300 actual requests to your provider. Exceeding either returns the free partial_source_coverage outcome for whatever could not be reached, never a paid row built on an incomplete read.

FAQ

Does this Actor guess email address patterns?

No, under any path this build has. The billable address always comes from your own provider account's own identity-matched, positively verified result. There is no pattern-synthesis code anywhere in this Actor.

Can I automatically send outreach based on an owner_match row?

The Dataset schema is designed to make that hard to do by accident. safeToAutomate and safeForOutreach are both fixed false, and recommendedAction is a queue label for a human, not an authorization string for an automated send integration.

What happens if I submit a Lever posting?

It is read, searched and matched normally — but it will not produce a billable row, because Lever's feed never publishes the employer's name and this Actor requires a corroborated employer identity before billing the person half. See Evidence and boundaries for the full reasoning.

Why does the input schema accept five provider names if only one is implemented?

All five publish BYOK-compatible terms in principle; only Hunter's call pattern is built and tested. Selecting one of the other four returns the honest free provider_not_implemented outcome rather than a misleadingly empty result.

What happens if I supply a job title in a language other than English?

Role recognition is English-only. A non-English title returns the free no_relevant_owner_returned outcome rather than a wrong guess built on an unrecognized word.

If a run stops early on a spend cap, does raising the cap and re-running pick up where it left off?

Not against the same Dataset and key-value store — the free budget-stop row is replay-protected the same way a paid row is. Retrying a budget-stopped vacancy requires a genuinely new Task run.

Does two people sharing the same name at the same company ever cause a problem?

Yes, in one disclosed direction: two different mailboxes under one normalized name are an unresolvable tie (ambiguous_owner), free; the identical mailbox surfacing twice is deduplicated as duplicate_owner_contact — which also means two different employees sharing a normalized name are merged into one billed contact. See Evidence and boundaries.

Is this Actor affiliated with Hunter, Apollo, Prospeo, Snov, RocketReach, Greenhouse, Lever or Ashby?

No. Those names appear on this page because they are the real market options and public boards this Actor's own code actually checked or integrates with — not because any of them has approved, endorsed or contracted with this Actor.

Sources and rights

Job/company half — public boards, read without a key of ours. Greenhouse's board API (boards-api.greenhouse.io) publishes a robots.txt that returns HTTP 200 and disallows only /embed/; live reads return full job listings with company_name present. Lever's postings API (api.lever.co) publishes a robots.txt that returns HTTP 200 with Crawl-delay: 1, which this Actor honors as a hard floor. Neither board's response carries an explicit redistribution license — robots permission establishes only that automated reading is not disallowed, recorded as such rather than overstated.

Ashby is excluded by decision, not by omission. api.ashbyhq.com/robots.txt returns HTTP 401 — the policy itself cannot be read. This Actor's fail-closed convention treats an unreadable robots policy as no permission, so Ashby boards are never read.

Person/email half — your own provider account, never ours. You supply your own key for a contact-data provider as a secret input, called on your own account's quota; this Actor never stores, logs, reuses across runs, or redistributes that key or the data it returns beyond the single row it produces for your run. The publicly listed Apify Actor canadesk/hunter-io already operates on exactly this buyer-supplied-key pattern. Hunter's, Apollo's and RocketReach's own published terms each restrict an operator from reselling their data to third parties without a separate written agreement; BYOK is outside that restriction's scope because the buyer, not this Actor, is the provider's direct contracting party. Snov's terms were not read in comparable depth; a similar restriction is presumed by analogy but not independently confirmed.

Limits

  • One to one hundred jobs per run; maxConcurrency 1–5, and source work stays sequential on a monetized run regardless of that setting.
  • Only Greenhouse and Lever boards are read; Ashby is excluded by decision, and any other board host returns the free ats_not_supported outcome without a request being made.
  • Only Hunter's call pattern is implemented; the other four accepted provider names return the free provider_not_implemented outcome.
  • No email address is ever pattern-guessed, MX/SMTP-probed or catch-all-tested.
  • Role recognition — who counts as a relevant recruiter, HR leader or department owner — is English-only.
  • Lever postings are read normally but cannot currently produce a billable row, because Lever's own feed never publishes the employer's name.
  • The same contact at one company is billed at most once per run — matched on either the normalized full name or the mailbox, either one being enough — which also merges two different people who share a normalized full name at the same company into one billed contact. That scope covers a resumed run as well as a single process: the contact keys are persisted in the same write that records the delivery, so a vacancy reached only after a restart sees contacts billed before it. This is a statement about scope, not a promise of exactly-once delivery — the interruption case described under replay safety in the Operating guide still applies.
  • A free budget-stop row is replay-protected the same way a paid row is; retrying it requires a new Task run against fresh storage.
  • Run-wide caps: 300 actual HTTP requests and 250 logical source reads for the job/company half, 300 actual requests for the person half, and a 12,000,000-byte ceiling on any single board response.
  • A domain that spells a name's diacritic out in full as a transliteration (gruner-soehne.de for "Grüner Söhne GmbH") does not yet corroborate — a known, disclosed gap on the conservative side: a legitimate pairing like this is refused rather than sold. A lost sale, never a wrong one — the same trade this whole document makes throughout.
  • safeToAutomate and safeForOutreach are fixed false for every row this schema allows.

This build was independently reviewed multiple times before release, with every substantive finding fixed as a general rule rather than patched as a one-off example.

Support boundary

Actor support covers deterministic input validation, the source-access and owner-matching behavior described on this page, and the Dataset/Output/key-value-store schemas. It cannot decide whether a specific outreach action is appropriate for your jurisdiction, cannot restore access if Greenhouse, Lever or your provider changes its own API or terms, cannot guarantee a company keeps publishing the same vacancy or field, and cannot provide legal advice on outreach compliance or your provider's terms. When reporting a problem, include the Actor build/run ID, the redacted entityId, verificationStatus, errorCode and approximate observedAt time. Never send full provider responses or any credential — this Actor's own code never logs or stores your provider key, and support will never ask for it outside the Input form.