hyreflow Native
First-party tools metered in hyreflow credits with no separate subscription: job scrapes across career pages, LinkedIn, Indeed, Arbeitsagentur and StepStone (launch, then poll, charged per job returned), LinkedIn post feeds for a person or a company, keyword search across LinkedIn posts scoped to a date window the people who reacted to a post, and a LinkedIn profile lookup by URL that feeds the linkedin_profile waterfall (one call, charged per request), a free postcode-radius lookup that turns 'within N miles of a postcode' into a town list for people search, and a repost scan that finds a company's hard-to-fill roles in its PredictLeads job history.
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 / StepStone 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 tools 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. Profile lookup is the second step of the linkedin_profile waterfall; every other native tool runs on its own.
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 |
| Post reactions | A single call returns a page of reactors | Per request, one page of up to 10 reactors, an unreachable post included |
| Profile lookup | A single call returns one profile | Per request, a URL that resolves to no profile included |
| Repost scan | A single call reads a company's job history | 0.8 credits/page for each PredictLeads history page it pulls (one per domain); the scan itself adds nothing |
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) |
scrape_stepstone_jobs | stepstone_jobs | get_stepstone_jobs | StepStone (Germany's largest commercial job board) |
scrape_arbeitsagentur_jobs only returns recruiter details with include_recruiter_details: true (slower). The poll charges +0.1 credits per job with recruiter details, up to 0.15 credits per job including the per-job rate; other jobs bill the per-job rate only, and re-polling never re-charges it.
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}'Post reactions: who engaged with a post
get_post_reactions (alias post_reactions) reads the people who reacted to a LinkedIn post — the
engagement list a feed row's totalReactionCount only counts. Pass the post's postUrl (or any
linkedin.com/posts/... link); one call returns one page of up to 10 reactors and is charged as one
request, whether or not the post resolves. total and total_pages report the whole reaction set, so a
heavily-reacted post is a paged read — advance with next_page (null at the end). limit trims what
comes back, never what a request costs.
Each reactor carries fullName, headline, profileUrl, urn, profileType and the reactionType
they left. reaction_type filters to one kind — LIKE, EMPATHY, PRAISE, INTEREST, FUNNY or
APPRECIATION; omit it for all reactions.
# who reacted to a post — one call, one charge
hyreflow tools execute post_reactions \
--payload '{"url":"https://www.linkedin.com/posts/acme-dana-reed_we-are-hiring-740001"}'
# next page, likes only
hyreflow tools execute post_reactions \
--payload '{"url":"https://www.linkedin.com/posts/acme-dana-reed_we-are-hiring-740001","page":2,"reaction_type":"LIKE"}'Profile lookup: one URL, one profile
get_linkedin_profile reads one person's LinkedIn profile from its URL (linkedin.com/in/<handle>; a bare handle also works). It is the second step of the linkedin_profile waterfall, after AI Ark, so for work history across a list run linkedin_profile and let the waterfall pick; call it directly for one person's full record.
The response is {success, profile, message}. profile carries the name, headline, about, location, open-to-work and hiring flags, experience (newest first, each with company, title, start and end as YYYY-MM, is_current, location, employment type and the company's LinkedIn URL, industry and size), education, skills, certifications and languages. It carries no email or phone, so it is safe to run on candidates.
A URL that resolves to no profile returns success: false and profile: null, and is charged as one request like any other. Inside the linkedin_profile waterfall a miss costs nothing and the chain moves on.
# one person's profile — one call, one charge
hyreflow tools execute hyreflow_native get_linkedin_profile \
--payload '{"linkedin_url":"https://www.linkedin.com/in/acme-dana-reed"}'Postcode or city radius: towns within N miles
People databases match a person's location by exact town name and have no radius filter. places_within_radius (alias postcode_radius) turns a postcode or a city and a radius into every city, town, village and suburb inside it, nearest first, as a ready-made locations list for people_search. It is free.
- The centre, exactly one of:
postcode: a full postcode or ZIP (SW1A 1AA,94103,10115). A bare UK outcode such asSW1Ais not found.place: a city, town or village name (Marburg). It matches the settlement only, soNew Yorkis the city, not the state.
radiuswithunitmi(default) orkm, up to 25 km (about 15 mi);countryas an ISO-2 code (gb,de,us).limit: the nearest places to keep, 50 by default and at most 200.- Returns
center,radius_km,places(name,type,distance_km),locations(led by the centre's own town),total_placesandtruncated.
hyreflow tools execute postcode_radius --payload '{"postcode":"SW1A 1AA","radius":3,"country":"gb"}'
hyreflow tools execute postcode_radius --payload '{"place":"Marburg","radius":20,"unit":"km","country":"de"}'Profiles carry a town, not a postcode, so a match means the person lists a town inside the circle. Keep the metro label in the list you pass on: most people list the metro, not their suburb. Place data © OpenStreetMap contributors (ODbL).
Repost scan: hard-to-fill roles
repost_scan reads a company's PredictLeads job-opening history and returns only the roles that show a hard-to-fill signal, each with its listings as evidence:
repost: a listing first seen in the lastfresh_daysdays, after an earlier listing of the same role at the same site ran 21 days or more and stopped.long-open: the role has been live 90 days or more.evergreen: three or more back-to-back relists about a month apart, the latest still live.
Listings are the same role when the titles match once seniority words and gender tags are dropped, and they're in the same town. Apprenticeship, student and intern listings never count. Pass related_domains for the company's parent or subsidiary domains, and titles or onet_codes to check one role. Pairs the titles can't settle, such as a renamed role, come back in needs_review so you can compare their descriptions. warnings says when a history was cut off or the crawl data is stale; then a missing signal isn't a "no".
The scan itself is free. Each history page it pulls is one PredictLeads job_openings request, billed at that rate (one page per domain, a page holds 1,000 listings), and free on your own PredictLeads key.
hyreflow tools execute hyreflow_native repost_scan --payload '{"domain":"acme.example.com","titles":["Account Executive"],"fresh_days":30}'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. - StepStone:
titleis required (there is no location-only search);locationis optional andradiusis in kilometres.rowsmust be 25, 50, 75 or 100 (whole 25-listing pages); page deeper by steppingstart_pagebyrows / 25, and stop at the first run that returns no jobs (a full page can come back a listing short, and an empty run costs nothing). Optional filters:posted_within_days(1 or 7),work_type,work_from_home,job_language,application_method,discipline. Salary is login-gated on StepStone, so salary fields are always empty.include_details: falsereturns the search-card teaser and no posting date. The run total istotal_foundrather thanjobs_total. - 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. - Post reactions: hand a feed row's
postUrltopost_reactionsto see who engaged; a reactor'sprofileUrlis the handle forprofile_postsor a profile enrich.
# 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.