API docs

Every Reqbeat hiring-signal endpoint, the field dictionary, and webhook signature verification — generated from the live API, never hand-maintained.

Read July 29, 2026 at 20:48 UTC · Refreshed every 3 hours

API reference

Endpoints

GET /v1/whoami What your API key resolves to
What the presented key resolves to. The response can only ever reflect the presented key's own customer, never another's.
POST /v1/quickstart Get started in five minutes
5-min quickstart: curl/Python/TS/MCP/Clay snippets with the caller's own live key injected, plus an auto-created watch on a high-motion company on `body.webhook_url` so a webhook fires shortly after signup. Not billable -- getting started must never cost quota. Idempotent: re-opening the quickstart never mints a second watch or webhook endpoint.
GET /v1/onboarding Guided watch and webhook onboarding
Guided watch/webhook onboarding flow: first watch -> first signed webhook delivered -> the 80%-quota expansion nudge. Not billable, same as `/quickstart`. Idempotent: re-polling never mints a second watch or endpoint and never double-counts a delivery.
GET /v1/keys List your API keys
Account-scoped key listing -- every key (active and revoked) for the caller's own customer, with `last_used_at` from the usage ledger.
POST /v1/keys Issue a new API key
Self-serve key issuance: a developer who already holds one key can mint another for their OWN account -- no operator step. The new key's `tier`/`scopes` come from the request body; its customer is always the AUTHENTICATED caller's own (never caller-supplied), so a key can never be issued for another tenant. The mint inherits the authenticated parent key's `first_touch_surface`, exactly as rotation carries it forward: an additional self-serve mint is the same acquisition as the key that authorized it.
DELETE /v1/keys/{key_id} Revoke an API key
Self-serve key revocation, scoped to the caller's own customer -- revoking another tenant's key id 404s, identical to revoking one that doesn't exist at all (fail-fast, no cross-tenant leak).
POST /v1/keys/{key_id}/rotate Rotate an API key
Agent-driven key rotation: mints a replacement key bound to the SAME account and revokes `key_id` in one call -- an autonomous agent rotates its own credential without an operator or a human seat in the loop, so usage keeps billing to the account. Scoped to the caller's own customer; `key_id` need not be the presented key. 404 for an id that doesn't exist, belongs to another tenant, or is already revoked -- indistinguishable, same as `delete_key`.
GET /v1/usage Your metered usage over a rolling window
Metered usage for the caller's customer over a rolling window -- `window` is hours (default 24). Aggregates across every key the customer holds, not just the one presented on this call.
GET /v1/usage/dashboard Your usage and billing dashboard
Usage & billing dashboard: per-key usage vs quota (green/amber/red), the account's spend-cap status, an in-context 402 upgrade prompt when either is exhausted, and the 80%-quota expansion nudge -- every number read off the same meter enforced at request time. Not billable: reading the dashboard must never itself cost quota.
POST /v1/account/pause Pause your account's metering
Pause-instead-of-cancel: suspends metering for the caller's OWN account -- every billable route 403s with `account_paused` until resumed. Watches are untouched.
POST /v1/account/resume Resume a paused account
Resume a paused account -- the next billable call the caller's keys make is enforced normally again.
GET /v1/companies/{company_id}/is-hiring Is this company hiring?
Cheap is-hiring gate -- ATS/board-only, freshness-floored: qualifies a company before an agent spends on a richer call. A free-tier account past its cost-to-serve cap degrades to a demand-signal-only path, returning `202 {status: "over_cap"}` instead of a served read -- the same convention as the cold-tail `202 {job_id, status: "crawling"}`.
GET /v1/companies/{company_id}/open-reqs A company's open reqs, deduped across boards
The company's current active reqs, deduped across boards -- ATS/board-only, freshness-floored. The same real req posted on two boards appears once, with both boards listed. An unresolvable `function` -- anything that is not a function taxonomy id -- is rejected with 422 instead of returning a silently-empty result. `limit` is bounded: an oversized page is rejected with 422 rather than truncated.
GET /v1/companies/{company_id}/enrichment Company firmographics from ATS and job boards
Basic ATS/board-derived company firmographics -- no LinkedIn-derived field ever appears here by construction. Freshness-floored: `boards` only reflects ATS/board sources observed at or before the floor cutoff.
GET /v1/companies/{company_id}/hiring-pulse A company's hiring velocity and momentum
A company's hiring velocity/direction/momentum in one call -- the first paid route, honoring `max_age` (seconds). Cold (no ATS/board data at all, and `max_age` demands freshness beyond nothing) returns `202 {job_id, status: "crawling"}` instead of a synchronous body.
GET /v1/reqs/search Find companies hiring for a role
Reverse who's-hiring-for {role, geo}: companies with active reqs matching `role` (posting title) / `geo` (country) / `since` (first-seen lower bound), deduped by company, each with its full hiring pulse and the specific reqs that matched. ATS-only, freshness-floored, keyset-paginated via `next_cursor` (the last page's highest `company_id`). `role` 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, and a role that matches nothing comes back as an empty page rather than an error. 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. `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.
GET /v1/jobs/search Search open roles across companies
Flat, role-granular job search: the individual open roles across companies matching `role` (function) / `geo` (country) / `since` (first-seen lower bound), one row per logical req. ATS-only, freshness-floored, PK-ordered and keyset-paginated via the opaque `next_cursor` (the page's last `company_id:req_key`). The company-granular reverse view is `/v1/reqs/search`; full multi-board dedup for one company is `/v1/companies/{company_id}/open-reqs`. An unresolvable `role` -- anything that is not a function taxonomy id -- is rejected with 422 instead of returning a silently-empty result. `limit` is bounded: an oversized page is rejected with 422 rather than truncated -- page through the full set with `next_cursor` instead of raising `limit`.
GET /v1/companies/{company_id}/first-hire A company's first hire in each function
The earliest first-in-function event per function -- detects a new budget line. Sourced from the role-slot recompute aggregate, not a specific board posting; `source_board` says so honestly rather than being excluded outright. Free-tier reads are history-capped. An unresolvable `function` -- anything that is not a function taxonomy id -- is rejected with 422 instead of returning a silently-empty result.
GET /v1/companies/{company_id}/repost-pain A company's hardest-to-fill reqs
Hard-to-fill reqs by repost count, hardest first -- ATS-only, freshness-floored. Free-tier reads are history-capped.
GET /v1/companies/{company_id}/ats-migrations A company's ATS vendor switches
A company's ATS-vendor switches -- ledger-backed, with a provenance-wrapped `occurred_at`. No ATS-only filter needed: ATS platform accounts have no LinkedIn dimension.
GET /v1/markets/role-demand Market-wide demand for a role
Market-wide (no single-company scope) active-req demand series for `role` / `geo` -- ATS-only by construction. An unresolvable `role` -- anything that is not a function taxonomy id -- is rejected with 422 instead of returning a silently-empty result.
GET /v1/companies/{company_id}/pre-action-brief A company's whole pre-action brief in one call
The motion atoms + history primitives pre-joined into one bounded, compact call -- an agent's whole pre-action context in one round-trip instead of five. Honors `max_age` (seconds); cold returns `202 {job_id, status: "crawling"}`, same as hiring-pulse.
GET /v1/events The change feed since your cursor
The `since=cursor` diff feed -- ledger events with `event_seq > since`, ordered ascending, plus a `next_cursor` an agent replays instead of polling or re-scraping. Meters one `change` unit per event RETURNED, independent of poll count: an empty page costs nothing. A free-tier caller is change-feed-gated to the same freshness floor every other free atom uses. `limit` is bounded: an oversized page is rejected with 422 rather than truncated -- replay with `next_cursor` instead of raising `limit`.
POST /v1/webhooks Register a webhook endpoint
Register a webhook delivery target -- `secret` is stored as the endpoint's HMAC signing key, scoped to the caller's own customer, and never logged. Idempotent on (customer, url): re-registering the same target returns the SAME id rather than a second row, so a retrying caller ends up with one endpoint it can hand straight to `POST /v1/watches`. Omitted `secret` is generated server-side.
POST /v1/watches Watch a company or a saved search
Subscribe to hiring events on a registered webhook -- scoped to the caller's own customer: `webhook_endpoint_id` must belong to the same customer. Meters one `watch` unit. The watch covers one company (`company_id`) or a saved search (`criteria`) -- exactly one, and a criteria outside the closed vocabulary is a 422 raised before any row is written. A free key gets exactly ONE watch; the second is a 402 carrying the upgrade path, checked before the watch is created, so a walled call leaves no subscription behind.
GET /v1/watches List your live watches
The caller's own live watches -- what is currently subscribed on their customer, newest first, canceled ones excluded. A "Watch this search" signup lands with a watch already installed, and this is where its owner confirms what it watches and which endpoint it delivers to. Not billable, like the other self-serve introspection reads (`/webhook-deliveries`): reading your own subscriptions is account management, not metered corpus access.
DELETE /v1/watches/{watch_id} Cancel a watch
Cancel a watch, scoped to the caller's own customer -- canceling another tenant's watch id 404s, identical to canceling one that doesn't exist (fail-fast, mirrors `delete_key`). Canceling one's own already-canceled watch is a 204 no-op, so the verb stays idempotent.
GET /v1/webhook-deliveries Your webhook delivery log
The caller's own self-serve webhook delivery log -- every delivery for their own customer, most recent first.
GET /v1/webhook-deliveries/{delivery_id} One webhook delivery's detail
One delivery's detail, scoped to the caller's own customer.
POST /v1/webhook-deliveries/{delivery_id}/replay Replay a webhook delivery
Reopens a webhook delivery for the next dispatcher tick to redeliver -- a self-serve replay debugger. Scoped to the caller's own customer, idempotent, and within the replay SLA window.
POST /v1/companies/{company_id}/outcomes Report a conversion outcome
Write back a conversion outcome for `company_id`. Appends to the caller's own outcome labels, which are retention-protected.
GET /v1/attribution See which delivered signals you won
"Signal #N -> you won." -- the caller's own outcome labels joined back to the signals this plane actually delivered them. A label for a company we never signalled is deliberately absent: this is an attribution view, not a label dump. `attributed_14d` counts the ones that landed in the last 14 days.
GET /v1/companies/{company_id}/intent A company's calibrated intent score
The calibrated-intent score + coverage meter for `company_id` -- computed live off every customer's submitted outcome labels. `score` is `None` below the coverage-gating minimum (never a fabricated number).
POST /v1/clay/enrich Enrich a company for a Clay column
Clay HTTP-provider hiring-motion enrichment column -- entity-resolves `domain`/`name` to a company via the local plane cache, then composes company enrichment and hiring pulse into a flat, Clay-column-shaped response: no field beyond this declared model ever leaks, every field is ATS-only and provenance-backed by its own primitive. Metered to the presented (Clay account) key. A miss is a 200, not a 404: a 404 blanks the Clay cell, which the customer reads as "not hiring". It carries `coverage_status`, a `message`, a `checked_at` -- and NULL motion fields, never zeros. The call still meters.
POST /v1/migration-import Import a company list from your current vendor
Migration import from an incumbent: a switcher's uploaded Coresignal/Apollo company list, resolved to the panel and enriched with the hiring-motion column; every miss is routed to a real coverage request, so switching cost drops to near zero without a second "please crawl this" mechanism. Not billable -- same convention as `/quickstart` and `/onboarding`.

Webhook signature verification

Algorithm
HMAC-SHA256
Signature header
X-Plane-Signature
Idempotency header
X-Plane-Idempotency-Key

Recompute hmac_sha256(your_webhook_secret, raw_request_body) and compare it (constant-time) to the X-Plane-Signature header value. The X-Plane-Idempotency-Key header is stable across retries of the same delivery -- dedupe on it, don't process the same value twice.

Field dictionary

Field Type Description
account_hard_cap integer Account Hard Cap
account_id integer Account Id
account_soft_cap integer Account Soft Cap
account_status string Account Status
account_used integer Account Used
active_reqs integer Active Reqs
as_of string As Of
attempt_count integer Attempt Count
attributed_14d integer Attributed 14D
board string Board
boards array Boards
bucket string Bucket
bucket_start string Bucket Start
by_function object By Function
by_meter_class object By Meter Class
calls integer Calls
changes integer Changes
checked_at string Checked At
companies array Companies
company_id integer Company Id
company_name Company Name
confidence Confidence
contact_email string Contact Email
country Country
coverage number Coverage
coverage_status string Coverage Status
covered integer Covered
created_at string Created At
criteria Criteria
ctx object Context
customer_id string Customer Id
delivered_at Delivered At
detail array Detail
direction Direction
domain Domain
enrichment object Enrichment
enrichment_as_of Enrichment As Of
event_seq integer Event Seq
event_type string Event Type
event_types array Event Types
events array Events
expansion_nudge_triggered boolean Expansion Nudge Triggered
first_seen string First Seen
first_webhook_delivered boolean First Webhook Delivered
forced_fresh integer Forced Fresh
from_vendor string From Vendor
function Function
hq_country Hq Country
id integer Id
input Input
is_hiring Is Hiring
is_surge Is Surge
items array Items
jobs array Jobs
key string Key
key_id integer Key Id
keys array Keys
label_count integer Label Count
last_used_at Last Used At
loc array Location
matched_reqs array Matched Reqs
message Message
migrations array Migrations
misses array Misses
momentum array Momentum
motion_as_of Motion As Of
msg string Message
name Name
negative_count integer Negative Count
new_roles_30d New Roles 30D
next_cursor integer Next Cursor
observed_at string Observed At
occurred_at
open_req_count Open Req Count
outcome string Outcome
paused boolean Paused
plan Plan
positive_count integer Positive Count
pulse
quota integer Quota
raw_title Raw Title
reason string Reason
referral_credit_units integer Referral Credit Units
repost_count
req_key Req Key
reqs array Reqs
resolved array Resolved
revoked_at Revoked At
rows array Rows
scopes Scopes
score Score
secret Secret
series array Series
sla_credit_units integer Sla Credit Units
snippets object Snippets
source_board Source Board
status string Status
step string Step
tier string Tier
title Title
to_vendor string To Vendor
total integer Total
type string Error Type
upgrade_prompt
upgrade_url string Upgrade Url
url string Url
used integer Used
value Value
velocity Velocity
watch_id integer Watch Id
watches integer Watches
webhook_endpoint_id integer Webhook Endpoint Id
webhook_url string Webhook Url

Changelog

Every schema change is recorded automatically the moment the API's contract moves — this log is generated, never edited by hand.