DEV Community

Dodo Data
Dodo Data

Posted on

Reading SmartRecruiters job boards through the public Posting API: six things that will trip your parser

SmartRecruiters hosts the career pages of companies such as Bosch, ServiceNow, Experian and Western Digital, and it documents a public Posting API for reading the jobs those companies have published. No key, no login: a company's public postings are one GET away. We built a tool on it this month, and this is what the API returns once you read it at volume, measured on 20 October 2026.

Two calls: the list, then each job

The list call gives you a page of postings:

GET https://api.smartrecruiters.com/v1/companies/{company}/postings?limit=100&offset=0
Enter fullscreen mode Exit fullscreen mode
{ "offset": 0, "limit": 100, "totalFound": 672, "content": [ ... ] }
Enter fullscreen mode Exit fullscreen mode

Each item carries the title, department, function, location, employment type, experience level, a reference number and the release date. It does not carry the job text. For that you open each posting:

GET https://api.smartrecruiters.com/v1/companies/{company}/postings/{id}
Enter fullscreen mode Exit fullscreen mode

The answer has the job ad in four sections: companyDescription, jobDescription, qualifications and additionalInformation. On ServiceNow's jobs the four together were several thousand characters, and the median full description across 500 jobs from two companies was about 5,500.

So a full row costs one request per job. The useful consequence is that everything you filter on (title, place, date) is in the list, so filter there and open only the jobs you keep. Opening every posting to throw most of them away is where the time and the money go.

1. limit stops at 100

Asking for limit=200 returns "limit": 100 and 100 items, with no error. Page with offset in steps of the limit the server echoes back, not the one you sent, and stop when offset reaches totalFound or a page comes back short. ServiceNow listed 672 open jobs and Bosch 4,975 on the day we looked, so a full read of Bosch is 50 list pages before a single description.

2. A company that does not exist is not a 404

GET /v1/companies/ThisCompanyDoesNotExist123/postings
200 {"offset":0,"limit":100,"totalFound":0,"content":[]}
Enter fullscreen mode Exit fullscreen mode

A typo in the company identifier looks exactly like a company with no openings. If a zero is unexpected, say so to whoever typed the name rather than reporting an empty board as a success. The identifier is not case-sensitive, at least: servicenow, SERVICENOW and ServiceNow all returned the same 672 jobs.

3. fullLocation has holes in it

"Chicago, ILLINOIS, United States"
"London, , United Kingdom"
"Tel Aviv-Yafo, Tel Aviv District, Israel"
Enter fullscreen mode Exit fullscreen mode

fullLocation is city, region and country joined by commas, and when the region is empty the comma stays. 30 of ServiceNow's first 100 jobs had the empty slot. Our first version passed it through, and 137 of the first 500 rows read "London, , United Kingdom". Split and rejoin instead:

const where = String(loc.fullLocation ?? '')
    .split(',')
    .map((s) => s.trim())
    .filter(Boolean)
    .join(', ');
Enter fullscreen mode Exit fullscreen mode

Two smaller things in the same object: country is a lower-case ISO code (us, gb, in), and the region is often in capitals ("ILLINOIS") while the city is not. Upper-case the country if you compare it with anything else.

4. Remote and hybrid are two flags

location.remote and location.hybrid are separate booleans. On ServiceNow's first 100 jobs, 31 were remote, 31 hybrid and 38 had both false. Both false is the absence of a flag, not a statement that the job is on site, so we leave the workplace type empty there rather than guess.

5. Some fields are filled for form's sake

experienceLevel.label was "Not Applicable" on 87 of ServiceNow's first 100 jobs. Bosch filled no department at all on its 200 jobs we read, and put the team in function instead. Neither is wrong, but a filter on department returns nothing for Bosch, so keep both fields and let the reader choose.

6. Pay is in the text, and only some companies state it

The API has no pay field. ServiceNow writes a sentence into the job text for US and Canadian locations: "For positions in this location, we offer a base pay of $139,900 - $218,551". Reading those sentences gave a range on 142 of ServiceNow's 300 jobs and on 6 of Bosch's 200, 146 in US dollars and 2 in Canadian. A pay phrase has to sit right before the numbers for us to read it, and bonus, equity and on-target figures are left out, because a wrong salary is worse than none.

What we leave out on purpose

The job detail carries a creator object with the name and avatar of the person who posted the job. It is the one field in the answer that is about a person rather than a job, and nothing a job feed needs, so we never read it. E-mail addresses and phone numbers written inside the job text are removed too.

The tool

SmartRecruiters Jobs Scraper on Apify Store does all of the above: company names or career-page URLs in, filters applied on the list, one request per job kept, and 30 fields per job out as JSON. 500 jobs from Bosch and ServiceNow took 101 seconds. With several companies and a row cap, each company gets an equal share instead of the first big board filling the run. A monitor mode returns only the jobs that are new or changed since your last run. $1 per 1,000 jobs.

It returns the same rows as our Career Page Jobs Scraper, which reads twelve career-page systems including Greenhouse, Lever, Ashby and Workday, so SmartRecruiters jobs drop into the same table as everything else.

Top comments (0)