---
title: "Find companies hiring for a role"
method: GET
path: "/v1/reqs/search"
---

# Find companies hiring for a role

`GET /v1/reqs/search`

## Request

- Base URL: `https://api.reqbeat.com`
- URL: `https://api.reqbeat.com/v1/reqs/search`
- Auth: one of:
  - API key in header `X-API-Key`
  - HTTP bearer

Reverse who's-hiring-for {role, geo}: companies with active reqs
matching `q` (posting title) / `geo` (country) / `since` (first-seen
lower bound), deduped by company, each with its full hiring pulse and
the specific reqs that matched. LinkedIn-excluded: ATS tenants plus public job boards and aggregators; each matched req's `boards` names its
source. `since` and each req's `first_seen` are our first observation of
the req, not the employer's posting date. Freshness-floored on the
free tier only (by when a req was first seen), keyset-paginated via `next_cursor` (the last page's highest
`company_id`).

`q` is a full-text query over the raw posting title, not an id: type
the role the way a posting would spell it (`staff engineer`), every
word must match -- plurals and other inflections included, so
`staff engineers` is the same search -- and a title that matches nothing
comes back as an empty page rather than an error. `role` is its deprecated
former name, still accepted until its `Sunset` date. The two filters are deliberately asymmetric
-- an unresolvable `geo` IS rejected with 422, because a country either
is or is not in the stored vocabulary, whereas an unmatched title is an
honest zero result.

`geo`, `industry`, `industry_terms` and `headcount_band` can each be
repeated: repeat a parameter to OR its values; different parameters AND.
Every other parameter takes one value, and repeating it is rejected with
422. `industry`, `industry_terms` and `headcount_band` cut the corpus down
to your ICP's companies. They are filters only -- no company facet is added to the
response by passing one -- and a company whose fact we do not hold is never
matched, so a cut is honestly narrow rather than quietly padded.

`limit` is bounded: this endpoint meters the billed `source_row` atom
off its own serialized rows, so an unbounded page is an unbounded
invoice. An out-of-range `limit` is rejected with 422 rather than
silently truncated; page through the full set with `next_cursor`. The
shared public demo key gets a tighter page instead -- it bills nothing,
so no ledger-derived gate can bound it, and a full-size page would make
a world-readable credential a bulk-export window onto the fresh index.

Requires the `read` scope.

## Query parameters

