Skip to main content

API reference

Post jobs from your ATS

Use the Jobby.dev REST API to create live events and attach roles directly from your ATS (Greenhouse, Ashby, Lever).

Last updated

If your team manages roles in Greenhouse / Ashby / Lever, you can push them straight to Jobby.dev whenever they go live — no copy-paste between tools. ~30 minutes; the heaviest lift is mapping ATS fields to Jobby.dev role fields.

Architecture

Two flows wired together:

  1. ATS → Jobby.dev when a role opens. We create a Jobby.dev role and attach it to your next live event window.
  2. Jobby.dev → ATS when a candidate advances. The Merge-powered ATS sync (recruiter integrations) handles this leg automatically once you've authorized the integration.

1. ATS → Jobby.dev: trigger

Three options depending on your ATS:

  • Greenhouse— "Job Created" webhook in the Greenhouse webhooks UI.
  • Ashby— "Job Posted" webhook in Ashby's integrations.
  • Lever— Lever doesn't emit a job-created webhook; poll their /v1/postings endpoint on a 5-minute cron and diff against last-known state.

2. Map ATS fields to Jobby.dev role fields

The mapping isn't 1:1; here's what to plumb:

ATS fieldJobby.dev role fieldNotes
Job titletitle1:1.
Job posting bodydescriptionStrip HTML; collapse whitespace.
Skills / tagsrequired_skillsTrim to 3-7 strongest tags.
Salary rangecompensation_min + _maxConvert to USD if needed.
Locationlocation_city + _countryParse out remote / hybrid signal separately.
Visa sponsorshipvisa_sponsorshipYes / no / case-by-case.

3. Create the role on Jobby.dev

async function createJobbydevRole(atsJob) {
  const role = mapAtsToJobbydev(atsJob);
  const res = await fetch("https://jobby.dev/api/v1/jobs", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.JOBBYDEV_API_TOKEN}`,
      "Content-Type": "application/json",
      "Idempotency-Key": `ats-${atsJob.id}`,
    },
    body: JSON.stringify(role),
  });
  if (!res.ok) throw new Error(await res.text());
  const { id: jobbydevRoleId } = await res.json();
  // Persist the mapping so subsequent updates target the same role
  await db.atsJobMapping.upsert({
    ats_id: atsJob.id,
    jobbydev_role_id: jobbydevRoleId,
  });
}

Note the Idempotency-Key— re-firing the webhook from your ATS won't create duplicate roles.

4. Attach to a live event

Roles need to be attached to an event to be visible to seekers. Two patterns:

  • Auto-attach to the next scheduled event. Simplest. The team's next live event picks up everything new since last live.
  • Per-role event. Power Play tier — every role gets a dedicated 1-hour window. Higher signal, more scheduling load.

5. Update + delete

Subscribe to your ATS's job-updated and job-closed events. Mirror via PATCH /api/v1/jobs/{id} and DELETE /api/v1/jobs/{id} using the mapping from step 3.

6. Test

Open a test role in your ATS; confirm it appears on Jobby.dev within ~10 seconds. Close it; confirm it disappears.

Production hardening

  • Rate limits: bulk imports may exceed 30 writes/minute. Throttle to one role per 2 seconds during initial backfill.
  • PII: never copy candidate data from your ATS into a Jobby.dev role. Roles are public; they shouldn't carry private notes or candidate identifiers.
  • Audit: log every Jobby.dev API call your bridge makes — useful when reconciling discrepancies.

Related reading