<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: HwangByeongSeon</title>
    <description>The latest articles on DEV Community by HwangByeongSeon (@hwangbyeongseon).</description>
    <link>https://dev.to/hwangbyeongseon</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4091698%2Fa6f32fc3-87f3-4326-a16c-7799fb49f952.png</url>
      <title>DEV Community: HwangByeongSeon</title>
      <link>https://dev.to/hwangbyeongseon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hwangbyeongseon"/>
    <language>en</language>
    <item>
      <title>I measured what 12 ATS APIs actually return, across 1.5M job postings</title>
      <dc:creator>HwangByeongSeon</dc:creator>
      <pubDate>Mon, 24 Aug 2026 06:27:19 +0000</pubDate>
      <link>https://dev.to/hwangbyeongseon/i-measured-what-12-ats-apis-actually-return-across-15m-job-postings-33l9</link>
      <guid>https://dev.to/hwangbyeongseon/i-measured-what-12-ats-apis-actually-return-across-15m-job-postings-33l9</guid>
      <description>&lt;p&gt;BambooHR's public job endpoint has an &lt;code&gt;isRemote&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;Across &lt;strong&gt;50,616 postings from 9,711 live BambooHR boards, not one had it set.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not "most were blank". Not one. If you write a client that trusts that field, you will conclude&lt;br&gt;
that no company using BambooHR is hiring remotely, and you will be wrong in a way no error message&lt;br&gt;
will ever tell you about.&lt;/p&gt;

&lt;p&gt;That is the kind of thing you only find by measuring, so I measured all of it.&lt;/p&gt;
&lt;h2&gt;
  
  
  What this is
&lt;/h2&gt;

&lt;p&gt;Twelve applicant tracking systems publish an open JSON endpoint that their customers' careers pages&lt;br&gt;
read. No key, no login, no HTML parsing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s1"&gt;'https://boards-api.greenhouse.io/v1/boards/stripe/jobs?content=true'&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s1"&gt;'https://api.lever.co/v0/postings/spotify?mode=json'&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s1"&gt;'https://api.ashbyhq.com/posting-api/job-board/notion?includeCompensation=true'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plenty of articles list these endpoints. What none of them tell you is &lt;strong&gt;which fields actually come&lt;br&gt;
back populated&lt;/strong&gt;, which is the only thing that matters once you start building. "Supports salary"&lt;br&gt;
is not useful. "42.5% of postings carry a pay range, as separate numbers" is.&lt;/p&gt;

&lt;p&gt;So I read every live board I could find on each platform — between 17,000 and 179,000 postings per&lt;br&gt;
platform, &lt;strong&gt;1,551,977 in total&lt;/strong&gt; — and counted.&lt;/p&gt;
&lt;h2&gt;
  
  
  The table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Posting date&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Pay&lt;/th&gt;
&lt;th&gt;Marked remote&lt;/th&gt;
&lt;th&gt;Postings measured&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Workday&lt;/td&gt;
&lt;td&gt;78.2%&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;41,683&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SmartRecruiters&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;7.7%&lt;/td&gt;
&lt;td&gt;86,464&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Greenhouse&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;179,303&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workable&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;36.3%&lt;/td&gt;
&lt;td&gt;68,849&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BambooHR&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;50,616&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lever&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;94.7%&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;4.8%&lt;/td&gt;
&lt;td&gt;46,005&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Breezy HR&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;45.7%&lt;/td&gt;
&lt;td&gt;15.7%&lt;/td&gt;
&lt;td&gt;47,085&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Personio&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;8.8%&lt;/td&gt;
&lt;td&gt;41,608&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ashby&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;38.9%&lt;/td&gt;
&lt;td&gt;54.2%&lt;/td&gt;
&lt;td&gt;32,407&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recruitee&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;26.9%&lt;/td&gt;
&lt;td&gt;9.9%&lt;/td&gt;
&lt;td&gt;21,621&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rippling&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;17.4%&lt;/td&gt;
&lt;td&gt;18,701&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pinpoint&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;42.5%&lt;/td&gt;
&lt;td&gt;8.8%&lt;/td&gt;
&lt;td&gt;17,302&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;yes&lt;/code&gt; means 99.5% or more. &lt;code&gt;no&lt;/code&gt; means the endpoint carries no such field at all — not that&lt;br&gt;
employers left it blank. Title, company, location and an apply URL come back everywhere.&lt;/p&gt;
&lt;h2&gt;
  
  
  Salary: four platforms, and the form matters more than the rate
&lt;/h2&gt;

&lt;p&gt;Only four of the twelve publish compensation, and none fills it most of the time. But the split&lt;br&gt;
that actually affects your code is not the percentage — it is whether you get numbers or a sentence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Numbers you can compute on:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Pinpoint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;—&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;separate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;fields&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"compensation_minimum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;45000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"compensation_maximum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;55000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compensation_currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GBP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"compensation_frequency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"annual"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Recruitee&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;—&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;same&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;idea&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"salary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4500"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6500"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"period"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"month"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sentences you cannot:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Breezy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;—&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;highest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;fill&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;rate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;four,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;45.7&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"salary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$28 – $100 / hour"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Ashby&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"compensationTierSummary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$211.4K – $290.6K • Offers Equity"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parsing the second group means guessing at currency, period, and whether equity is folded into the&lt;br&gt;
top number. I do not parse them. They pass through as strings and the schema says so.&lt;/p&gt;

