Greenhouse Sensor
What it ingests
Hiring activity from Greenhouse Harvest — the ATS objects, not the Job Board API:
| Harvest object | Ontology entity | Signal |
|---|---|---|
| Job | Project | job |
| Candidate | Person (identity) | candidate |
| Application | Issue | application |
| Scheduled interview | Meeting | interview |
| Scorecard | Comment | scorecard |
| Offer | ActionItem | offer |
A job is a body of work (the HubSpot deal analog); an application is the pipeline ticket. Candidates are identity — they resolve to Person via email/name, the same contract as a HubSpot contact. Each object kind has its own signal, so you can disable a noisy stream (scorecards, typically) from the source's bindings without disabling the source.
This is Greenhouse alone. Lever is a different ATS with a different API and stays a coming-soon catalog entry.
Read-only and poll-based against https://harvest.greenhouse.io/v1. Harvest webhooks exist but are configured in the Greenhouse UI, never via API, so they cannot be provisioned at install time.
Credential
References a greenhouse_harvest_api_key credential by id. The secret is a Harvest API key. Harvest authenticates with HTTP Basic: the key as the username and an empty password. Greenhouse is a single cloud API, so there is no site URL beside the key.
The Job Board API token is a different product and will not authenticate Harvest.
curl -sS -X POST https://your-corveil-host/api/credentials \
-H "Authorization: Bearer <admin>" -H "Content-Type: application/json" \
-d '{"type":"greenhouse_harvest_api_key","name":"acme-greenhouse","secret":"<harvest-api-key>"}'OAuth is a partner-app product and is out of scope in v1.
Config fields
| Key | Required | Notes |
|---|---|---|
enabled_objects | no | Comma-separated Harvest object kinds. Blank polls every supported kind. Unknown names are rejected at configure time. |
app_host | no | Hostname in your Greenhouse UI URLs (example: app4.greenhouse.io), used only to build links back to jobs and candidates. Blank omits links. Do not include a path. |
Supported object names: jobs, candidates, applications, interviews, scorecards, offers. scheduled_interviews is accepted as an alias for interviews.
There is no default_entity_type. This source emits multiple primary types, so a single override would silently mistype the rest.
Provider-side setup
- In Greenhouse, go to Configure → Dev Center → API Credential Management.
- Create a Harvest API key (not a Job Board token).
- Grant read access to the object kinds you intend to poll. Harvest keys are permissioned per endpoint — a key that can list jobs may still 403 on scorecards.
- Copy the key. Store only the key value, not
key:orBasic …. - Optional: note the host in your Greenhouse UI URL (
app4.greenhouse.io,app.greenhouse.io, …) forapp_host.
Wire-up
POST /api/sensors
{
"sensor_type": "greenhouse",
"name": "Acme Greenhouse",
"config": {
"enabled_objects": "jobs, candidates, applications, interviews, scorecards, offers",
"app_host": "app4.greenhouse.io"
},
"credentials": { "api_key": "<greenhouse_harvest_api_key credential id>" }
}Verify
Run Test connection. It authenticates the key against the first enabled object's list endpoint, so a 401/403 fails at save/test rather than on the first poll. Harvest keys are permissioned per endpoint; the probe uses an object this source is actually configured to read.
Once polling, each object kind's count appears in the per-poll summary log (jobs_collected, applications_collected, …). A kind stuck at zero while the others move is the signal to check that kind's Harvest permission.
How incremental sync works
Worth knowing, because it explains a few behaviours you may notice.
- Applications filter on
last_activity_after. Harvest never addedupdated_afterto the applications list. Filtering applications on the wrong parameter is ignored (not an error) and the list returns everything, so a poll would re-read the whole history every tick rather than failing. Corveil pins the literal with a test. - Everything else uses
updated_after/updated_at. - Each poll re-reads a two-minute overlap. Harvest list clocks can lag the write, so a strict resume would step permanently past a record that was briefly behind.
- Candidate poll also emits nested applications with the candidate's name attached, so the Person→Issue edge can fire without a second Harvest call. The standalone applications poll fills the rest;
sensor_eventsdedupes. - Deletes are invisible. A record vanishing from results is not treated as a deletion.
- Rate limits are shared. Harvest's published budget is 50 requests per 10 seconds per customer, shared with every other Harvest integration in the account. The source paces itself under that floor and backs off for the cycle on a 429 rather than retrying in-loop.
Not included
- Lever. A different ATS; do not configure this source against Lever.
- Job Board API. A different Greenhouse product; its token will not authenticate Harvest.
- OAuth / partner apps. Only needed for multi-customer marketplace distribution.
- Webhooks. Harvest supports them, but subscriptions are created in the Greenhouse UI, never via API — so they cannot be provisioned at install time. Polling is the supported path.
- EEOC, attachments, activity-feed N+1. Deferred.