← Reqbeat

MCP tools

Hiring-demand signals, callable directly from your agent. Reqbeat runs a remote MCP server over streamable HTTP; connect it with your API key and your client lists the tools below.

Connect

Paste this into your client's server config, replacing the placeholder with your own key. Endpoint https://mcp.reqbeat.com/mcp, authenticated with the X-API-Key request header.

{
  "mcpServers": {
    "reqbeat": {
      "url": "https://mcp.reqbeat.com/mcp",
      "headers": { "X-API-Key": "YOUR_REQBEAT_API_KEY" }
    }
  }
}

No key yet? Get an API key — free, no card.

15 tools

is_hiring

WHEN an agent already holds a company and needs to qualify it -- the cheap gate before spending a richer call. LinkedIn-excluded (ATS tenants plus public job boards and aggregators) and freshness-floored. Takes the integer company_id from an earlier result, not a company name or domain. company_id=1234 -> is_hiring true, open_req_count 7, coverage_status "ats_direct_hit". company_id=5678 -> is_hiring false with coverage_status "no_ats_signal" -- that company has no ATS coverage yet, which is not evidence it is quiet, so do not score it as a negative. To find companies in the first place, use who_is_hiring_for.

get_open_reqs

The company's current active reqs, deduped across boards -- LinkedIn-excluded: ATS tenants plus public job boards and aggregators; each row's boards names its source. Freshness-floored. Each row's first_seen is our first observation of the req, not the employer's posting date; source_posted_at is the date the source itself published, null when it gave none. function is a function name (a prior result's function_name, like 'Software & IT') or a function id (its function); anything else, like 'engineering', is rejected with an explicit error that lists the names, rather than an empty result. limit is bounded: an oversized page is rejected rather than truncated.

hiring_pulse

One number set for one company: how many reqs it opened in the last 30 days, a velocity ratio of that against the 30 days before it, a direction of up / flat / down between the two, a surge flag, and momentum -- postings published per week, a flow rather than a stock. Use it to rank or score a company you already hold a company_id for. Freshness is controlled by max_age (seconds); a company with no ATS/board data yet returns {job_id, status: "crawling"} instead of a body -- poll again later, do not read it as "not hiring". For the whole picture in one round-trip use pre_action_brief; for a cheap yes/no use is_hiring.

who_is_hiring_for

WHEN an agent needs to FIND the companies worth working -- the sourcing step, before it knows which companies exist. Reverse who's-hiring-for {title, geo} search: companies with active reqs matching q/geo/since, deduped by company, keyset-paginated via cursor. q is free text matched against the posting title, not an id; plurals and other inflections match, so "backend engineers" is the same search. geo is resolved to a stored country before matching; an unresolvable one is an explicit error, never a quietly partial page. limit is bounded -- page through the full set with cursor instead of raising it. Billed per company returned -- an empty result bills nothing. q="backend engineer", geo="USA" -> the companies with matching active reqs, each with its pulse and its matched reqs. geo="Atlantis" -> an explicit unresolvable-country error rather than an empty page. Already holding a company_id and only need a yes/no? Use is_hiring.

search_jobs

Flat, role-granular job search -- the individual open roles across companies matching function / geo (country) / since, one row per logical req (each with its own company_id + req_key), LinkedIn-excluded (ATS tenants plus public job boards and aggregators; each row's board names its source) + freshness-floored. since filters on first_seen, our first observation of the req (not the employer's posting date); each row's source_posted_at is the date the source itself published, null when it gave none. Keyset-paginated via the opaque cursor (a prior call's next_cursor). Use who_is_hiring_for for the company-granular reverse view. function is a function name (a prior result's function_name, like 'Software & IT') or a function id (its function); anything else, like 'engineering', is rejected with an explicit error that lists the names, rather than an empty result -- pass plain language as q instead, which searches the posting's own title, expanded semantically to nearby titles, and reports each row's relevance (0-1). q is independent of function: pass both to search titles within one function. sort is 'relevance' (the default with a q) or 'recency'; omit both and the page keeps its stable default order. limit is bounded: an oversized page is rejected rather than truncated, so page through the full set with cursor instead of raising limit.

get_role

One open role's detail, addressed by the company_id + req_key pair every search_jobs row already carries -- the follow-up call for a role you hold an identifier for, instead of re-pulling the whole company with get_open_reqs. Scoped exactly as search_jobs is: LinkedIn-excluded (ATS tenants plus public job boards and aggregators) and freshness-floored. The body adds raw_title (the posting's own title) and boards, the full deduped list of boards reporting this req. A role that does not exist, is closed, or has not reached the freshness floor is an explicit error, never an empty success -- and it is not billed.

find_qualified_reqs

