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.
-
17ba23161af5 2026-07-28T04:07:05.054716Z
- ~ changed /v1/account/pause
- ~ changed /v1/account/resume
- ~ changed /v1/attribution
- ~ changed /v1/clay/enrich
- ~ changed /v1/companies/{company_id}/ats-migrations
- ~ changed /v1/companies/{company_id}/enrichment
- ~ changed /v1/companies/{company_id}/first-hire
- ~ changed /v1/companies/{company_id}/hiring-pulse
- ~ changed /v1/companies/{company_id}/intent
- ~ changed /v1/companies/{company_id}/is-hiring
- ~ changed /v1/companies/{company_id}/open-reqs
- ~ changed /v1/companies/{company_id}/outcomes
- ~ changed /v1/companies/{company_id}/pre-action-brief
- ~ changed /v1/companies/{company_id}/repost-pain
- ~ changed /v1/events
- ~ changed /v1/jobs/search
- ~ changed /v1/keys
- ~ changed /v1/keys/{key_id}
- ~ changed /v1/keys/{key_id}/rotate
- ~ changed /v1/markets/role-demand
- ~ changed /v1/migration-import
- ~ changed /v1/onboarding
- ~ changed /v1/quickstart
- ~ changed /v1/reqs/search
- ~ changed /v1/usage
- ~ changed /v1/usage/dashboard
- ~ changed /v1/watches
- ~ changed /v1/watches/{watch_id}
- ~ changed /v1/webhook-deliveries
- ~ changed /v1/webhook-deliveries/{delivery_id}
- ~ changed /v1/webhook-deliveries/{delivery_id}/replay
- ~ changed /v1/webhooks
- ~ changed /v1/whoami
-
6b1e13440e43 2026-07-28T03:14:42.603849Z
- ~ changed /v1/account/pause
- ~ changed /v1/account/resume
- ~ changed /v1/attribution
- ~ changed /v1/clay/enrich
- ~ changed /v1/companies/{company_id}/ats-migrations
- ~ changed /v1/companies/{company_id}/enrichment
- ~ changed /v1/companies/{company_id}/first-hire
- ~ changed /v1/companies/{company_id}/hiring-pulse
- ~ changed /v1/companies/{company_id}/intent
- ~ changed /v1/companies/{company_id}/is-hiring
- ~ changed /v1/companies/{company_id}/open-reqs
- ~ changed /v1/companies/{company_id}/outcomes
- ~ changed /v1/companies/{company_id}/pre-action-brief
- ~ changed /v1/companies/{company_id}/repost-pain
- ~ changed /v1/events
- ~ changed /v1/jobs/search
- ~ changed /v1/keys
- ~ changed /v1/keys/{key_id}
- ~ changed /v1/keys/{key_id}/rotate
- ~ changed /v1/markets/role-demand
- ~ changed /v1/migration-import
- ~ changed /v1/onboarding
- ~ changed /v1/quickstart
- ~ changed /v1/reqs/search
- ~ changed /v1/usage
- ~ changed /v1/usage/dashboard
- ~ changed /v1/watches
- ~ changed /v1/watches/{watch_id}
- ~ changed /v1/webhook-deliveries
- ~ changed /v1/webhook-deliveries/{delivery_id}
- ~ changed /v1/webhook-deliveries/{delivery_id}/replay
- ~ changed /v1/webhooks
- ~ changed /v1/whoami
-
83cdf77615b7 2026-07-27T18:40:44.518926Z
- ~ changed /v1/companies/{company_id}/first-hire
- ~ changed /v1/companies/{company_id}/open-reqs
- ~ changed /v1/jobs/search
- ~ changed /v1/markets/role-demand
-
23545789aa0d 2026-07-27T18:32:07.962631Z
- ~ changed /v1/reqs/search
-
d7ee167e8897 2026-07-25T21:01:03.303030Z
- ~ changed /v1/watches/{watch_id}
-
518a4b4c8d16 2026-07-23T01:37:41.185827Z
- ~ changed /v1/watches
-
d76f7615d2e5 2026-07-20T15:36:51.423458Z
Initial schema snapshot.