Job Search
Filter 13M+ open roles by title, pay, and location, then count matches before you pull postings.
Job Search
We built job search so you can treat open roles as data: titles, compensation, seniority, location, and whether the posting is still active. Use it to pull the postings themselves, or as a hiring-intent signal — companies staffing up for roles your product supports.
We cover 13M+ postings, about 1M new per week.
Search and count share the same searchParams object — count is how you
size a query before you pay for rows.
The payload is in the same neighborhood as listings you'd browse on
LinkedIn Jobs, Glassdoor, or ZipRecruiter: title, company,
location, pay when listed, and whether the req is still open. job_url on a
result is typically a LinkedIn job link. We consider public job boards like
those among the data sources for this API. We do not ask you to sign in to
them.
Filter by employer the way you'd scan a careers page — Microsoft, Amazon, Google, Salesforce, Stripe — via domain, LinkedIn company URL, slug, or org ID.
This is job postings, not people changing jobs. Role-change alerts live
on Tracker (person_changed_company
and related career rules). Pasting a JD to
find candidates is jdToProfileSearch. US
hourly and trade listings are a separate API:
Blue-collar job search.
What it does
- Filter postings by company, title, status, posted date, applicants, function, industry, pay, experience, modality, employment type, seniority, and country/region.
- Count first with
jobPostingSearchCount— one credit,totalJobsFound, no posting payload. - Page through matches with
jobPostingSearch— you pay per posting returned. - Read structured fields on each hit: description, salary range, location, modality, applicant range, and the posting URL.
Operations
| Job | Operation | HTTP | When to use |
|---|---|---|---|
| Count | jobPostingSearchCount | POST /v1/job-search/count | Iterate filters; "how many?" before pulling rows |
| Search | jobPostingSearch | POST /v1/job-search | Fetch posting records (pageSize 1–1000, default 25) |
OpenAPI: jobPostingSearch, jobPostingSearchCount.
Both routes allow 120 requests / minute. Count has no pageSize or
cursor.
Call functions from @fiberai/sdk. Request and response Zod schemas live on
@fiberai/sdk/zod — JobPostingSearchData ↔ zJobPostingSearchData, same
pattern for count. Parse untrusted input with those schemas; the function
import stays small if you never load /zod.
import {
jobPostingSearch,
jobPostingSearchCount,
} from "@fiberai/sdk";
import type {
JobPostingSearchCountData,
JobPostingSearchData,
} from "@fiberai/sdk";
import {
zJobPostingSearchCountData,
zJobPostingSearchData,
} from "@fiberai/sdk/zod";
const searchParams: JobPostingSearchCountData["body"]["searchParams"] = {
title: ["Account Executive"],
isActive: "true",
jobModality: ["Remote"],
countryOrRegionCode: ["USA"],
postedAt: {
strategy: "relative",
window: { method: "within", period: "day", upperBound: 30 },
},
};
const countRequest: JobPostingSearchCountData =
zJobPostingSearchCountData.parse({
body: {
apiKey: process.env.FIBER_API_KEY!,
searchParams,
},
});
const count: Awaited<ReturnType<typeof jobPostingSearchCount>> =
await jobPostingSearchCount(countRequest);
console.log("Matches:", count.data?.output.totalJobsFound);
const searchRequest: JobPostingSearchData = zJobPostingSearchData.parse({
body: {
apiKey: process.env.FIBER_API_KEY!,
pageSize: 25,
searchParams,
},
});
const page: Awaited<ReturnType<typeof jobPostingSearch>> =
await jobPostingSearch(searchRequest);
console.log("First title:", page.data?.output.data[0]?.title);
console.log("Next page:", page.data?.output.nextCursor);
console.log("Cost:", page.data?.chargeInfo);Search vs count
| Count | Search | |
|---|---|---|
| Credits | 1 per request (flat) | 1 per posting returned (default) |
| Payload | totalJobsFound | data[] plus nextCursor |
| Pagination | None | cursor in, nextCursor out |
| Empty matches | Still 1 credit | 0 credits for an empty page (unfilled pageSize is refunded) |
Unlike companyCount / people count, job count is not free.
It is still the cheap way to tighten filters before you pull descriptions.
Filters (searchParams)
Search and count take the same object. Every field is optional; {} is a
valid unfiltered query. Unknown fields are rejected (strict schema).
| Filter | Shape | What it does |
|---|---|---|
companies | Discriminated union | Jobs at specific companies. One identifier type per request — do not mix slugs with domains in the same object |
title | string[] | Partial match on job titles (e.g. ["Software Engineer", "Manager"]) |
isActive | "true" | "false" | "no_preference" | Active only, closed only, or both |
postedAt | Date selection | When the role was posted (absolute range or relative window) |
numApplicants | { lowerBound?, upperBound? } | Applicant-count range |
jobFunctions | enum[] | Function categories (Engineering, Sales, Operations, …) |
industries | enum[] | Standardized industries (Software, Healthcare, Finance, …) |
annualSalaryUsd | { lowerBound?, upperBound? } | Annual pay in USD |
yearsOfExperience | { lowerBound?, upperBound? } | Experience required |
jobModality | "On-site" | "Remote" | "Hybrid"[] | Match any listed modality |
employmentType | enum[] | Full-time, Part-time, Contract, Temporary, Internship, Volunteer, Other |
seniorityLevel | enum[] | Entry level, Associate, Mid-Senior level, Director, Executive, Internship |
countryOrRegionCode | string[] | ISO 3166-1 alpha-3 countries (USA, IND, GBR) and/or region codes (X-APAC, X-LATAM, X-ANGLOSPHERE, X-EMEA, …) |
jobLocationType | same as modality | Deprecated. Use jobModality |
Full enum members for functions, industries, and region codes are on the OpenAPI pages — send the canonical strings, not free-text synonyms.
Company identifiers
Pick one identifier; value is always an array:
identifier | value examples |
|---|---|
linkedinSlug | ["microsoft"] |
domain | ["microsoft.com"] |
linkedinUrl | ["https://www.linkedin.com/company/microsoft/"] |
linkedinOrgID | ["1035"] (numeric strings) |
{
"companies": {
"identifier": "domain",
"value": ["stripe.com"]
}
}Posted date (postedAt)
Discriminated on strategy:
Absolute — a fixed window, regardless of when you run the query:
{
"strategy": "absolute",
"range": { "lowerBound": "2026-01-01", "upperBound": "2026-03-31" }
}Relative — a sliding window from today. Prefer method: "within" with
period of day | week | month | quarter | year and at least one
of lowerBound / upperBound (counts of that period ago). method: "calendar"
with which: "current" | "previous" pins to this or last calendar period.
{
"strategy": "relative",
"window": { "method": "within", "period": "day", "upperBound": 14 }
}What a posting contains
Each item in data includes job_id, title, company_name,
company_logo_url, posted_at, job_url, description, status
(active | closed), modality, seniority_level, employment_type,
job_function, standard_industries, applicant and experience ranges,
compensation_range (currency + hr / m / yr / daily), and
annual_salary_usd. Location is standardized_location (city, region,
country, coordinates).
job_location_type on the response is deprecated; read modality.
Pagination
Pass nextCursor from a response back as cursor. Treat the token as opaque
— do not construct or edit it. null means no further page. A bad cursor
returns 400 (Invalid cursor value.) and refunds the hold.
Using it effectively
- Count, then search. Broad
titlearrays plus a largepageSizeis how spend spikes. - Prefer
jobModalityoverjobLocationType. - Resolve companies with typeaheads before filling
companies, so slugs and domains are ones we can match. - Honor HTTP 429 (120/min). Failed searches refund the credit hold.
chargeInfois authoritative — published rates can differ per org.
Use cases
- GTM / AI SDR: companies actively hiring for a role your product supports
(the
/searchcatalog frames this as hiring-intent, 1 credit per posting). - Recruiting intel: compensation, seniority, and remote mix on live reqs.
- Competitive staffing: titles and volume at a named competitor (
companies). - Trend checks: repeat the same
searchParamsonjobPostingSearchCountover time without downloading descriptions.
In the dashboard, the same idea is a company filter: companies whose postings match titles, seniority, pay, modality, and status — see Using the UI. That finds companies; these endpoints return postings.
Trade and blue-collar listings
Hourly and trade roles are a different product. See
Blue-collar job search (blueCollarJobsSearch,
data sources including Indeed).
Credits
| Operation | Default credits |
|---|---|
jobPostingSearch | 1 per posting returned |
jobPostingSearchCount | 1 per request |
See Billing & credits.
Related: Blue-collar job search · Agentic search · Typeaheads · Tracker · MCP · SDKs