Company Job Boards API

Read company-direct job boards from four ATS providers in one schema

The ATS Job Boards API returns company-direct job postings pulled straight from applicant-tracking systems (Greenhouse, Lever and more) as clean JSON.

no credit card1,000 free credits · instant API key · live in 10 seconds
Missing a Company Job Boards endpoint, or need a source we don't have yet?Contact us real people · same-day reply.
C
/ats-jobs/v1

4 active endpoints. Every call is 1 credit.

  • POST/ats-jobs/v1/company_jobs
  • POST/ats-jobs/v1/job_detail
  • POST/ats-jobs/v1/search
  • POST/ats-jobs/v1/providers

What Company Job Boards endpoints does ReefAPI ship?

4 live read endpoints. Read-only data API: no writes, no account actions, no dashboard access on the target site.

4 endpoints

company_jobs

1 cr

all open jobs for a board → {jobs[]{id,title,url,company,location,locations,...}, count, tota…

required
ats, company
optional
limit, cursor, page

job_detail

1 cr

one fully-populated job (description + requirements/benefits) by ats + company + job_id.

required
ats, company, job_id
optional

search

1 cr

cross-company job search over the Workable aggregator (170K jobs) → jobs[].

required
query
optional
ats, limit, cursor, page

providers

1 cr

the supported ATS providers + their capabilities.

required
optional

Every parameter, every allowed value →

Company Job Boards API

3 of 4 endpoints, ready to run

View docs ↗

Every open role on one company's board, from the board itself. No paging: the whole thing comes back in a single call.

1 credit2 required · 1 optional
POST/ats-jobs/v1/company_jobs
idle
// Press "Try it" and this pane shows exactly what the
// live site returned this second — including an empty
// result, if that is the truth. No key, no account.

How the Company Job Boards API works

Company Job Boards is a normal ReefAPI surface — the same four rules that hold for every other engine on the key.

01
Authenticate
x-api-key header

No OAuth app, no request signing, no per-site account. One key covers all 185 engines.

02
Call
POST /ats-jobs/v1/…

Every route is a POST with a JSON body. Parameters are validated against the published schema before anything is charged.

03
Pay
1 credit per call

Credits, not seats. Failed and blocked calls are never charged, and cache hits cost nothing.

04
Read
{ ok, data, meta, error }

One envelope everywhere. meta carries latency_ms, record_count and the endpoint that answered.

Skip the aggregator and read the employer's own board

An aggregator row is a copy with a redirect on the end of it. A board row is the employer's own record, with the employer's own apply link and its own requisition id — which is what you want if you are deduplicating, or if you care whether a role is actually still open.

01company_jobs
POST/ats-jobs/v1/company_jobs
{"ats": "greenhouse", "company": "gitlab"}

217 open roles came back in one call, in two seconds, with no cursor. A second board on a different provider returned 89 the same way.

02job_detail
POST/ats-jobs/v1/job_detail
{"ats": "greenhouse", "company": "gitlab", "job_id": "<jobs[].id>"}

Adds the full description, and carries posted_date and updated_date as separate ISO timestamps — so you can tell a repost from a genuinely new role.

The employer's own board, in one shape across four providers, with an apply_url that points at the employer rather than at a redirect.

request
curl -X POST https://api.reefapi.com/ats-jobs/v1/company_jobs \
  -H "x-api-key: $REEF_KEY" \
  -H "content-type: application/json" \
  -d '{"ats":"greenhouse","company":"gitlab"}'
response envelope
{
  "ok": true,
  "data": { … },
  "meta": {
    "api": "ats-jobs",
    "endpoint": "company_jobs",
    "mode": "live",
    "latency_ms": …,
    "record_count": …
  },
  "error": null
}

The four providers, what to pass as company, and what each id looks like

The providers action returns exactly four keys and they do not behave the same way. Three of them serve one company's board; the fourth is a cross-company index and is the only one the search action accepts. Job ids are shaped differently per provider and mean nothing outside their own board. The table was measured on greenhouse/gitlab, lever/spotify, ashby/ramp and a workable search for 'python engineer'.

atsWhat to pass as companyjobs[].id shapeWhat we measured
greenhouseThe board token from the job-board URL, e.g. 'gitlab'10-digit numeric string: 8503792002, 8556658002company_jobs returned total 219. requisition_id is filled ('6263'); compensation was null.
leverThe org slug, e.g. 'spotify'Lowercase UUID: a0fa7da3-4c3c-4fa2-97bd-7d6eb01eb9e5total 90. employment_type 'full_time' and team ('Advertising R&D') filled; compensation null.
ashbyThe org slug, e.g. 'ramp'Lowercase UUID: 34413f8d-26bf-4bbc-8ade-eb309a0e2245total 134, and the only provider that returned pay: compensation{} plus salary{min:211400, max:290600, currency:'USD', source:'structured'}.
workableUse the search action rather than company_jobs22-character base62 code: oUJx9skkD6SnA9zEFabpodsearch 'python engineer' reported total 2226. company_jobs against Workable account slugs returned ok:true with total 0 in every test.
posted_date(all providers)ISO-8601, but the flavor differsGreenhouse carries a US-Eastern offset ('2026-04-17T05:58:03-04:00'), Lever and Ashby are UTC with sub-second precision, Workable ends in 'Z'. Normalize before sorting across boards.
count vs total(all providers)Two different numberscount is the rows in this response, total is the size of the whole board or result set. limit=3 on gitlab gave count 3 and total 219.
company echo(all providers)Not always what you passedGreenhouse echoes the board's display name ('GitLab'); Lever, Ashby and Workable echo the lowercase slug. company_slug always holds the slug.
bad valuesats / companyRejected differentlyAn ats outside the four is INVALID_PARAM with the allowed list attached. An unknown board token is NOT_FOUND. Passing 'greenhouse' to search is INVALID_PARAM, because only workable aggregates across companies.

Job ids do not collide across providers, and they are not portable either. A Lever UUID sent with ats=greenhouse returned NOT_FOUND, and a Greenhouse numeric id sent with ats=lever did the same. Keep the (ats, company, id) triple together as your key.

Four providers, one shape, and the two places it does not hold

Measured on 2026-08-28 against boards on all four providers and by walking the aggregator search. Two of these lines go against us.

No account anywhere — these boards are public by design

An ATS board exists to be read by applicants, so there is no login, no API key of the employer's, and no account of yours in the path. Nothing here can get a seat of yours limited. The only thing between you and the board is your ReefAPI key.

There is no paging on a company board — you get all of it

company_jobs returns the whole board in one call. One Greenhouse board came back with 217 roles in two seconds; a Lever board returned 89 in three. There is no cursor to carry and no total to compare against a reachable number, because the number returned is the total.

🔴 The cross-company search silently ignores `page`

Against us, and the kind of bug that quietly halves a dataset. We asked the same query for page 1, page 8 and page 40: all three returned the same 25 ids, every time. Depth on this action comes from limit instead, and that works properly — limit 200 returned 200 rows, 198 of them unique, across ten upstream requests, with a reported total of 897. Use limit; ignore page until this is fixed.

🔴 Workable boards resolved to zero on every token we tried

Against us. company_jobs with ats workable returned ok:true with total 0 for five different board tokens, and a clean 404 for a sixth. The other three providers answered normally with the same call shape. Workable is still worth having — the cross-company search runs on its aggregator and returned 897 hits for one keyword — but for a named company board, use Greenhouse, Lever or Ashby today.

Timestamps are the best in this batch

Rows carry posted_date and updated_date as separate ISO-8601 values with a timezone offset, to the second. One role we opened was posted in April and updated in August — a distinction none of the aggregator engines in this batch can make, because they carry at most one date.

No salary field, on any provider

compensation came back null on the board rows and on the detail we opened. Some employers do publish a band inside the description; nothing parses it into a number for you here. If pay is the point, pair this with levels-fyi or indeed.

The location field is two fields, and both are filled

location is the employer's own free-text string ("Remote, Italy") and locations is the normalised list next to it, alongside a boolean remote and a workplace_type. department comes through as the employer wrote it; team was frequently null.

The identifier is the board token, not the company name

company is the slug in the board's own URL — the subdomain or org path — not a brand name, and a wrong token is a clean NOT_FOUND rather than a wrong company's jobs. If you only have a domain, enrich-hiring probes all four providers for you and reports which one answered.

What comes back, and what does not

Postings and the employer: id, title, url, company, location, department, employment type, remote flag, requisition id, timestamps, description and the employer's own apply link. No hiring-manager names, no recruiter contacts, no applicant data.

What people build with Company Job Boards

The jobs this data is most often used for.

4

endpoints

1

credit per call

01

Sales-intelligence teams call company_jobs to detect hiring signals (a company opening 10 sales roles) from the source ATS.

02

Job boards run company_jobs across a company list to aggregate fresh, first-source postings.

03

Labor-market analysts use search to measure open-role volume by department and location over time.

What Company Job Boards data costs

The cheapest call here is 1 credit, so $15/mo (Pro) buys 10,000 of them — $1.50 per 1,000 credits. Credits roll over and never expire, and failed or blocked calls are not charged.

Full pricing →
$0.67–$1.50 / 1,000 credits
  • 1,000 free credits on signup, no card
  • One key, all 185 APIs, one credit pool
  • Failed and blocked calls are never charged
  • Credits roll over and never expire

Call it in two lines

Sign up, get 1,000 credits and one key that works on every engine. Then this is the whole protocol.

curl
curl -X POST https://api.reefapi.com/ats-jobs/v1/company_jobs \
  -H "x-api-key: $REEF_KEY" \
  -H "content-type: application/json" \
  -d '{"ats":"greenhouse","company":"gitlab"}'
python
import requests

r = requests.post(
    "https://api.reefapi.com/ats-jobs/v1/company_jobs",
    headers={"x-api-key": REEF_KEY},
    json={
  "ats": "greenhouse",
  "company": "gitlab"
},
)
print(r.json()["data"])
FAQ

Have a question? We got answers.

The questions people actually ask before wiring up Company Job Boards.

Get a free key →
Which ATS providers are actually supported?

The providers action returns four keys, alphabetically: ashby, greenhouse, lever, workable. Anything else is rejected at the parameter level with INVALID_PARAM and the allowed list in error.detail.allowed, so a typo in the provider name never turns into a silently empty result.

Is a job id unique across providers?

No, and the shapes are different enough that you can usually tell them apart: Greenhouse ids are 10-digit numeric strings, Lever and Ashby both use lowercase UUIDs, Workable uses a 22-character base62 code. An id is only meaningful inside one board, which we confirmed both ways: a Lever UUID with ats=greenhouse and a Greenhouse id with ats=lever both returned NOT_FOUND. Store the (ats, company, id) triple as the key.

Which provider gives me salary data?

Ashby, in our measurements. A Ramp job came back with a compensation object holding tiers and components, plus a normalized salary{min:211400, max:290600, currency:'USD', source:'structured'}. compensation was null on every Greenhouse, Lever and Workable job we read, so if pay is the point of your query, start with Ashby boards.

Why does search reject ats=greenhouse?

Only Workable runs a public cross-company index, so search is a Workable-only action and ats defaults to 'workable'. Passing greenhouse, lever or ashby returns INVALID_PARAM with a message pointing you at company_jobs. To search across Greenhouse boards you have to enumerate the board tokens yourself and call company_jobs per company.

What is the difference between count and total?

count is how many rows are in this response, total is the size of the underlying set. Asking gitlab for 3 jobs returned count 3 and total 219; spotify reported total 90 and ramp 134. On search, total is the size of the query's result set, 2226 for 'python engineer'. Use total to decide whether to page, not to size your array.

A job says remote: true but location_parsed.remote is false. Which is right?

Both, because they answer different questions. The top-level remote flag reflects whether the posting offers a remote option at all, which is why a Ramp job listed in New York with 'Remote (US)' among its locations reads remote:true. location_parsed.remote reflects only the primary location string, so it stays false unless that string itself says remote. workplace_type ('remote', 'hybrid', 'onsite') is usually the field you actually want.

Can I list one company's jobs on Workable?

Not reliably today. company_jobs with ats=workable and account slugs taken straight from search results returned ok:true with total 0 and an empty jobs array, and job_detail for those same ids returned NOT_FOUND. Treat Workable as a search-only surface, and use company_jobs for greenhouse, lever and ashby, where we verified live boards of 219, 90 and 134 jobs.

What do is_new and requisition_id mean?

is_new was true on the Workable rows posted within the previous week and false on the older Greenhouse, Lever and Ashby postings we read, so it is a freshness hint rather than a state you can rely on. requisition_id is the employer's own internal number, and only Greenhouse supplied it ('6263'); it is null on the other three providers.

What is the Company Job Boards API?

Company Job Boards API is a ReefAPI endpoint group for jobs straight from company career pages. It returns live JSON through POST requests under /ats-jobs/v1.

Is the Company Job Boards API free to try?

Yes. ReefAPI starts with 1,000 free credits, no card required. Company Job Boards calls use the same shared credit balance as every other ReefAPI engine.

Do I need a Company Job Boards login or account?

No login to Company Job Boards is needed for the API response. You call ReefAPI with your x-api-key header, and the playground can run live examples before you create a production key.

How fresh is the Company Job Boards data?

The page example is captured from a live company_jobs call, and production requests fetch live data through ReefAPI rather than a static sample.

How many credits does the Company Job Boards API use?

Company Job Boards actions currently cost 1 credit per successful call. Failed or blocked calls are free, and all APIs draw from one credit pool.

Can I call Company Job Boards from an AI assistant or MCP client?

Yes. Connect ReefAPI once through MCP and your assistant can call ats-jobs actions with the same key, credit pool and JSON envelope used by normal REST requests.

15 Jobs & Hiring APIs on the same key

One key, one credit pool, one response envelope. If you are pulling Company Job Boards, you are one call away from the rest of the category — no second contract, no second integration.

Need something this API does not do?

Name the endpoint, the field, or a source we do not carry yet. We ship new APIs every week and you would be first to get the key. Real people read every message and reply the same day.

0/4000

No account needed · we reply from [email protected]

Try it on your own data before you pay anything

The call above is the real endpoint, not a recording. A free key gives you 1,000 credits, the other 184 APIs, and the same envelope everywhere.

Endpoints, parameters and credit costs on this page are read from the live catalog and cannot drift from what the API accepts. Field notes were captured on 2026-08-28.