hyreflow
Integrations

hyreflow Native

First-party scrapers metered in hyreflow credits with no separate subscription: job scrapes across career pages, LinkedIn, Indeed and Arbeitsagentur (launch, then poll, charged per job returned), LinkedIn post feeds for a person or a company, and keyword search across LinkedIn posts scoped to a date window (one call, charged per request).

Reach for hyreflow Native on three triggers. "Find the open roles", pull everything a company is hiring for off its career site, or search LinkedIn / Indeed / the German federal board by title and location. "What have they been posting?", read a person's or a company's recent LinkedIn posts for launch, hiring, funding, exec-commentary and layoff signals. "Who is posting about this?", search every public LinkedIn post from the past week for a keyword ("we're hiring recruiter"), narrowed to authors whose headline matches, when you don't yet know whose feed to read. First-party scrapers you run on hyreflow credits: no scraper subscription, no API key to manage.

Native (hyreflow-metered, no BYOK): first-party capability billed in hyreflow credits; the provider-precedence waterfall does not apply.

Job scrapes and post feeds work differently, and they bill differently:

ShapeBilling
Job scrapesAsynchronous: launch, then pollPer job delivered; the launch is free, and an empty result costs nothing
Post feedsA single call returns a page of postsPer request, one page of up to 50 posts, whatever the page carries
Post searchA single call returns a page of matching postsPer request, one page of up to 10 posts, a keyword with no hits included

Job scrapes: launch, then poll

A scrape takes a few minutes, so every job scraper is a two-step flow:

  1. Launch with a scrape_* tool (or its short alias) → returns { request_id, status: "QUEUED" }. Free.
  2. Poll the matching get_* tool with that request_id, from the same workspace that launched it: a request_id another workspace launched (or one Hyreflow never issued) reads the same as one that doesn't exist, 404. status moves QUEUEDRUNNINGCOMPLETED. When COMPLETED, result carries a page of jobs plus jobs_total, jobs_returned, jobs_filtered_out, limit, offset, has_more, and warnings. Poll roughly every 10s: re-polls dedup per job, so a tighter loop never costs extra, it only shortens the wait.

You're charged once per unique job delivered: each job in a scrape is billed exactly once. Re-polling, widening the limit, or overlapping page windows never double-charge; paging forward bills only the newly-delivered jobs.

Because a launch is free and returns immediately, you don't have to run scrapes one at a time. Launch several scrape_* calls up front, collect their request_ids, and poll them together, launches aren't serialized, and a batch of around 20 runs is a practical working size. A multi-angle sweep (the same titles across career pages, LinkedIn, Indeed, and Arbeitsagentur, or the same query across several cities) then costs roughly one scrape's wall-clock instead of the sum of every scrape run sequentially. Each run in the batch bills its own delivered jobs as you poll it, so a wide batch commits that spend at once, size the batch accordingly.

jobs_returned is only the current page: with the default limit of 10, a first poll showing 10 jobs says nothing about how many roles the scrape actually found. Check jobs_total and has_more to judge coverage before deciding you need another scrape.

Job scrapers

Launch (free)AliasPoll for resultsScrapes
scrape_career_pagescareer_pagesget_career_pagesA company's career site + its ATS (Greenhouse, Lever, Workday, Ashby)
scrape_linkedin_jobslinkedin_jobsget_linkedin_jobsLinkedIn jobs for one company's LinkedIn page, by title plus location, or specific postings by URL or id
scrape_indeed_jobsindeed_jobsget_indeed_jobsIndeed jobs by title + location (regional hosts)
scrape_arbeitsagentur_jobsarbeitsagentur_jobsget_arbeitsagentur_jobsBundesagentur für Arbeit (German federal job board)

The get_* pollers accept limit/offset (default 10, max 50) to page through result.jobs. A limit above 50 is rejected rather than quietly trimmed, so a paging loop can't silently skip the jobs a shortened page would have left behind. Advancing to a new page bills the jobs that page delivers. Dedup makes re-polling a window you already fetched free, it does not make the next window free, so has_more: true is not a reason to keep going. Stop once you have what was asked for; to walk the whole result deliberately, keep incrementing offset by limit and re-polling while result.has_more is true.

Post feeds: one call, one page

A post feed is not asynchronous: one call returns the posts inline, newest first:

ToolAliasReads
get_profile_postsprofile_postsA person's recent posts, from the handle in linkedin.com/in/<username>
get_company_postscompany_postsA company page's recent posts, from the handle in linkedin.com/company/<username>

One request returns one page of up to 50 posts and is charged as one request. To walk further back, pass the next_start the previous call returned while has_more is true, each page is another request, so it is another charge. limit trims how many posts come back (handy when you only want the last handful in context); it does not change what a request costs, so there is no saving in asking for fewer, and it is not the paging step, which is why next_start is the value to advance on.

Every post carries its full text, the post URL, when it was posted (both a timestamp and a date), reaction / comment / repost counts, the content type, the author, and any companies tagged in it. A company feed also reports total and total_page for the whole feed.

A request is charged whether or not the handle resolves, a page of 50 posts and an unreachable handle cost the same. When a handle can't be read, success is false and message says why, so check the handle against the profile URL before spending a request on it.

# a person's recent posts — one call, one charge
hyreflow tools execute profile_posts --payload '{"username":"acme-dana-reed","limit":10}'