- `q` string, nullable: Full-text query over the raw posting title, e.g. 'staff engineer'. Every word must match, in any order, case-insensitively. Plurals and other inflections match, so 'engineers' finds 'engineer'. Omit to search every open req. A title that matches nothing returns an empty page rather than an error.
- `role` string, nullable: Deprecated former name of `q`, accepted until 2026-11-09 and answered exactly as `q` would be. A call that uses it carries `Deprecation` and `Sunset` response headers. Send `q` instead; sending both is rejected with 422.
- `geo` string[], nullable: Country name to restrict the search to, e.g. 'United States'. Repeat it to search several countries at once. Matched against the geocoder's pinned country list; a country that cannot resolve is rejected with 422 rather than answered emptily.
- `since` string, date-time, nullable: ISO-8601 timestamp lower bound on `first_seen` -- our first observation of the req, not the employer's posting date -- e.g. '2026-01-01T00:00:00Z'. Omit for no lower bound.
- `source_type` 'ats_tenant' | 'board', nullable: Keep only reqs read from one kind of source: 'ats_tenant' (the employer's own applicant tracking system) or 'board' (a job board or aggregator). Matches each req's `source_type`. Omit to search both, which is the default; any other value is rejected with 422.
- `exclude_agencies` boolean: Drop companies we have positively classified as a middleman — a staffing agency, job aggregator, subcontractor network or job board. A company whose kind we have not determined is KEPT, not dropped: this is a veto on proven intermediation, never a filter down to proven direct employers, so an unclassified company can never disappear from your results for want of a classification.
- `industry` string[], nullable: Company industry, matched exactly (case-insensitively), e.g. 'software development'. Repeat for an OR-set. The stored vocabulary is granular -- 'software development', 'computer software' and 'it services and it consulting' are three separate labels -- so prefer `industry_terms` unless you know the exact label you want. A company whose industry we do not hold is never matched.
- `headcount_band` string[], nullable: Company size band: one of '1-10', '11-50', '51-200', '201-500', '501-1000', '1001-5000', '5001+'. Repeat for an OR-set. A band outside that ladder is rejected with 422 rather than answered emptily; a company whose headcount we do not hold is never matched.
- `industry_terms` string[], nullable: Substring of the company industry, matched case-insensitively, e.g. 'health' to reach both 'hospitals and health care' and 'health, wellness and fitness'. Repeat for an OR-set. The forgiving counterpart to `industry`, and usually the one to reach for first.
- `cursor` integer, nullable: Keyset pagination cursor: pass the previous page's `next_cursor` to get the next page. Omit for the first page.
- `limit` integer: Maximum companies per page. An out-of-range value is rejected with 422 — page through the full set with `next_cursor` instead.

## Headers

- `Idempotency-Key` string, nullable

## Response `200`

Successful Response

- ReqsSearchResponse
  - `companies` CompanyMatchOut[], required
    - `company_id` integer, required
    - `company_name` string, nullable
    - `company_domain` string, nullable
    - `pulse` HiringPulseResponse, required
      - `is_hiring` ProvenanceOut, required
        - `value` unknown, required
        - `source_board` string, nullable: The board this value was read from. Null when the value is computed across many boards rather than read from one -- every hiring-pulse scalar is, so it is always null there; the boards behind the pulse are listed once in its `source_boards`.
        - `observed_at` string, date-time, nullable: When this value was observed. For a value computed across the company's whole ledger (every hiring-pulse scalar) this is the time it was computed -- the request time, equal to the pulse's `as_of` -- not when any posting behind it was last seen.
      - `open_req_count` ProvenanceOut, required
        - `value` unknown, required
        - `source_board` string, nullable: The board this value was read from. Null when the value is computed across many boards rather than read from one -- every hiring-pulse scalar is, so it is always null there; the boards behind the pulse are listed once in its `source_boards`.
        - `observed_at` string, date-time, nullable: When this value was observed. For a value computed across the company's whole ledger (every hiring-pulse scalar) this is the time it was computed -- the request time, equal to the pulse's `as_of` -- not when any posting behind it was last seen.
      - `new_roles_30d` ProvenanceOut, required
        - `value` unknown, required
        - `source_board` string, nullable: The board this value was read from. Null when the value is computed across many boards rather than read from one -- every hiring-pulse scalar is, so it is always null there; the boards behind the pulse are listed once in its `source_boards`.
        - `observed_at` string, date-time, nullable: When this value was observed. For a value computed across the company's whole ledger (every hiring-pulse scalar) this is the time it was computed -- the request time, equal to the pulse's `as_of` -- not when any posting behind it was last seen.
      - `velocity` ProvenanceOut, required
        - `value` unknown, required
        - `source_board` string, nullable: The board this value was read from. Null when the value is computed across many boards rather than read from one -- every hiring-pulse scalar is, so it is always null there; the boards behind the pulse are listed once in its `source_boards`.
        - `observed_at` string, date-time, nullable: When this value was observed. For a value computed across the company's whole ledger (every hiring-pulse scalar) this is the time it was computed -- the request time, equal to the pulse's `as_of` -- not when any posting behind it was last seen.
      - `direction` ProvenanceOut, required
        - `value` unknown, required
        - `source_board` string, nullable: The board this value was read from. Null when the value is computed across many boards rather than read from one -- every hiring-pulse scalar is, so it is always null there; the boards behind the pulse are listed once in its `source_boards`.
        - `observed_at` string, date-time, nullable: When this value was observed. For a value computed across the company's whole ledger (every hiring-pulse scalar) this is the time it was computed -- the request time, equal to the pulse's `as_of` -- not when any posting behind it was last seen.
      - `is_surge` ProvenanceOut, required
        - `value` unknown, required
        - `source_board` string, nullable: The board this value was read from. Null when the value is computed across many boards rather than read from one -- every hiring-pulse scalar is, so it is always null there; the boards behind the pulse are listed once in its `source_boards`.
        - `observed_at` string, date-time, nullable: When this value was observed. For a value computed across the company's whole ledger (every hiring-pulse scalar) this is the time it was computed -- the request time, equal to the pulse's `as_of` -- not when any posting behind it was last seen.
      - `by_function` object, required: Open reqs per stored `function`. The key `unspecified` counts reqs that were not classified at all. A key whose leading id has no `function_name` counts reqs classified from their skills alone: a skill family, which has no readable name. The two are kept under separate keys.
      - `source_boards` string[], required: Every board that reported a req the scalars above count, sorted. This is the pulse's provenance: each scalar's own `source_board` is null because one board cannot describe a value counted across several.
      - `momentum` MomentumPointOut[], required: Posting history bucketed over time (weekly), oldest bucket first. A flow, over the company's whole publication history — not a series of `open_req_count` snapshots.
        - `bucket_start` string, date-time, required: Start of the bucket (weekly by default), UTC.
        - `open_req_count` integer, required: How many direct-ATS reqs this company POSTED in this bucket — a flow, counted off the posting history by `posted_at`, including reqs long since closed. This is NOT the number open during the bucket, and it is NOT comparable to the sibling `open_req_count` on the pulse itself, which counts reqs open right now. A bucket larger than that scalar is the ordinary case for a company that publishes more in a week than it leaves open.
      - `as_of` string, date-time, required
      - `coverage_status` 'ats_direct_hit' | 'board_hit' | 'no_ats_signal' | 'unsupported_ats', required
    - `matched_reqs` MatchedReqOut[], required
      - `title` string, nullable, required: The normalized posting title, as the crawler normalizer wrote it. OPTIONAL: present on 73.1% of served reqs (309,255 of 422,826, measured 2026-10-07). `null` means the posting's signal produced no normalized title -- it failed normalization, or normalized without one -- never that the req has no title. Read `raw_title` for a label that is always there; it is populated on every served req and is what `?q=` matches.
      - `raw_title` string, nullable: The posting title exactly as the board published it, and the text `?q=` full-text matches. Populated on 100% of served reqs (422,826 of 422,826, measured 2026-10-07), so this is the field to render when `title` is null.
      - `function` string, nullable, required
      - `country` string, nullable, required
      - `first_seen` string, date-time, required: Our first observation of this req (not the employer's posting date). The `since` parameter filters on this value.
      - `source_posted_at` string, date-time, nullable: The posting date the source itself published for this req. Null when the source gave no date, or none has been recorded yet -- `first_seen` is then the only date we hold, and it is our observation time.
      - `boards` string[], required
      - `source_type` 'ats_tenant' | 'board', nullable: What kind of source this req was most recently read from: 'ats_tenant' (the employer's own applicant tracking system) or 'board' (a job board or aggregator). Most served reqs are 'board'; pass `source_type=ats_tenant` to keep only the ones read from the employer's own system.
      - `country_code` string, nullable, required: The posting country's ISO-3166-1 alpha-2 code, uppercase (`DE`, `US`), derived from `country` and never stored. `null` when `country` is absent or is not a country this corpus names — never a guess. Note this is an output only: the `geo`/`country` filter still takes the name, because a bare two-letter code is ambiguous on input (`CA` is Canada to ISO and California to this column).
      - `function_name` string, nullable, required: The job function's human-readable name (`Software & IT`, `Healthcare`), derived from `function` and never stored. The `?function=` filter accepts this name as well as the opaque taxonomy id in `function`. `null` when the req carries no function attribution (`unspecified`, 10.2% of served reqs), or when it was classified from its skills alone, so its id is outside the named title-family vocabulary (16.7%) — never a guess and never the raw id. Named on 73.1% of served reqs (309,251 of 422,826, measured 2026-10-07).
  - `next_cursor` integer, nullable, required
  - `as_of` string, date-time, required

## Other responses

- `401`: No API key was sent.
- `402`: The key is over its plan's rolling call quota, or the account is past its spend cap. `upgrade_url` and `purchase_url` are the ways out.
- `403`: The presented key is invalid or revoked, or (see `x-required-scope` on this operation) it does not carry the scope this operation requires.
- `422`: Validation Error
- `429`: Too many calls in the current 60-second window. The limit is per API key, set by the key's plan, and shared by every metered REST route and MCP tool; the shared demo key is limited per IP address instead. Wait `Retry-After` seconds and retry. The `RateLimit-*` headers are absent when `reason` is `key_throttled`.

---

[API](https://skmtc.dev/reqbeat/apis/reqbeat-api.md) · [All operations](https://skmtc.dev/reqbeat/apis/reqbeat-api/llms.txt) · [OpenAPI document](https://skmtc.dev/reqbeat/apis/reqbeat-api/revisions/4758e5957e8c?raw)
