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:
| Shape | Billing | |
|---|---|---|
| Job scrapes | Asynchronous: launch, then poll | Per job delivered; the launch is free, and an empty result costs nothing |
| Post feeds | A single call returns a page of posts | Per request, one page of up to 50 posts, whatever the page carries |
| Post search | A single call returns a page of matching posts | Per 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:
- Launch with a
scrape_*tool (or its short alias) → returns{ request_id, status: "QUEUED" }. Free. - Poll the matching
get_*tool with thatrequest_id, from the same workspace that launched it: arequest_idanother workspace launched (or one Hyreflow never issued) reads the same as one that doesn't exist,404.statusmovesQUEUED→RUNNING→COMPLETED. WhenCOMPLETED,resultcarries a page ofjobsplusjobs_total,jobs_returned,jobs_filtered_out,limit,offset,has_more, andwarnings. 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) | Alias | Poll for results | Scrapes |
|---|---|---|---|
scrape_career_pages | career_pages | get_career_pages | A company's career site + its ATS (Greenhouse, Lever, Workday, Ashby) |
scrape_linkedin_jobs | linkedin_jobs | get_linkedin_jobs | LinkedIn jobs for one company's LinkedIn page, by title plus location, or specific postings by URL or id |
scrape_indeed_jobs | indeed_jobs | get_indeed_jobs | Indeed jobs by title + location (regional hosts) |
scrape_arbeitsagentur_jobs | arbeitsagentur_jobs | get_arbeitsagentur_jobs | Bundesagentur 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:
| Tool | Alias | Reads |
|---|---|---|
get_profile_posts | profile_posts | A person's recent posts, from the handle in linkedin.com/in/<username> |
get_company_posts | company_posts | A 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.
| Argument | Default | Meaning |
|---|---|---|
keyword | required | Text the post must contain; a phrase is fine, e.g. "we are hiring" |
date_posted | past-week | past-24h, past-week, past-month, or any to drop the window |
author_title | none | Only posts by authors whose headline matches, e.g. "Recruiter", "Talent Acquisition" |
from_members | none | A list of profile handles to restrict to |
sort_by | date_posted | date_posted (newest first) or relevance |
page | 1 | 1-based page; walk forward with the next_page the previous call returned |
limit | 10 | Trims 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) ortarget_titles_prompt(a plain-English filter), use one, not both. - LinkedIn / Indeed: pass
titles_query(a string or an OR-list of titles) andlocations;countryselects the regional site.company_urlon LinkedIn pulls a whole company's jobs. - LinkedIn direct fetch: when you already hold the postings (a pasted link, the
job_urloff an earlier scrape), passlinkedin_job_urlsand/orlinkedin_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 (nocountry, norows), and the result always carries job detail. The fetch's size is the number of distinct references you sent, so the poll'slimitis the only knob: 100 references exceed one page and need two polls (offset: 0, thenoffset: 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, andjobs_filtered_outis always 0 here. rowsandlimitare separate knobs, size both to what was asked.rowson the launch bounds what the scraper collects upstream (Indeed defaults to 25; LinkedIn to the upstream default);limiton the poll bounds what one page hands back (10). They are independent, sorows: 15with a default poll returns 10. For 15 roles sendrows: 15and poll withlimit: 15, one poll then delivers the ask and bills exactly it.arbeitsagentur_jobsandcareer_pagestake norows, so there the poll'slimitand stopping at the ask are the only guard.- Arbeitsagentur:
titleand/orlocation;radiusis 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:
usernameis 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 withprofile_postsif 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.