# a company page's feed, second page
hyreflow tools execute company_posts --payload '{"username":"acme","start":50}'

Post search: one keyword, one page

search_posts (alias post_search) finds public LinkedIn posts by what they say rather than who said them. It is the tool for a hiring signal you can't attach to a handle yet: recruiting-agency staff announcing a search, a hiring manager posting "we're hiring", a founder mentioning a new office.

ArgumentDefaultMeaning
keywordrequiredText the post must contain; a phrase is fine, e.g. "we are hiring"
date_postedpast-weekpast-24h, past-week, past-month, or any to drop the window
author_titlenoneOnly posts by authors whose headline matches, e.g. "Recruiter", "Talent Acquisition"
from_membersnoneA list of profile handles to restrict to
sort_bydate_posteddate_posted (newest first) or relevance
page11-based page; walk forward with the next_page the previous call returned
limit10Trims how many posts come back (a page holds at most 10), never what the request costs

A search is scoped to the past week by default. A hiring post is only a signal while the role is open, so an unscoped search is rarely what you want; when it is, pass date_posted: "any" deliberately. Every result is the same post record a feed returns (text, URL, dates, engagement counts, author, tagged companies), so the two read alike downstream. The response reports total for the whole match set alongside page, page_size, has_more, and next_page.

A page is 10 posts, so one request is one page of up to 10 hits and one charge, and a keyword nobody posted about this week is charged the same as a full page. Narrow with author_title before paging: "hiring" matches everything, "hiring" from a "Recruiter" is the agency-staff signal in one request.

How the window is applied depends on the sort. With sort_by: "relevance" the window filters the match set and total counts only posts inside it. With the default sort_by: "date_posted" results stream newest-first and the window is applied to that stream: total counts the whole unwindowed match set, and the walk ends (has_more: false, next_page: null) on the first page that reaches past the window, so you never pay for a page of stale posts. An empty page whose message reads "No matching posts returned (the search source reported no result twice)" is a transient upstream miss rather than a real zero: the request was already retried once automatically; re-run the same call. It is still charged like any other request.

# recruiting-agency staff announcing a search this week — one call, one charge
hyreflow tools execute search_posts \
  --payload '{"keyword":"we are hiring","author_title":"Recruiter"}'

# widen to a month, next page
hyreflow tools execute search_posts \
  --payload '{"keyword":"talent acquisition partner","date_posted":"past-month","page":2}'

Guidance

  • Career pages: narrow with target_titles (a list) or target_titles_prompt (a plain-English filter), use one, not both.
  • LinkedIn / Indeed: pass titles_query (a string or an OR-list of titles) and locations; country selects the regional site. company_url on LinkedIn pulls a whole company's jobs.
  • LinkedIn direct fetch: when you already hold the postings (a pasted link, the job_url off an earlier scrape), pass linkedin_job_urls and/or linkedin_job_ids, 1–100 per field; the two can be paired, and an id given both ways is fetched once. No search runs, so no other search input goes with them (no country, no rows), and the result always carries job detail. The fetch's size is the number of distinct references you sent, so the poll's limit is the only knob: 100 references exceed one page and need two polls (offset: 0, then offset: 50), billed per job exactly as in search mode, so 100 references cost at most 5.0 credits. len(result.jobs) can come back smaller than what you sent, a dead or unfetchable posting costs nothing, and jobs_filtered_out is always 0 here.
  • rows and limit are separate knobs, size both to what was asked. rows on the launch bounds what the scraper collects upstream (Indeed defaults to 25; LinkedIn to the upstream default); limit on the poll bounds what one page hands back (10). They are independent, so rows: 15 with a default poll returns 10. For 15 roles send rows: 15 and poll with limit: 15, one poll then delivers the ask and bills exactly it. arbeitsagentur_jobs and career_pages take no rows, so there the poll's limit and stopping at the ask are the only guard.
  • Arbeitsagentur: title and/or location; radius is in kilometres.
  • Each returned job carries title, location, employment type, salary (when available), the apply URL, and company fields, feed them straight into a sourcing or BD play.
  • Post feeds: username is the handle out of the LinkedIn URL, not the display name and not the full URL.
  • Post search: start from a keyword plus author_title, read the page, and only then open a specific author's feed with profile_posts if their history matters. Search finds the people; the feed reads one of them.
# 1. launch (free)
hyreflow tools execute linkedin_jobs \
  --payload '{"titles_query":"recruiter","locations":["London"],"rows":15}'
#    → { "request_id": "...", "status": "QUEUED" }

# 2. poll until COMPLETED (charged per job in result.jobs)
hyreflow tools execute hyreflow_native_get_linkedin_jobs \
  --payload '{"request_id":"<id>","limit":15}'
# direct fetch — postings you already hold, no search runs
hyreflow tools execute linkedin_jobs \
  --payload '{"linkedin_job_urls":["https://www.linkedin.com/jobs/view/4123456789"],"linkedin_job_ids":["4000000001"]}'
#    → { "request_id": "...", "status": "QUEUED" }

hyreflow tools execute hyreflow_native_get_linkedin_jobs \
  --payload '{"request_id":"<id>","limit":2}'

A job scrape is asynchronous: the launch returns a handle, not jobs. Keep polling the matching get_* until status is COMPLETED (or FAILED); a still-running or empty result costs nothing. A post feed or post search has no launch half: the single call returns the posts.

On this page