&lt;p&gt;Two traps in that group:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Recruitee keeps the salary object with zeros when the employer skipped the field.&lt;/strong&gt; A naive
reader ships &lt;code&gt;"0 USD hour"&lt;/code&gt; as a pay range. Treat &lt;code&gt;min &amp;lt;= 0 &amp;amp;&amp;amp; max &amp;lt;= 0&lt;/code&gt; as absent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;European employers quote monthly.&lt;/strong&gt; &lt;code&gt;4500–6500 EUR month&lt;/code&gt; is not an annual figure, and
multiplying by twelve to normalise is a guess about holiday allowance and 13th-month pay.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Dates: half the platforms do not have them
&lt;/h2&gt;

&lt;p&gt;Four platforms — Personio, Rippling, Pinpoint and BambooHR — publish no posting date at all.&lt;br&gt;
Workday publishes prose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"postedOn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Posted 30+ Days Ago"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;"Posted Today"&lt;/code&gt; and &lt;code&gt;"Posted 5 Days Ago"&lt;/code&gt; convert to a timestamp. &lt;code&gt;"30+"&lt;/code&gt; does not, and inventing&lt;br&gt;
one is worse than admitting you do not know. That is why Workday sits at 78.2% rather than 100%.&lt;/p&gt;

&lt;p&gt;This is a design decision, not a detail. If you build a "posted in the last 7 days" filter, you&lt;br&gt;
either drop undated rows — silently discarding a third of the market — or keep them, and your&lt;br&gt;
filter means less than it says. There is no third option. Saying which one you chose, in the docs,&lt;br&gt;
is the honest version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remote: a real field on five, a regex on the rest
&lt;/h2&gt;

&lt;p&gt;Five platforms let the employer state the work arrangement. The other seven do not, so the only&lt;br&gt;
option is matching the location string — which misses &lt;code&gt;"Anywhere"&lt;/code&gt; and misfires on&lt;br&gt;
&lt;code&gt;"Remote Support Engineer, London office"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The spread between platforms says more about who uses them than about the job market:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ashby: 54.2% marked remote.&lt;/strong&gt; Remote-friendly tech companies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workable: 36.3%.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lever: 4.8%&lt;/strong&gt; — and that is inferred, not stated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And then BambooHR, from the top of this post: the field exists, and across 50,616 postings nobody&lt;br&gt;
had touched it. "Has the field" and "the field has data" are different claims, and only one of them&lt;br&gt;
is worth putting in a comparison table.&lt;/p&gt;

&lt;h2&gt;
  
  
  The traps that cost me the most time
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SmartRecruiters caps at 100 rows whatever &lt;code&gt;limit&lt;/code&gt; you pass.&lt;/strong&gt; Ask for 500, get 100. Page with&lt;br&gt;
&lt;code&gt;offset&lt;/code&gt; and take the count from &lt;code&gt;totalFound&lt;/code&gt;, not from the length of the array you got back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Breezy answers &lt;code&gt;403&lt;/code&gt;, not &lt;code&gt;404&lt;/code&gt;, for a subdomain with no board on it.&lt;/strong&gt; Treat that as rate&lt;br&gt;
limiting and you back off from empty subdomains forever; treat it as fatal and you drop live boards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workday's &lt;code&gt;total&lt;/code&gt; saturates at 2000, and the API keeps serving rows past the end of the list.&lt;/strong&gt;&lt;br&gt;
Probing offsets until a page comes back empty never terminates. My first pass recorded twelve&lt;br&gt;
boards at a 60,000 probe ceiling and would have advertised 720,000 postings that do not exist. The&lt;br&gt;
&lt;code&gt;facets&lt;/code&gt; counts are the real total — they match &lt;code&gt;total&lt;/code&gt; exactly on boards that have not saturated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A 429 is not a dead board.&lt;/strong&gt; I once discarded 1,644 Workable boards as dead. The only problem was&lt;br&gt;
my own request rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the boards is the actual work
&lt;/h2&gt;

&lt;p&gt;Calling these endpoints is easy. Knowing that Notion is on Ashby and Figma is on Greenhouse, and&lt;br&gt;
what string goes in the URL, is not — there is no public directory of that.&lt;/p&gt;

&lt;p&gt;The practical source is the Wayback CDX index, with one non-obvious catch that cost me two separate&lt;br&gt;
platforms:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CDX sorts by &lt;code&gt;urlkey&lt;/code&gt;.&lt;/strong&gt; For &lt;code&gt;recruitee.com&lt;/code&gt; that means &lt;code&gt;com,recruitee)/…&lt;/code&gt; — the platform's own&lt;br&gt;
marketing pages — fills the first pages of results, and customer boards like&lt;br&gt;
&lt;code&gt;com,recruitee,acme)/&lt;/code&gt; sit deep in a 92-page index. Read only the front and you get zero&lt;br&gt;
subdomains and conclude the platform has no public boards. I wrote off both Recruitee and BambooHR&lt;br&gt;
that way before going back and walking the whole index. They turned out to have 1,126 and 9,711&lt;br&gt;
live boards.&lt;/p&gt;