WHEN an agent already knows its ideal customer profile and wants the matching open roles as rows it can act on: several countries, an industry, a headcount band and title keywords in ONE call, instead of a search followed by a lookup per company. criteria takes up to three groups -- company (country, industry, industry_terms, headcount_band, company_kind), req (q, q_excludes, seniority_level, function, skills, remote_type, location) and event (company_id, board, source_type, ats_vendor, country) -- AND across fields, OR within one. A field outside that list is an explicit error, never quietly dropped. criteria={"company": {"country": ["United States", "Germany"]}, "req": {"q": ["backend engineer"]}} -> live backend roles in both countries, each row naming the company and carrying the posting URL, seniority, skills and declared or estimated yearly pay. salary_min_usd / salary_max_usd bracket that pay. Agencies and expired postings are excluded by default. Page with cursor (the previous next_cursor); limit is bounded. Billed per row returned, and an empty page bills nothing. Needs an API key: without one the answer is a signup link.

pre_action_brief

Everything an agent needs before acting on one company, in one bounded round-trip instead of five: the hiring pulse, its top open reqs deduped across boards, first-hire-by-function events, hardest-to-fill (reposted) reqs and ATS-vendor migrations, pre-joined and each section capped so the payload stays compact. Use it right before writing outreach or a qualification note for a company_id you already hold. Honors max_age (seconds); a company with no ATS/board data returns {job_id, status: "crawling"}. If you only need the velocity number, hiring_pulse is cheaper.

get_changes

The change feed, not a search: ledger events -- a req opened, re-observed, reposted or closed -- with event_seq > since, ascending, plus next_cursor. Replay with next_cursor instead of polling or re-searching; an empty page bills nothing and one change unit is metered per event returned. Filter by company_id, or by one exact event_type. limit is bounded -- page with the cursor rather than raising it. Free-tier callers see events at the same freshness floor as every other free read. This is the pull-based twin of watch_company (push via webhook).

find_company

WHEN you hold a company's website or name but not the company_id every company-scoped tool takes (is_hiring, get_open_reqs, hiring_pulse, pre_action_brief, watch_company). Pass exactly one of domain or name; both or neither is an error naming that rule. domain takes a bare host or a full URL and matches exactly ({"domain": "https://www.stripe.com/jobs"} -> the companies at stripe.com); name returns up to five candidates, each with a match_confidence -- 1.0 for an exact name, 0.8 for a match once legal suffixes are dropped. Each company carries company_id, company_name, company_domain, country_code and coverage_status, and no hiring signal: ask is_hiring for that. Nothing matching is an empty companies list, never an error. Never billed.

register_webhook

Register the delivery target a watch fires to, and get back the webhook_endpoint_id watch_company needs -- call this first if you do not already hold one. Idempotent: registering the same url twice returns the same id, so retrying is safe and never leaves you with two endpoints. secret is optional; supply one to verify the X-Plane-Signature on delivered payloads, omit it and one is generated (it is never returned). Registration itself is free -- the watch_company call that follows is what bills.

watch_company

Subscribe to a company's hiring events on a registered webhook -- webhook_endpoint_id must belong to the same customer as the authenticated key. Meters one watch unit. event_types names which events fire it, at least one of: opened (a req seen for the first time), reobserved (a known req seen again on a later day), reposted (a req that came back after it was gone), closed (a req verified gone from its source). Anything else is refused and creates nothing: a watch on a type that never fires would be billed and silent. A free key holds a limited number of watches at once; the one past that is refused with the same 402 the REST route raises, and cancelling a watch returns the slot.

list_watches

WHEN you need to see what you are subscribed to -- before adding a watch, or to find the id cancel_watch takes. Returns your live watches newest first, cancelled ones excluded: {} -> {"watches": [{"id": ..., "company_id": ..., "event_types": [...], "webhook_endpoint_id": ..., "status": "firing", ...}]}. Each also reports its heartbeat -- last_fired_at, fires_last_7d, fires_last_30d, fires_last_hour against max_fires_per_hour, rate_limited, and a one-word status -- so a watch silent because nobody is hiring reads differently from one that can never match or whose endpoint is failing. Never billed. Needs your key: a blank one returns a signup link.

cancel_watch

WHEN a watch should stop firing. Takes the id watch_company returned (or list_watches lists): {"watch_id": 42} -> {"id": 42, "canceled": true}. Idempotent -- cancelling a watch you already cancelled succeeds again. An id that is not one of your watches is an error, identical whether it belongs to someone else or does not exist. Frees the slot a free key's watch limit counts. Never billed. Needs your key: a blank one returns a signup link.

write_outcome

WHEN a company you reached through a signal converts -- a reply, a meeting, a closed deal -- report it back. Takes the company_id, a short outcome label of your choosing (e.g. "meeting_booked"), the observed_at time it happened and, optionally, the req_key of the role that prompted the outreach: {"company_id": 1234, "outcome": "meeting_booked", "observed_at": "2026-10-01T09:00:00Z"} -> {"id": 87}. Each call adds one record, visible only to your own account; nothing is overwritten, so report a later stage as a new outcome. Never billed. Needs your key: a blank one returns a signup link.

Source & listings: GitHub · server.json · Official MCP registry · Smithery · Glama