<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Detroit Web Agent]]></title><description><![CDATA[Detroit Web Agent]]></description><link>https://detroitwebagent.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 28 Oct 2026 04:42:42 GMT</lastBuildDate><atom:link href="https://detroitwebagent.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to pull open jobs from Greenhouse, Lever and Ashby without scraping HTML]]></title><description><![CDATA[Public JSON endpoints for Greenhouse, Lever and Ashby job boards, normalized in about 20 lines of Python, plus when a ready-made tool is worth it.]]></description><link>https://detroitwebagent.hashnode.dev/how-to-pull-open-jobs-from-greenhouse-lever-and-ashby-without-scraping-html</link><guid isPermaLink="true">https://detroitwebagent.hashnode.dev/how-to-pull-open-jobs-from-greenhouse-lever-and-ashby-without-scraping-html</guid><category><![CDATA[api]]></category><category><![CDATA[Python]]></category><category><![CDATA[web scraping]]></category><dc:creator><![CDATA[Detroit Web Agent]]></dc:creator><pubDate>Tue, 20 Oct 2026 19:34:51 GMT</pubDate><content:encoded><![CDATA[<p>Most applicant-tracking systems publish their job boards as public JSON. No login, no HTML parsing, no headless browser. Here is how to use them, and when a ready-made tool is worth it.</p>
<h2>The three public endpoints</h2>
<table>
<thead>
<tr>
<th>ATS</th>
<th>Endpoint</th>
<th>Slug example</th>
</tr>
</thead>
<tbody><tr>
<td>Greenhouse</td>
<td><code>https://boards-api.greenhouse.io/v1/boards/{slug}/jobs?content=true</code></td>
<td><code>gitlab</code></td>
</tr>
<tr>
<td>Lever</td>
<td><code>https://api.lever.co/v0/postings/{slug}?mode=json</code></td>
<td><code>palantir</code></td>
</tr>
<tr>
<td>Ashby</td>
<td><code>https://api.ashbyhq.com/posting-api/job-board/{slug}</code></td>
<td><code>ashby</code></td>
</tr>
</tbody></table>
<p>The slug is the company's board name, visible in its careers-page URL (<code>boards.greenhouse.io/gitlab</code>, <code>jobs.lever.co/palantir</code>, <code>jobs.ashbyhq.com/ashby</code>).</p>
<h2>Normalize them in ~20 lines of Python</h2>
<pre><code class="language-python">import requests

def greenhouse(slug):
    r = requests.get(f"https://boards-api.greenhouse.io/v1/boards/{slug}/jobs", timeout=30).json()
    return [{"title": j["title"], "location": (j.get("location") or {}).get("name"),
             "url": j["absolute_url"], "posted": j.get("updated_at")} for j in r["jobs"]]

def lever(slug):
    r = requests.get(f"https://api.lever.co/v0/postings/{slug}", params={"mode": "json"}, timeout=30).json()
    return [{"title": j["text"], "location": j["categories"].get("location"),
             "url": j["hostedUrl"], "posted": j["createdAt"]} for j in r]

def ashby(slug):
    r = requests.get(f"https://api.ashbyhq.com/posting-api/job-board/{slug}", timeout=30).json()
    return [{"title": j["title"], "location": j.get("location"),
             "url": j["jobUrl"], "posted": j.get("publishedAt")} for j in r["jobs"]]

for fn, slug in [(greenhouse, "gitlab"), (lever, "palantir"), (ashby, "ashby")]:
    jobs = fn(slug)
    print(slug, len(jobs), jobs[0]["title"])
</code></pre>
<p>Add a <code>remote</code> flag by checking the location string for "remote", and store <code>posted</code> as ISO dates (Lever gives epoch milliseconds).</p>
<h2>Finding the slug from a domain</h2>
<p>Fetch the company's <code>/careers</code> page and look for links to <code>boards.greenhouse.io/</code>, <code>jobs.lever.co/</code> or <code>jobs.ashbyhq.com/</code>. Many sites embed the board in an iframe or script tag; the slug is in that URL. When there is no match, try the domain name itself as the slug.</p>
<h2>When a ready-made tool beats your script</h2>
<p>If you track hundreds of companies, you also need Workable, Recruitee and SmartRecruiters, retries, and de-duplication. Disclosure: I built an Apify actor for exactly that: <a href="https://apify.com/transparent_meteorite/ats-open-roles">ats-open-roles</a>. Give it company slugs or domains, get normalized open roles back. It is pay-per-result at $0.002 per job, so 1,000 roles cost about $2. If you only need a handful of companies, the script above is the better deal.</p>
]]></content:encoded></item></channel></rss>