&lt;p&gt;The mirror-image mistake is trusting a single source. Common Crawl does not crawl &lt;code&gt;jobs.lever.co&lt;/code&gt;&lt;br&gt;
at all — one URL, domain-wide — so an early count showed 40 Lever boards against Greenhouse's&lt;br&gt;
2,996 and made Lever look like a rounding error. It has 1,917.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check whether a coverage gap is your crawler's blind spot before concluding the segment is&lt;br&gt;
small.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One methodological note, because I got this wrong three times
&lt;/h2&gt;

&lt;p&gt;Every number above is the &lt;strong&gt;full population&lt;/strong&gt; for that platform — every live board in the registry,&lt;br&gt;
not a sample.&lt;/p&gt;

&lt;p&gt;That is not pedantry. Board sizes vary by orders of magnitude, so any sample is dominated by&lt;br&gt;
whichever large boards happen to land in it. Measuring Recruitee's salary fill rate:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sample&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 board&lt;/td&gt;
&lt;td&gt;94%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40 boards&lt;/td&gt;
&lt;td&gt;43.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All 1,126 boards (21,621 postings)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;26.9%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I published a page with a sampled number before catching this, and had to correct it. Ashby's&lt;br&gt;
remote rate went from 80.7% on a 50-board sample to &lt;strong&gt;54.2%&lt;/strong&gt; across all 1,588. If you are going to&lt;br&gt;
put a percentage in front of people, walk the whole set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;These are &lt;strong&gt;listing endpoints&lt;/strong&gt;. Several platforms expose more on a per-posting detail endpoint —
BambooHR's date, description and compensation all live there — at the cost of one request per row.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coverage is what I have found&lt;/strong&gt;, not every company on earth. A miss means the board is not in my
registry, not that it does not exist.&lt;/li&gt;
&lt;li&gt;Boards close. Measured decay is about 0.6% every day and a half, so these counts drift.&lt;/li&gt;
&lt;li&gt;Measured 2026-08-24.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where the details are
&lt;/h2&gt;

&lt;p&gt;Per-platform pages with the request shape, the measured fields and the traps for each one:&lt;br&gt;
&lt;a href="https://hwangbyeongseon.github.io/ats-job-feed-recipes/greenhouse-api.html" rel="noopener noreferrer"&gt;Greenhouse&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://hwangbyeongseon.github.io/ats-job-feed-recipes/lever-api.html" rel="noopener noreferrer"&gt;Lever&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://hwangbyeongseon.github.io/ats-job-feed-recipes/ashby-api.html" rel="noopener noreferrer"&gt;Ashby&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://hwangbyeongseon.github.io/ats-job-feed-recipes/workday-api.html" rel="noopener noreferrer"&gt;Workday&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://hwangbyeongseon.github.io/ats-job-feed-recipes/smartrecruiters-api.html" rel="noopener noreferrer"&gt;SmartRecruiters&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://hwangbyeongseon.github.io/ats-job-feed-recipes/workable-api.html" rel="noopener noreferrer"&gt;Workable&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://hwangbyeongseon.github.io/ats-job-feed-recipes/breezy-api.html" rel="noopener noreferrer"&gt;Breezy&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://hwangbyeongseon.github.io/ats-job-feed-recipes/personio-api.html" rel="noopener noreferrer"&gt;Personio&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://hwangbyeongseon.github.io/ats-job-feed-recipes/recruitee-api.html" rel="noopener noreferrer"&gt;Recruitee&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://hwangbyeongseon.github.io/ats-job-feed-recipes/bamboohr-api.html" rel="noopener noreferrer"&gt;BambooHR&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://hwangbyeongseon.github.io/ats-job-feed-recipes/pinpoint-api.html" rel="noopener noreferrer"&gt;Pinpoint&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://hwangbyeongseon.github.io/ats-job-feed-recipes/rippling-api.html" rel="noopener noreferrer"&gt;Rippling&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The comparison table lives at&lt;br&gt;
&lt;a href="https://hwangbyeongseon.github.io/ats-job-feed-recipes/ats-api-fields.html" rel="noopener noreferrer"&gt;Which ATS API gives you what&lt;/a&gt;,&lt;br&gt;
and runnable JavaScript and Python recipes are on&lt;br&gt;
&lt;a href="https://github.com/HwangByeongSeon/ats-job-feed-recipes" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you would rather not maintain twelve clients and a board registry, I run the same engine as&lt;br&gt;
hosted Actors — but the endpoints above are open, and everything in this post is reproducible&lt;br&gt;
without them.&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
      <category>programming</category>
      <category>data</category>
    </item>
  </channel>
</rss>
