Fiber AI
Search & discovery

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

  1. Filter postings by company, title, status, posted date, applicants, function, industry, pay, experience, modality, employment type, seniority, and country/region.
  2. Count first with jobPostingSearchCount — one credit, totalJobsFound, no posting payload.
  3. Page through matches with jobPostingSearch — you pay per posting returned.
  4. Read structured fields on each hit: description, salary range, location, modality, applicant range, and the posting URL.

Operations

JobOperationHTTPWhen to use
CountjobPostingSearchCountPOST /v1/job-search/countIterate filters; "how many?" before pulling rows
SearchjobPostingSearchPOST /v1/job-searchFetch 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/zodJobPostingSearchDatazJobPostingSearchData, 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

CountSearch
Credits1 per request (flat)1 per posting returned (default)
PayloadtotalJobsFounddata[] plus nextCursor
PaginationNonecursor in, nextCursor out
Empty matchesStill 1 credit0 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).

FilterShapeWhat it does
companiesDiscriminated unionJobs at specific companies. One identifier type per request — do not mix slugs with domains in the same object
titlestring[]Partial match on job titles (e.g. ["Software Engineer", "Manager"])
isActive"true" | "false" | "no_preference"Active only, closed only, or both
postedAtDate selectionWhen the role was posted (absolute range or relative window)
numApplicants{ lowerBound?, upperBound? }Applicant-count range
jobFunctionsenum[]Function categories (Engineering, Sales, Operations, …)
industriesenum[]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
employmentTypeenum[]Full-time, Part-time, Contract, Temporary, Internship, Volunteer, Other
seniorityLevelenum[]Entry level, Associate, Mid-Senior level, Director, Executive, Internship
countryOrRegionCodestring[]ISO 3166-1 alpha-3 countries (USA, IND, GBR) and/or region codes (X-APAC, X-LATAM, X-ANGLOSPHERE, X-EMEA, …)
jobLocationTypesame as modalityDeprecated. 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:

identifiervalue 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 title arrays plus a large pageSize is how spend spikes.
  • Prefer jobModality over jobLocationType.
  • 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.
  • chargeInfo is authoritative — published rates can differ per org.

Use cases

  • GTM / AI SDR: companies actively hiring for a role your product supports (the /search catalog 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 searchParams on jobPostingSearchCount over 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

OperationDefault credits
jobPostingSearch1 per posting returned
jobPostingSearchCount1 per request

See Billing & credits.

Related: Blue-collar job search · Agentic search · Typeaheads · Tracker · MCP · SDKs

On this page