<?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: glitchbound</title>
    <description>The latest articles on DEV Community by glitchbound (@glitchbound).</description>
    <link>https://dev.to/glitchbound</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%2F4055417%2F57611ce1-a653-43cc-a2cc-478624eb75e9.png</url>
      <title>DEV Community: glitchbound</title>
      <link>https://dev.to/glitchbound</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/glitchbound"/>
    <language>en</language>
    <item>
      <title>Lovable put a .env file in my GitHub repo. Is my Supabase key leaked?</title>
      <dc:creator>glitchbound</dc:creator>
      <pubDate>Fri, 13 Nov 2026 17:08:00 +0000</pubDate>
      <link>https://dev.to/glitchbound/lovable-put-a-env-file-in-my-github-repo-is-my-supabase-key-leaked-2bc6</link>
      <guid>https://dev.to/glitchbound/lovable-put-a-env-file-in-my-github-repo-is-my-supabase-key-leaked-2bc6</guid>
      <description>&lt;p&gt;You open your project on GitHub and see a file called &lt;code&gt;.env&lt;/code&gt;. Inside is your Supabase address and a long key. Every guide says never commit a &lt;code&gt;.env&lt;/code&gt; file. Is your app leaking?&lt;/p&gt;

&lt;p&gt;Usually not. My own checker made this exact mistake. Its first version raised its loudest alarm on this file in three of six apps, and it was wrong all three times.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is in the file
&lt;/h2&gt;

&lt;p&gt;In the apps I looked at, the file held the project address, a key named &lt;code&gt;VITE_SUPABASE_PUBLISHABLE_KEY&lt;/code&gt; or &lt;code&gt;VITE_SUPABASE_ANON_KEY&lt;/code&gt;, and one more public setting such as the project id.&lt;/p&gt;

&lt;p&gt;Two things make these harmless to publish.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;VITE_&lt;/code&gt; prefix.&lt;/strong&gt; The build tool copies every value with that prefix into the code that runs in the browser. Anyone who visits your app already has it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The kind of key.&lt;/strong&gt; Supabase has public keys and secret keys. The publishable key is the public kind, and Supabase's own guide calls it safe to expose online. The older anon key is the earlier version of the same thing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the file tells the world nothing your website does not already tell it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The condition that matters
&lt;/h2&gt;

&lt;p&gt;The public key is safe only if your database rules are in place. It opens the door to the database, and Row Level Security decides what the visitor can do once inside. With a table that has no rules, that public key is usually enough to read and change the table.&lt;/p&gt;

&lt;p&gt;That is the real question. It is not "is my key visible". It is "what can someone do with it".&lt;/p&gt;

&lt;h2&gt;
  
  
  Which key is the dangerous one
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;New style: a key starting with &lt;code&gt;sb_secret_&lt;/code&gt; is secret. One starting with &lt;code&gt;sb_publishable_&lt;/code&gt; is public.&lt;/li&gt;
&lt;li&gt;Old style: both keys are long strings starting with &lt;code&gt;eyJ&lt;/code&gt;. In the Supabase dashboard one is labelled &lt;code&gt;anon&lt;/code&gt; and the other &lt;code&gt;service_role&lt;/code&gt;. The &lt;code&gt;service_role&lt;/code&gt; key is the secret one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A secret key skips every rule you have. It belongs on a server only. It is a real leak if it appears in any of these places:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the &lt;code&gt;.env&lt;/code&gt; file in your repository.&lt;/li&gt;
&lt;li&gt;In any variable whose name starts with &lt;code&gt;VITE_&lt;/code&gt;, even if the file is not committed.&lt;/li&gt;
&lt;li&gt;In an older commit. Deleting a file does not remove it from the history.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The same goes for keys of other paid services, such as an AI provider or an email service.&lt;/p&gt;

&lt;h2&gt;
  
  
  If the secret key was exposed
&lt;/h2&gt;

&lt;p&gt;Deleting it is not enough. First take it out of the code, so the new key does not leak the same way. Then create a new key in the Supabase dashboard, switch your server to it, and turn the old one off. Supabase calls this rotating the key.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two-minute check
&lt;/h2&gt;

&lt;p&gt;Run this in the Supabase SQL editor. It only reads.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;schemaname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tablename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rowsecurity&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pg_tables&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;schemaname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt; &lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;tablename&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any table with &lt;code&gt;rowsecurity = false&lt;/code&gt; has no rules at all. That matters far more than the &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;One more thing to know: Supabase says it is deprecating the older &lt;code&gt;anon&lt;/code&gt; and &lt;code&gt;service_role&lt;/code&gt; keys by the end of 2026, so a move to the new keys is coming either way.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want your app checked
&lt;/h2&gt;

&lt;p&gt;I read your repository and its history for a fixed list of problems, including secret keys in the code and tables without rules, and report each with its file and line. It is an option with the scan on &lt;a href="https://www.upwork.com/services/product/development-it-a-cleanup-of-your-ai-written-code-with-proof-that-nothing-broke-2120785581882277469" rel="noopener noreferrer"&gt;my Upwork listing&lt;/a&gt;. The scan costs $95, and the security check adds one day and $50. That is an opening price for the first five clients. After that it is $150. An empty report does not mean the app is secure.&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>security</category>
      <category>vibecoding</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Vibe code cleanup: how to know the cleanup did not break your AI-built app</title>
      <dc:creator>glitchbound</dc:creator>
      <pubDate>Tue, 10 Nov 2026 20:48:13 +0000</pubDate>
      <link>https://dev.to/glitchbound/vibe-code-cleanup-how-to-know-the-cleanup-did-not-break-your-ai-built-app-1ga7</link>
      <guid>https://dev.to/glitchbound/vibe-code-cleanup-how-to-know-the-cleanup-did-not-break-your-ai-built-app-1ga7</guid>
      <description>&lt;p&gt;You built an app with Lovable, Cursor, Bolt or Claude Code, and it works. People use it. But every new feature now breaks an old one, and the AI keeps writing things again that it already wrote.&lt;/p&gt;

&lt;p&gt;The fix is a cleanup. The problem is that a cleanup can break the app that works today, and you may not find out until a customer does.&lt;/p&gt;

&lt;p&gt;I offer a cleanup service for AI-written code, and its whole promise is that you get proof nothing broke. Before selling that, I wanted to know whether the proof could be trusted. So I ran it on a real app and then tried to fool it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The app
&lt;/h2&gt;

&lt;p&gt;I picked an open-source web app with more than 900 stars. Its author describes it as vibe-coded, built over about a year with AI tools. It is a real product that people use, and it comes with 817 automated tests of its own.&lt;/p&gt;

&lt;p&gt;A scan found what a year of AI-written code usually leaves behind. The same small function was written several times in different files. Old copies of files were left lying around with nothing using them. Some files had grown to thousands of lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cleanup
&lt;/h2&gt;

&lt;p&gt;I merged the identical copies into one, deleted the files nothing used, and split two oversized files into smaller ones. That came to 28 files changed and 2,937 fewer lines.&lt;/p&gt;

&lt;p&gt;I left alone anything that would change how the parts of the app talk to each other. One 4,906-line piece of code stayed as it was, because splitting it is a design decision that belongs to the owner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I broke it on purpose
&lt;/h2&gt;

&lt;p&gt;On a throwaway copy, I removed one small step from one of the merged functions. It was the step that turns a file name into lower case.&lt;/p&gt;

&lt;p&gt;All 817 tests still passed.&lt;/p&gt;

&lt;p&gt;In the app itself, four export screens now produced &lt;code&gt;My-Demo-File-_v2&lt;/code&gt; where they used to produce &lt;code&gt;my-demo-file-_v2&lt;/code&gt;. It is a harmless example of a serious problem. "The tests pass" is the usual evidence that a cleanup was safe, and here it would have been wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What caught it
&lt;/h2&gt;

&lt;p&gt;The cleanup is checked four ways, and each one compares the old version with the new one.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every piece of code in the old version is matched to the new version. All 2,348 were accounted for. This check caught the planted bug.&lt;/li&gt;
&lt;li&gt;The app's own tests run on both versions. All 817 passed on both, and no test was edited.&lt;/li&gt;
&lt;li&gt;The same screens are opened in the old build and the new build. All 16 were identical down to the pixel, with the same text, errors, network requests and saved data. This check caught the planted bug too, on exactly the four screens that use the function.&lt;/li&gt;
&lt;li&gt;The functions that were touched are run with the same inputs on both versions. 594 of 594 results were identical.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One more thing came out of the first check. Eleven places in this app define a function with the same name, and they do not all do the same thing. Merging the wrong two would change behaviour far away from the edit, so the check follows where every name leads, not only whether the text looks the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking the checks
&lt;/h2&gt;

&lt;p&gt;One planted bug is an anecdote. So the cleaned code was broken in 230 small ways, one at a time, and the first check noticed all 230.&lt;/p&gt;

&lt;p&gt;My checks were not perfect the first time, and I think you should know that. The cleanup deleted a file that a settings file still mentioned. Nothing broke, but the report should have pointed at it and did not. I fixed the check, corrected the report in public, and the report now lists that line for a person to read.&lt;/p&gt;

&lt;p&gt;Two open-source maintainers have merged small cleanups that went through the first two of the four checks above. In Calino, a calendar app, one removed 55 duplicated lines from one file, and you can read it &lt;a href="https://github.com/Ivan-Malinovski/calino/pull/268" rel="noopener noreferrer"&gt;here&lt;/a&gt;. In relay-ai, a tool its README says was vibe coded, one replaced two identical copies of a function with a single shared copy, and you can read it &lt;a href="https://github.com/jacob-bd/relay-ai/pull/91" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Five of relay-ai's tests need the internet, so they fail in my sandbox, the same way before and after my change. They passed in the maintainer's own run.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a cleanup does not do
&lt;/h2&gt;

&lt;p&gt;A cleanup that changes nothing also fixes nothing. If your app has a bug or a security hole today, a careful cleanup keeps it, because keeping behaviour the same is the whole point.&lt;/p&gt;

&lt;p&gt;Finding security problems, such as open database rules or leaked keys, is a different job, usually called an audit. Many founders need that before launch. Make sure you know which of the two you are buying, because they are often sold under one name.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get when you hire me
&lt;/h2&gt;

&lt;p&gt;It always starts with a scan, which costs $95. You get a report on your whole repository that lists the duplicated functions, the look-alikes that are not safe to merge, the files over 1,000 lines and the values that are retyped in many places. It ends with a fixed price for the cleanup, so you know the cost before you commit to anything. If you then hire me for the cleanup, the $95 comes off its price.&lt;/p&gt;

&lt;p&gt;Then we agree on what gets cleaned and which parts of the app matter most to you. I do the cleanup in small steps and run the four checks above on your app.&lt;/p&gt;

&lt;p&gt;You receive two things. The first is one pull request, which is the cleaned code ready for you to accept. The second is the proof report, which lists every piece of code that was moved, merged or removed, with before and after screenshots of your screens.&lt;/p&gt;

&lt;p&gt;If a change in behaviour caused by my cleanup shows up within 30 days, I fix it free.&lt;/p&gt;

&lt;p&gt;Your code runs on my machine only inside a sandbox with no internet, after its packages have been checked against a public malware database. I am happy to sign an NDA.&lt;/p&gt;

&lt;p&gt;The service covers JavaScript and TypeScript apps, the languages that tools like Lovable and Bolt normally produce for web apps. It does not cover other languages such as Python or Swift.&lt;/p&gt;

&lt;p&gt;You can order the scan or a cleanup through &lt;a href="https://www.upwork.com/services/product/development-it-a-cleanup-of-your-ai-written-code-with-proof-that-nothing-broke-2120785581882277469" rel="noopener noreferrer"&gt;my Upwork listing&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three questions for anyone you hire
&lt;/h2&gt;

&lt;p&gt;Whoever does your cleanup, these are worth asking.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What evidence will I get that compares the old version with the new one?&lt;/li&gt;
&lt;li&gt;What can that evidence not see?&lt;/li&gt;
&lt;li&gt;Has it ever caught a mistake planted on purpose?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the only answer is "the tests pass", you now know what that is worth.&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>ai</category>
      <category>webdev</category>
      <category>startup</category>
    </item>
    <item>
      <title>Coinbase, Kalshi and Binance app rankings API: the App Store and Google Play finance charts in 30 countries</title>
      <dc:creator>glitchbound</dc:creator>
      <pubDate>Fri, 30 Oct 2026 16:39:21 +0000</pubDate>
      <link>https://dev.to/glitchbound/coinbase-kalshi-and-binance-app-rankings-api-the-app-store-and-google-play-finance-charts-in-30-9jg</link>
      <guid>https://dev.to/glitchbound/coinbase-kalshi-and-binance-app-rankings-api-the-app-store-and-google-play-finance-charts-in-30-9jg</guid>
      <description>&lt;p&gt;Coinbase's place in the App Store is a number people watch. On 2026-10-30 it was #37 among free finance apps on the US iPhone, and no crypto exchange was in the overall top 100 of either store in any of the 30 countries read here. Five things to know, read from both stores' top free finance and overall charts on 2026-10-30 between 16:27 and 16:32 UTC, 100 apps a chart in 30 countries, from the United States and Brazil to Japan and Nigeria.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Coinbase is outside every overall top 100
&lt;/h2&gt;

&lt;p&gt;Coinbase was in the top 100 free finance apps in 12 of the 30 countries on the iPhone and 8 on Google Play. Its best places were #37 on the iPhone in the United States and #33 on Google Play in Canada (#47 in the US). It was in neither store's overall top 100 anywhere, and neither was any other crypto exchange. The highest any exchange reached in a finance chart was Kraken at #9 on Google Play in Italy; on the iPhone it was Binance at #16 in the United Arab Emirates.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Kalshi leads US finance
&lt;/h2&gt;

&lt;p&gt;Kalshi, a prediction market, was #2 among free finance apps on the US iPhone and #1 on Google Play, and #7 and #16 in the overall US charts. It was in no other country's charts. Polymarket was #12 in US finance on the iPhone and #50 on Google Play; Robinhood was #29 and #51.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Binance travels, and stock brokers sit higher
&lt;/h2&gt;

&lt;p&gt;Binance's main app was in the iPhone finance top 100 in 23 of the 30 countries, and on Google Play in 12. Bitvavo (#17 in the Netherlands), OKX (#18 in Poland) and Luno (#32 in South Africa) ranked above it on the iPhone, and Kraken led the crypto apps on Google Play in Italy, France, Spain, Germany and Sweden. Stock brokers ranked higher still: XTB #4 in Poland, Trading 212 #5 in the United Kingdom and Trade Republic #6 in Germany and Spain, on the iPhone.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. One app's place in Apple's finance chart
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rank&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  &lt;span class="c"&gt;# country, app id: that app's place among the country's top 100 free finance apps&lt;/span&gt;
  curl -s &lt;span class="s2"&gt;"https://itunes.apple.com/$1/rss/topfreeapplications/limit=100/genre=6015/json"&lt;/span&gt; \
    &lt;span class="o"&gt;|&lt;/span&gt; jq -r --arg id &lt;span class="s2"&gt;"$2"&lt;/span&gt; &lt;span class="s1"&gt;'.feed.entry | to_entries[] | select(.value.id.attributes["im:id"] == $id) | "\(.key + 1) \(.value["im:name"].label)"'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
rank us 886427730
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That printed &lt;code&gt;37 Coinbase: Buy Crypto &amp;amp; Stocks&lt;/code&gt;. The same id is "Coinbase: Buy BTC, ETH, SOL" in Canada and "Coinbase: BTC, ETH, SOL kaufen" in Germany, so match on the id, not the title. No output means outside the top 100: this genre feed returns 100 apps even when asked for 200. &lt;code&gt;6015&lt;/code&gt; is Finance.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The #1 finance app is usually a bank or a wallet
&lt;/h2&gt;

&lt;p&gt;Lloyds led both stores in the United Kingdom, ING in Australia, Mercado Pago in Mexico and ShopeePay in Malaysia and Thailand; on Google Play, PhonePe led in India, PayPay in Japan and GCash in the Philippines. The same app led both stores in 10 of the 30 countries. Google Wallet led Google Play in Canada, Israel and the Netherlands, and was in the overall Google Play top 100 in 13 countries, more than any other finance app. In the United States, Capital One led the iPhone and Kalshi led Google Play.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosted
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/glitchbound/app-charts-scraper" rel="noopener noreferrer"&gt;App Store Top Charts Scraper&lt;/a&gt; and &lt;a href="https://apify.com/glitchbound/google-play-charts-scraper" rel="noopener noreferrer"&gt;Google Play Top Charts Scraper&lt;/a&gt; on Apify take a list of countries, charts and categories (&lt;code&gt;finance&lt;/code&gt; on both) and return every app with its id, rank, developer and price, and how far it moved since your previous run, so a daily run turns Coinbase's place into a series. The 30 countries above, finance and overall, were one run of each: under six minutes for the iPhone, 72 seconds for Google Play.&lt;/p&gt;

</description>
      <category>api</category>
      <category>cryptocurrency</category>
      <category>fintech</category>
      <category>mobile</category>
    </item>
    <item>
      <title>O'Reilly Auto Parts, Dollar Tree and Trane jobs API: the same jobs on two careers sites, and the id that joins them</title>
      <dc:creator>glitchbound</dc:creator>
      <pubDate>Fri, 30 Oct 2026 16:24:39 +0000</pubDate>
      <link>https://dev.to/glitchbound/oreilly-auto-parts-dollar-tree-and-trane-jobs-api-the-same-jobs-on-two-careers-sites-and-the-id-34h5</link>
      <guid>https://dev.to/glitchbound/oreilly-auto-parts-dollar-tree-and-trane-jobs-api-the-same-jobs-on-two-careers-sites-and-the-id-34h5</guid>
      <description>&lt;p&gt;O'Reilly Auto Parts (&lt;code&gt;careers.oreillyauto.com&lt;/code&gt;), Dollar Tree (&lt;code&gt;careers.dollartree.com&lt;/code&gt;) and Trane Technologies (&lt;code&gt;careers.tranetechnologies.com&lt;/code&gt;) show their jobs on a Phenom careers site, and the same jobs sit in the Workday site behind it. Read both and you hold every job twice. Five things to know, measured on 2026-10-29 with a few seconds between requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Two sites, one list
&lt;/h2&gt;

&lt;p&gt;The two sides report nearly the same count: O'Reilly 19,120 jobs on Phenom and 19,104 on Workday, Dollar Tree 15,537 and 15,573, Trane 1,751 and 1,746. They are not two pools of jobs, they are two views of one, refreshed at slightly different moments.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The requisition id joins them
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;req&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$(&lt;/span&gt;curl -s -X POST https://careers.oreillyauto.com/widgets -H &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; \
  -d &lt;span class="s1"&gt;'{"ddoKey":"refineSearch","jobs":true,"from":0,"size":1,"sort":{"order":"desc","field":"dateCreated"}}'&lt;/span&gt; \
  &lt;span class="o"&gt;|&lt;/span&gt; jq -r &lt;span class="s1"&gt;'.refineSearch.data.jobs[0] | "\(.reqId) \(.title)"'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Phenom: $req"&lt;/span&gt;
curl -s -X POST https://oreillyauto.wd1.myworkdayjobs.com/wday/cxs/oreillyauto/oreilly/jobs \
  -H &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; -d &lt;span class="s2"&gt;"{\"limit\":5,\"offset\":0,\"searchText\":\"${req%% *}\",\"appliedFacets\":{}}"&lt;/span&gt; \
  &lt;span class="o"&gt;|&lt;/span&gt; jq -c &lt;span class="s1"&gt;'{total, jobs: [.jobPostings[] | {title, id: .bulletFields[0]}]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That printed &lt;code&gt;Phenom: R209433 Parts Specialist&lt;/code&gt;, then &lt;code&gt;{"total":1,"jobs":[{"title":"Parts Specialist","id":"R209433"}]}&lt;/code&gt;. Phenom's &lt;code&gt;reqId&lt;/code&gt; is Workday's requisition id, the one in &lt;code&gt;bulletFields&lt;/code&gt; and at the end of the job's &lt;code&gt;externalPath&lt;/code&gt;. The same held for Dollar Tree (&lt;code&gt;R-363425&lt;/code&gt;) and Trane (&lt;code&gt;JR-19071&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Same jobs, different order
&lt;/h2&gt;

&lt;p&gt;O'Reilly's first 100 jobs on each side shared 52: the same openings, sorted differently. So comparing first pages, or titles and cities, says little. Collect both lists and join them on the employer plus the requisition id.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. How many
&lt;/h2&gt;

&lt;p&gt;In a nightly harvest of 2.77 million jobs from 52,000 boards on 2026-10-29, 45,024 rows were a second copy of a job read on another system: 19,080 at O'Reilly, 15,525 at Dollar Tree, 1,731 at Trane, then Northrop Grumman (1,360), Applied Materials (1,126) and Morgan Stanley (1,013), each with Eightfold over Workday, and Mayo Clinic (1,035) with Eightfold over Oracle. By system, 37,725 were Phenom over Workday, 5,256 Eightfold over Workday and 2,043 Eightfold over Oracle.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Keep the system's copy, keep the front end's extras
&lt;/h2&gt;

&lt;p&gt;Key the join on the employer's name, not on each board's own label: Workday names O'Reilly by its tenant, &lt;code&gt;oreillyauto&lt;/code&gt;, and Phenom says "O'Reilly Auto Parts", so a join on the raw labels finds nothing. Keep the Workday or Oracle copy, which is read whole, but take what only the front end has before dropping it: Dollar Tree's Phenom records carry a &lt;code&gt;salaryRange&lt;/code&gt; that its Workday list does not, on 5,244 of its 15,526 jobs that night.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosted
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/glitchbound/ats-jobs-search" rel="noopener noreferrer"&gt;Job Listings API&lt;/a&gt; on Apify searches that nightly index and returns each of these jobs once, with the pay the front end carried. The &lt;a href="https://apify.com/glitchbound/ats-jobs-scraper" rel="noopener noreferrer"&gt;Company Career Page Jobs Scraper&lt;/a&gt; takes &lt;code&gt;O'Reilly Auto Parts&lt;/code&gt; or &lt;code&gt;Dollar Tree&lt;/code&gt; by name, reads both sites, and keeps one row per requisition. Every other system's public endpoint, with a tested &lt;code&gt;curl&lt;/code&gt;, is in &lt;a href="https://github.com/danielhagever/job-board-apis" rel="noopener noreferrer"&gt;job-board-apis&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>webscraping</category>
      <category>career</category>
    </item>
    <item>
      <title>Temu, Shopee and Amazon shopping app rankings API: the App Store and Google Play shopping charts in 30 countries</title>
      <dc:creator>glitchbound</dc:creator>
      <pubDate>Fri, 30 Oct 2026 00:07:32 +0000</pubDate>
      <link>https://dev.to/glitchbound/temu-shopee-and-amazon-shopping-app-rankings-api-the-app-store-and-google-play-shopping-charts-in-2cgg</link>
      <guid>https://dev.to/glitchbound/temu-shopee-and-amazon-shopping-app-rankings-api-the-app-store-and-google-play-shopping-charts-in-2cgg</guid>
      <description>&lt;p&gt;Temu is in the top 100 free shopping apps on the iPhone and on Google Play in all 30 countries read here, and #1 on both stores in six. Most of the other #1s are local. Five things to know, read from both stores' top free shopping charts on 2026-10-29 between 23:51 and 23:54 UTC, 100 apps a chart in 30 countries, from the United States and Brazil to Japan and Nigeria.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Temu is in the top 100 everywhere
&lt;/h2&gt;

&lt;p&gt;Temu was #1 on both stores in the United Arab Emirates, Egypt, Israel, Nigeria, Singapore and South Africa, and in the top 10 in 21 of the 30 countries on the iPhone and 23 on Google Play. Its lowest places were #61 on the iPhone in Turkey and #57 on Google Play in Indonesia. In the United States, Vinted was #1 on both stores; Temu was #2 on the iPhone and #3 on Google Play.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The #1 is local almost everywhere else
&lt;/h2&gt;

&lt;p&gt;14 brands were #1 on the iPhone across the 30 countries, and 15 on Google Play. Mercado Libre led the iPhone in Argentina, Brazil and Mexico, and Google Play in Mexico. Meesho led both stores in India, Naver's store in Korea and A101 in Turkey. Shopify's Shop app was #1 on the iPhone in France, Italy, the Netherlands and Sweden. On Google Play, AliExpress was #1 in Australia and Brazil, and Shein in Saudi Arabia.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Apple's shopping chart is the same feed with a genre
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;top&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  &lt;span class="c"&gt;# country, genre id: the App Store's top 10 free apps in that genre&lt;/span&gt;
  curl -s &lt;span class="s2"&gt;"https://itunes.apple.com/$1/rss/topfreeapplications/limit=10/genre=$2/json"&lt;/span&gt; \
    &lt;span class="o"&gt;|&lt;/span&gt; jq -r &lt;span class="s1"&gt;'.feed.entry | to_entries[] | "\(.key + 1) \(.value["im:name"].label)"'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
top us 6024
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That printed Vinted, Temu, Walmart, Shop and Amazon Shopping as the first five. &lt;code&gt;6024&lt;/code&gt; is the Shopping genre (&lt;code&gt;6014&lt;/code&gt; is Games); &lt;code&gt;limit&lt;/code&gt; goes up to 100, and the position in &lt;code&gt;entry&lt;/code&gt; is the rank.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. One brand, many apps
&lt;/h2&gt;

&lt;p&gt;Shopee's main app is a separate app in each of the nine countries where it charted, on both stores. On the iPhone it is &lt;code&gt;959841443&lt;/code&gt; in Indonesia, &lt;code&gt;959841449&lt;/code&gt; in Vietnam and &lt;code&gt;959841453&lt;/code&gt; in Thailand, under eight developer names, from "SHOPEE THAILAND COMPANY LIMITED" to "SHPS I Private Limited" in Brazil; on Google Play it is &lt;code&gt;com.shopee.id&lt;/code&gt;, &lt;code&gt;com.shopee.vn&lt;/code&gt;, &lt;code&gt;com.shopee.th&lt;/code&gt; and so on. Counted as one brand, Shopee was #1 on the iPhone in three countries and on Google Play in six. Amazon is six iPhone apps across the 30: &lt;code&gt;297606951&lt;/code&gt; in 20 countries including the United States, &lt;code&gt;335187483&lt;/code&gt; in the United Kingdom, Spain, Italy, the Netherlands, Poland and Sweden, and one each for Germany, France, India and Japan. On Google Play it is one package, &lt;code&gt;com.amazon.mShop.android.shopping&lt;/code&gt;, in 28 countries, and a separate one in India. Keep a table that maps app ids to a brand: a developer name won't hold, and a title carries the current sale ("Shopee 11.11 Big Sale").&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The two stores pick different winners
&lt;/h2&gt;

&lt;p&gt;The same brand led both stores in 18 of the 30 countries. In the other 12 they split: in the United Kingdom, Amazon was #1 on the iPhone and GoWish, a wishlist app, on Google Play; in Japan, MUJI's app led the iPhone and Amazon led Google Play; in Canada it was Old Navy and DealSeek, in France Shop and Action. Shein shows the same gap: #1 on Google Play in Saudi Arabia, and #2 at best on the iPhone (Israel and South Africa).&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosted
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/glitchbound/app-charts-scraper" rel="noopener noreferrer"&gt;App Store Top Charts Scraper&lt;/a&gt; and &lt;a href="https://apify.com/glitchbound/google-play-charts-scraper" rel="noopener noreferrer"&gt;Google Play Top Charts Scraper&lt;/a&gt; on Apify take a list of countries, charts and categories (&lt;code&gt;shopping&lt;/code&gt; on both) and return every app with its id, rank, developer and price, and how far it moved since your previous run. The 30 countries above were one run of each: under three minutes for the iPhone, 30 seconds for Google Play.&lt;/p&gt;

</description>
      <category>api</category>
      <category>ecommerce</category>
      <category>mobile</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Roblox, Royal Match and Candy Crush rankings API: the top grossing mobile games in 30 countries on both stores</title>
      <dc:creator>glitchbound</dc:creator>
      <pubDate>Thu, 29 Oct 2026 23:31:03 +0000</pubDate>
      <link>https://dev.to/glitchbound/roblox-royal-match-and-candy-crush-rankings-api-the-top-grossing-mobile-games-in-30-countries-on-3no7</link>
      <guid>https://dev.to/glitchbound/roblox-royal-match-and-candy-crush-rankings-api-the-top-grossing-mobile-games-in-30-countries-on-3no7</guid>
      <description>&lt;p&gt;The top grossing chart is each store's list of the games taking the most money that day. Five things to know about it, read from both stores' top grossing and top free game charts on 2026-10-29 (23:22 UTC for Google Play, 23:24 UTC for the iPhone), 100 games a chart in 30 countries, from the United States and Brazil to Japan and Nigeria.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Five games are in the top 10 grossing almost everywhere
&lt;/h2&gt;

&lt;p&gt;Gossip Harbor was in the top 10 grossing in 27 of the 30 countries on the iPhone and 28 on Google Play. Roblox was in 25 on each, Royal Match in 25 and 21, Candy Crush Saga in 22 and 24, and Kingshot in 23 and 21.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The #1 is often a different game in each store
&lt;/h2&gt;

&lt;p&gt;In the United States, Gossip Harbor was #1 on the iPhone and Monopoly Go #2; on Google Play, Monopoly Go was #1 and Gossip Harbor #4. eFootball was #1 on the iPhone in eight countries (Brazil, India, Indonesia, Japan, Malaysia, Nigeria, Thailand and Turkey); on Google Play it was #2 to #8 in seven of them and #35 in Japan. Coin Master was #1 on Google Play in eight (the United Kingdom, Germany, France, Spain, Italy, Poland, Israel and Vietnam), and on the iPhone #1 in four of those and #2 or #3 in the rest. Mobile Legends led Google Play in Malaysia, the Philippines and Singapore.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Apple's games chart is the same feed with a genre
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;top&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  &lt;span class="c"&gt;# country, chart: the App Store's top 10 games there&lt;/span&gt;
  curl -s &lt;span class="s2"&gt;"https://itunes.apple.com/$1/rss/$2/limit=10/genre=6014/json"&lt;/span&gt; \
    &lt;span class="o"&gt;|&lt;/span&gt; jq -r &lt;span class="s1"&gt;'.feed.entry | to_entries[] | "\(.key + 1) \(.value["im:name"].label)"'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
top us topgrossingapplications
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That printed Gossip Harbor, Monopoly Go, Royal Match, Pokémon TCG Pocket and Roblox as the first five. &lt;code&gt;6014&lt;/code&gt; is the Games genre; &lt;code&gt;topfreeapplications&lt;/code&gt; and &lt;code&gt;toppaidapplications&lt;/code&gt; work the same way, up to 100 rows, and the position in &lt;code&gt;entry&lt;/code&gt; is the rank.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Match on the app id: the titles are translated
&lt;/h2&gt;

&lt;p&gt;Pokémon TCG Pocket was #1 on the iPhone in France and Italy, as "Le JCC Pokémon Pocket" and "GCC Pokémon Pocket"; in Spain, Argentina and Mexico it is "JCC Pokémon Pocket". Roblox is "Roblox (ロブロックス)" in Japan and "로블록스(Roblox)" in Korea. Of the iPhone's top grossing games, 58 appeared under three or more titles across the 30 countries; on Google Play, 6 did. Group by the app's id (&lt;code&gt;431946152&lt;/code&gt; and &lt;code&gt;com.roblox.client&lt;/code&gt; for Roblox), never by its name.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Free and grossing are different charts
&lt;/h2&gt;

&lt;p&gt;The top free chart is led by puzzles: Block Out! was #1 on the iPhone in nine countries and Meowdoku #1 on Google Play in twelve. Roblox is the game that sits high on both: in the top 10 free in 19 countries on the iPhone and 23 on Google Play, and #1 free on Google Play in Mexico, Malaysia, Saudi Arabia and Thailand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosted
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/glitchbound/app-charts-scraper" rel="noopener noreferrer"&gt;App Store Top Charts Scraper&lt;/a&gt; (category &lt;code&gt;games&lt;/code&gt;) and &lt;a href="https://apify.com/glitchbound/google-play-charts-scraper" rel="noopener noreferrer"&gt;Google Play Top Charts Scraper&lt;/a&gt; (&lt;code&gt;all-games&lt;/code&gt;, or one of 17 game genres) on Apify take a list of countries and charts and return every game with its rank, developer and price, and how far it moved since your previous run. Both charts in the 30 countries above took under three minutes a chart on the iPhone and 67 seconds together on Google Play.&lt;/p&gt;

</description>
      <category>api</category>
      <category>gamedev</category>
      <category>mobile</category>
      <category>datascience</category>
    </item>
    <item>
      <title>CommonSpirit Health, Crunch Fitness and The Joint jobs API: the pay in a job page's schema.org markup</title>
      <dc:creator>glitchbound</dc:creator>
      <pubDate>Thu, 29 Oct 2026 17:22:37 +0000</pubDate>
      <link>https://dev.to/glitchbound/commonspirit-health-crunch-fitness-and-the-joint-jobs-api-the-pay-in-a-job-pages-schemaorg-5d41</link>
      <guid>https://dev.to/glitchbound/commonspirit-health-crunch-fitness-and-the-joint-jobs-api-the-pay-in-a-job-pages-schemaorg-5d41</guid>
      <description>&lt;p&gt;Crunch Fitness (&lt;code&gt;crunchfitness&lt;/code&gt;), The Joint Chiropractic (&lt;code&gt;thejointchiropractic&lt;/code&gt;) and Ethos Veterinary Health (&lt;code&gt;ethosveterinaryhealth&lt;/code&gt;) hire through JazzHR, and CommonSpirit Health through Radancy. Both put a schema.org &lt;code&gt;JobPosting&lt;/code&gt; on each job's page, the markup Google for Jobs reads, and it has a field for the pay: &lt;code&gt;baseSalary&lt;/code&gt;. Five things to know about it, measured on 2026-10-29 on the eight newest jobs of five JazzHR boards and the ten newest of four Radancy sites, a few seconds between requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. JazzHR's list has no posting; the job page has a JobPosting
&lt;/h2&gt;

&lt;p&gt;A JazzHR board's &lt;code&gt;/apply&lt;/code&gt; page lists each opening's title, place and department, and nothing else. The posting, the date and the pay are on the job's own page, in a &lt;code&gt;JobPosting&lt;/code&gt;: on 7 of the 8 newest at Crunch Fitness, 7 at Bath Concepts, 6 at CentiMark, 5 at Ethos and 2 at The Joint. A page without one still shows the posting in its &lt;code&gt;#job-description&lt;/code&gt; block, but no date.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. One request per job page
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jp&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  &lt;span class="c"&gt;# a JazzHR job page: its schema.org JobPosting's title, date and pay&lt;/span&gt;
  curl -s &lt;span class="s2"&gt;"$1"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; perl -0ne &lt;span class="s1"&gt;'print "$1\n" while /&amp;lt;script type="application\/ld\+json"&amp;gt;(.*?)&amp;lt;\/script&amp;gt;/sg'&lt;/span&gt; \
    &lt;span class="o"&gt;|&lt;/span&gt; jq -c &lt;span class="s1"&gt;'select(."@type" == "JobPosting") | {title, datePosted, baseSalary}'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
jp https://centimark.applytojob.com/apply/Oyh2N2Nl4F/Assistant-Roofing-Foreman
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That printed &lt;code&gt;{"title":"Assistant Roofing Foreman","datePosted":"2026-10-08","baseSalary":{"@type":"MonetaryAmount","currency":"USD","value":{"@type":"QuantitativeValue","unitText":"HOUR","minValue":27,"maxValue":33}}}&lt;/code&gt;. That page carries two &lt;code&gt;ld+json&lt;/code&gt; blocks, the employer's &lt;code&gt;Organization&lt;/code&gt; first, so the command keeps the one whose &lt;code&gt;@type&lt;/code&gt; is &lt;code&gt;JobPosting&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. baseSalary is there when the employer fills it in
&lt;/h2&gt;

&lt;p&gt;On those eight jobs a board, &lt;code&gt;baseSalary&lt;/code&gt; had numbers on 5 at Ethos, 3 at CentiMark, 2 at The Joint, 1 at Crunch Fitness (an Operations Manager, 31,200 to 41,360 a year) and none at Bath Concepts. A band is &lt;code&gt;minValue&lt;/code&gt; and &lt;code&gt;maxValue&lt;/code&gt;; a single rate is &lt;code&gt;value&lt;/code&gt;, as The Joint wrote it for a Chiropractic Manager in Colorado: &lt;code&gt;{"@type":"QuantitativeValue","unitText":"YEAR","value":110000}&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The unit can be wrong
&lt;/h2&gt;

&lt;p&gt;Ethos marks four of its five bands &lt;code&gt;"unitText":"YEAR"&lt;/code&gt;: 28 to 32, 28 to 35 and 30 to 35 for licensed veterinary technicians, 12 to 27 for a veterinary assistant. Nobody is paid $28 a year; those are hourly rates, and the fifth, 28 to 35 for another technician, says &lt;code&gt;HOUR&lt;/code&gt;. Check the numbers against the unit before trusting either.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Radancy sends the field on every job, often empty
&lt;/h2&gt;

&lt;p&gt;CommonSpirit Health filled &lt;code&gt;baseSalary&lt;/code&gt; on all ten of its newest jobs, &lt;code&gt;61.81&lt;/code&gt; to &lt;code&gt;105.18&lt;/code&gt; an hour for a Nursing House Supervisor. UnitedHealth Group, Kaiser Permanente and Comcast, on the same system, sent it on all ten too, every time as &lt;code&gt;{"@type":"MonetaryAmount","currency":"","value":{"@type":"QuantitativeValue","unitText":""}}&lt;/code&gt;. At UnitedHealth the pay is in the posting's text instead: 7 of its 8 newest postings state it, 73,188 to 109,782 a year for a Clinical Manager.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosted
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/glitchbound/ats-jobs-scraper" rel="noopener noreferrer"&gt;Company Career Page Jobs Scraper&lt;/a&gt; on Apify takes &lt;code&gt;CommonSpirit Health&lt;/code&gt; or &lt;code&gt;The Joint Chiropractic&lt;/code&gt; by name, and JazzHR boards as &lt;code&gt;jazzhr:crunchfitness&lt;/code&gt;. It returns the pay as &lt;code&gt;salaryMin&lt;/code&gt;, &lt;code&gt;salaryMax&lt;/code&gt;, &lt;code&gt;salaryCurrency&lt;/code&gt; and &lt;code&gt;salaryPeriod&lt;/code&gt;: from a filled &lt;code&gt;baseSalary&lt;/code&gt; (CommonSpirit's on every run), from each JazzHR job page with &lt;code&gt;includeDescription&lt;/code&gt;, and from the posting's text where the field is empty or its unit cannot be true. Every other system's public endpoint, with a tested &lt;code&gt;curl&lt;/code&gt;, is in &lt;a href="https://github.com/danielhagever/job-board-apis" rel="noopener noreferrer"&gt;job-board-apis&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>webscraping</category>
      <category>career</category>
    </item>
    <item>
      <title>ChatGPT, Claude and Gemini app rankings API: the App Store and Google Play top charts in 30 countries</title>
      <dc:creator>glitchbound</dc:creator>
      <pubDate>Thu, 29 Oct 2026 17:21:30 +0000</pubDate>
      <link>https://dev.to/glitchbound/chatgpt-claude-and-gemini-app-rankings-api-the-app-store-and-google-play-top-charts-in-30-a63</link>
      <guid>https://dev.to/glitchbound/chatgpt-claude-and-gemini-app-rankings-api-the-app-store-and-google-play-top-charts-in-30-a63</guid>
      <description>&lt;p&gt;ChatGPT, Claude and Gemini are in the top free 100 on the iPhone in at least 29 of 30 countries, and in the Google Play top 100 in at least 28. Where they rank in each is a different story. Five things to know, read from both stores' top free charts on 2026-10-29 (16:46 UTC for the iPhone, 16:49 UTC for Google Play) in 30 countries, from the United States and Germany to Nigeria and Vietnam.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Claude is #1 on the iPhone in Germany, Spain and France
&lt;/h2&gt;

&lt;p&gt;On the iPhone top free chart, Claude was #1 in Germany, Spain and France, and in the top 5 in 14 of the 30 countries. ChatGPT was #1 in India and Nigeria and in the top 5 in 16. Gemini's best was #2, in Egypt, Spain and Poland. On Google Play, ChatGPT was #1 in 8 countries (Australia, Germany, Spain, France, the United Kingdom, India, the Netherlands and Poland), Claude's best was #5 (France, the Netherlands) and Gemini's #9 (Italy).&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Apple's chart is one public feed
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rank&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  &lt;span class="c"&gt;# country, app id: the app's place in that country's iPhone top free 100&lt;/span&gt;
  curl -s &lt;span class="s2"&gt;"https://itunes.apple.com/$1/rss/topfreeapplications/limit=100/json"&lt;/span&gt; \
    &lt;span class="o"&gt;|&lt;/span&gt; jq -r --arg id &lt;span class="s2"&gt;"$2"&lt;/span&gt; &lt;span class="s1"&gt;'.feed.entry | to_entries[] | select(.value.id.attributes["im:id"] == $id) | "\(.key + 1) \(.value["im:name"].label)"'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; c &lt;span class="k"&gt;in&lt;/span&gt; de es fr us &lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"$c $(rank $c 6473753684)"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; sleep 2&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;   &lt;span class="c"&gt;# Claude&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That printed &lt;code&gt;de 1&lt;/code&gt;, &lt;code&gt;es 1&lt;/code&gt;, &lt;code&gt;fr 1&lt;/code&gt;, &lt;code&gt;us 6&lt;/code&gt; and &lt;code&gt;in 12&lt;/code&gt;, each followed by "Claude by Anthropic". The feed has no rank field: the position in &lt;code&gt;entry&lt;/code&gt; is the rank, so the command counts it. &lt;code&gt;topfreeapplications&lt;/code&gt; is the iPhone chart; &lt;code&gt;toppaidapplications&lt;/code&gt; and &lt;code&gt;topgrossingapplications&lt;/code&gt; sit beside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The iPhone and Android charts are different charts
&lt;/h2&gt;

&lt;p&gt;Claude ranked higher on the iPhone than on Google Play in all 28 countries where it charted on both: a median of #6 against #29. Gemini did the same in 28 of 29, #6 against #30. ChatGPT is the one that does better on Android: a median of #3 on Google Play against #4.5 on the iPhone, higher on Play in 17 countries and level in 4. A rank in one store says little about the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Match on the app id, not the name
&lt;/h2&gt;

&lt;p&gt;"Gemini" in the title matched two iPhone apps in the same charts: Google Gemini (&lt;code&gt;6477489729&lt;/code&gt;) and Gemini Notebook (&lt;code&gt;6737527615&lt;/code&gt;), a second Google app. On Android, Google's Gemini is still &lt;code&gt;com.google.android.apps.bard&lt;/code&gt;. ChatGPT is published by "OpenAI OpCo, LLC" on the iPhone and "OpenAI" on Google Play, so a developer-name match fails too. And a chart can be led by an app that is nowhere else: Muse from Meta was #1 on both stores in Canada, Japan and Singapore, #1 on Google Play and #2 on the iPhone in the United States, and in neither top 100 in the other 26 countries.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Google Play has no feed
&lt;/h2&gt;

&lt;p&gt;Google Play publishes no chart feed. Its web app reads the charts through its own RPC, which the &lt;a href="https://dev.to/glitchbound/google-play-top-charts-api-top-free-top-grossing-and-trending-apps-in-130-countries-31ac"&gt;Google Play top charts write-up&lt;/a&gt; walks through. The Play numbers above came from that, 100 apps a chart a country.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosted
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/glitchbound/app-charts-scraper" rel="noopener noreferrer"&gt;App Store Top Charts Scraper&lt;/a&gt; and &lt;a href="https://apify.com/glitchbound/google-play-charts-scraper" rel="noopener noreferrer"&gt;Google Play Top Charts Scraper&lt;/a&gt; on Apify take a list of countries and a chart, and return every app with its rank, developer and price, and how far it moved since your previous run. Google Play rows carry the rating and installs; iPhone rows get the rating with &lt;code&gt;includeDetails&lt;/code&gt;. The 30 countries above were one run of each: 3 minutes for the iPhone, 29 seconds for Google Play.&lt;/p&gt;

</description>
      <category>api</category>
      <category>ai</category>
      <category>mobile</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Rippling, Carbon Health and Barry's jobs API: the pay in each job's own record, one location at a time</title>
      <dc:creator>glitchbound</dc:creator>
      <pubDate>Thu, 29 Oct 2026 16:30:52 +0000</pubDate>
      <link>https://dev.to/glitchbound/rippling-carbon-health-and-barrys-jobs-api-the-pay-in-each-jobs-own-record-one-location-at-a-45h3</link>
      <guid>https://dev.to/glitchbound/rippling-carbon-health-and-barrys-jobs-api-the-pay-in-each-jobs-own-record-one-location-at-a-45h3</guid>
      <description>&lt;p&gt;Rippling (&lt;code&gt;rippling&lt;/code&gt;), Carbon Health (&lt;code&gt;carbon-health&lt;/code&gt;) and Barry's (&lt;code&gt;barrys-careers&lt;/code&gt;) post their jobs on Rippling's own job boards, and Rippling serves every board as JSON with no key. Five things to know about getting the pay out of it, measured on 2026-10-29 with a few seconds between requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The list has five fields, and a job once per place
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/platform/api/ats/v1/board/&amp;lt;token&amp;gt;/jobs&lt;/code&gt; answers with &lt;code&gt;uuid&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt;, &lt;code&gt;department&lt;/code&gt; and &lt;code&gt;workLocation&lt;/code&gt;. No posting, no date, no pay. And a job with five places is five entries: Rippling's own board had 686 entries for 381 jobs, one of them listed in 20 places; Barry's 348 for 225; Carbon Health 320 for 233. Count by &lt;code&gt;uuid&lt;/code&gt;, not by entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Each job's own record has the rest
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rp&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  &lt;span class="c"&gt;# a Rippling board: entries and jobs in the list, then the first job's own record&lt;/span&gt;
  &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://api.rippling.com/platform/api/ats/v1/board/&lt;span class="nv"&gt;$1&lt;/span&gt;/jobs
  curl -s &lt;span class="s2"&gt;"$B"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; list.json
  jq -c &lt;span class="s1"&gt;'{entries: length, jobs: (map(.uuid) | unique | length)}'&lt;/span&gt; list.json
  curl -s &lt;span class="s2"&gt;"$B/$(jq -r '.[0].uuid' list.json)"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; jq -c &lt;span class="s1"&gt;'{name, createdOn, payRangeDetails}'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
rp barrys-careers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That printed &lt;code&gt;{"entries":348,"jobs":225}&lt;/code&gt;, then an Assistant General Manager created on 2026-09-23 with &lt;code&gt;payRangeDetails&lt;/code&gt; of &lt;code&gt;[{"location":"Washington, DC","currency":"USD","frequency":"YEAR","rangeStart":68000.0,"rangeEnd":70000.0,"isRemote":false}]&lt;/code&gt;. The record also carries &lt;code&gt;description&lt;/code&gt; in two parts, &lt;code&gt;company&lt;/code&gt; and &lt;code&gt;role&lt;/code&gt;, and every place in &lt;code&gt;workLocations&lt;/code&gt;. One request per job.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Barry's fills the pay field, one entry per location
&lt;/h2&gt;

&lt;p&gt;Barry's opens a separate job for each studio, so the field is a pay map. Its 16 Client Facilities Lead jobs run from $19.50 an hour in Southlake to $22.00 in Scottsdale and $22.50 to $23.00 in Los Angeles; Boston is $20 to $21, New York $20 to $22. &lt;code&gt;frequency&lt;/code&gt; is &lt;code&gt;HOUR&lt;/code&gt; or &lt;code&gt;YEAR&lt;/code&gt;, and &lt;code&gt;rangeStart&lt;/code&gt; equal to &lt;code&gt;rangeEnd&lt;/code&gt; is one rate, not a range. All 12 Barry's records in the sample had the field filled.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Rippling and Carbon Health leave it empty and write it in the text
&lt;/h2&gt;

&lt;p&gt;On all 12 Rippling records and 11 of 12 Carbon Health records, &lt;code&gt;payRangeDetails&lt;/code&gt; was &lt;code&gt;[]&lt;/code&gt;. The pay is in &lt;code&gt;description.role&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rippling writes tiers by office: "$205,000 /year OTE uncapped (Remote) $210,000 ... (Office based: Seattle &amp;amp; Chicago) $215,000 ... (Office based: New York &amp;amp; San Francisco)", then "60/40 commission split for base/variable pay". That is on-target earnings, base plus commission, and the base is 60% of it. Every posting's &lt;code&gt;company&lt;/code&gt; part also says Rippling "has raised $1.2B", which is not anybody's pay.&lt;/li&gt;
&lt;li&gt;Carbon Health writes two roles into one posting: "Physician Salary: $ 235,000 - $310,000 NP/PA Salary: $170,000 - $240,000", on a job titled "Area Medical Director (Physician Assistant / Nurse Practitioner)". The text does not say which band is this job's; the title does.&lt;/li&gt;
&lt;li&gt;A Carbon Health internship in Bogotá offers a "Connectivity stipend of $150,000 COP per month": a dollar sign in pesos, on a stipend.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. BambooHR keeps it in one free-text line
&lt;/h2&gt;

&lt;p&gt;BambooHR's list (&lt;code&gt;https://&amp;lt;board&amp;gt;.bamboohr.com/careers/list&lt;/code&gt;) has ten fields and no description or date. Each job's record has both, and &lt;code&gt;compensation&lt;/code&gt;, typed by the employer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bh&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  &lt;span class="c"&gt;# a BambooHR board: the list's fields, then the first six jobs' own records&lt;/span&gt;
  &lt;span class="nv"&gt;H&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://&lt;span class="nv"&gt;$1&lt;/span&gt;.bamboohr.com/careers
  curl -s &lt;span class="s2"&gt;"$H/list"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; bh.json
  jq -c &lt;span class="s1"&gt;'{jobs: (.result | length), fields: (.result[0] | keys)}'&lt;/span&gt; bh.json
  jq -r &lt;span class="s1"&gt;'.result[:6][].id'&lt;/span&gt; bh.json &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; ID&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; sleep 2
    curl -s &lt;span class="s2"&gt;"$H/$ID/detail"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; jq -c &lt;span class="s1"&gt;'.result.jobOpening | {jobOpeningName, compensation, datePosted}'&lt;/span&gt;
  &lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
bh eggharborcafe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Egg Harbor Cafe answered "$21-$27 (hourly plus tips)" for a server and "$15-$21 (hourly plus tips)" for food runners, on jobs posted in 2023 and still open. Elsewhere, on the first six jobs of eight large boards: Journey Nursing Services "$21-24 (up to $26 with differentials)", where $26 is a ceiling; Food People "$50,000-$60,000" for a manager and "14-18" for a line cook, no unit at all; Fessler &amp;amp; Bowman "DOE" on five of six. Four of the eight boards left it empty on all six.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosted
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/glitchbound/rippling-jobs-scraper" rel="noopener noreferrer"&gt;Rippling Jobs Scraper&lt;/a&gt; on Apify takes &lt;code&gt;barrys-careers&lt;/code&gt;, &lt;code&gt;carbon-health&lt;/code&gt; or &lt;code&gt;rippling&lt;/code&gt; and returns each job once, with its other places in &lt;code&gt;offices&lt;/code&gt;. With &lt;code&gt;includeDescription&lt;/code&gt; it reads each job's record for the posting and the date, and returns the pay as &lt;code&gt;salaryMin&lt;/code&gt;, &lt;code&gt;salaryMax&lt;/code&gt;, &lt;code&gt;salaryCurrency&lt;/code&gt; and &lt;code&gt;salaryPeriod&lt;/code&gt;: from the field for the row's own location, or from the band the text states. Rippling's tiers come back as one band, 205,000 to 215,000 USD a year. The &lt;a href="https://apify.com/glitchbound/ats-jobs-scraper" rel="noopener noreferrer"&gt;Company Career Page Jobs Scraper&lt;/a&gt; does the same for BambooHR boards (&lt;code&gt;bamboohr:eggharborcafe&lt;/code&gt;). Every other system's public endpoint, with a tested &lt;code&gt;curl&lt;/code&gt;, is in &lt;a href="https://github.com/danielhagever/job-board-apis" rel="noopener noreferrer"&gt;job-board-apis&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>webscraping</category>
      <category>career</category>
    </item>
    <item>
      <title>Publix, General Atomics and Hobby Lobby jobs API: BrassRing job details and the pay questions each employer names</title>
      <dc:creator>glitchbound</dc:creator>
      <pubDate>Thu, 29 Oct 2026 15:57:00 +0000</pubDate>
      <link>https://dev.to/glitchbound/publix-general-atomics-and-hobby-lobby-jobs-api-brassring-job-details-and-the-pay-questions-each-32i8</link>
      <guid>https://dev.to/glitchbound/publix-general-atomics-and-hobby-lobby-jobs-api-brassring-job-details-and-the-pay-questions-each-32i8</guid>
      <description>&lt;p&gt;Publix (&lt;code&gt;partnerid=26173&amp;amp;siteid=5197&lt;/code&gt;), General Atomics (&lt;code&gt;25539/5313&lt;/code&gt;) and Hobby Lobby (&lt;code&gt;25879/5295&lt;/code&gt;) take applications through BrassRing. Its search, one host for every employer, is covered in the &lt;a href="https://dev.to/glitchbound/home-depot-walgreens-and-best-buy-jobs-api-the-brassring-careers-search-one-cookie-and-50-jobs-a-55m1"&gt;Home Depot, Walgreens and Best Buy write-up&lt;/a&gt;. The search answers with titles and places. Five things to know about the rest, measured on 2026-10-29 with a few seconds between requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The search is not the posting
&lt;/h2&gt;

&lt;p&gt;Publix's search results carry no description at all: none on its ten newest jobs, and none on the ten newest at ten other employers. The job's details, the request the site makes when you open a job, carry 3,991 to 7,218 characters on the same ten Publix jobs, and the pay. At Walgreens they give the posting, the qualifications and "Salary Range: Pharmacist - $60.90/hr-$82.30/hr".&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The details are one POST, with the same cookie and token
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;br&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  &lt;span class="c"&gt;# partner id, site id: a session, then the newest job's pay questions&lt;/span&gt;
  &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://sjobs.brassring.com
  curl -s -c jar &lt;span class="s2"&gt;"$B/TGnewUI/Search/Home/Home?partnerid=$1&amp;amp;siteid=$2"&lt;/span&gt; \
    &lt;span class="o"&gt;|&lt;/span&gt; grep -o &lt;span class="s1"&gt;'__RequestVerificationToken" type="hidden" value="[^"]*'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; cut -d&lt;span class="s1"&gt;'"'&lt;/span&gt; -f5 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; rft
  &lt;span class="nv"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$(&lt;/span&gt;curl -s -b jar -X POST -H &lt;span class="s2"&gt;"RFT: $(cat rft)"&lt;/span&gt; -H &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; \
    -d &lt;span class="s2"&gt;"{\"PartnerId\":\"$1\",\"SiteId\":\"$2\",\"Keyword\":\"\",\"Location\":\"\",\"KeywordCustomSolrFields\":\"JobTitle\",\"LocationCustomSolrFields\":\"Location\"}"&lt;/span&gt; \
    &lt;span class="s2"&gt;"$B/TgNewUI/Search/Ajax/MatchedJobs"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; jq -r &lt;span class="s1"&gt;'.Jobs.Job[0].Questions[] | select(.QuestionName == "reqid") | .Value'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  curl -s -b jar -X POST -H &lt;span class="s2"&gt;"RFT: $(cat rft)"&lt;/span&gt; -H &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; \
    -d &lt;span class="s2"&gt;"{\"partnerId\":\"$1\",\"siteId\":\"$2\",\"jobid\":\"$ID\",\"configMode\":\"\",\"jobSiteId\":\"$2\",\"turnOffHttps\":false}"&lt;/span&gt; \
    &lt;span class="s2"&gt;"$B/TgNewUI/Search/Ajax/JobDetails"&lt;/span&gt; \
    &lt;span class="o"&gt;|&lt;/span&gt; jq -c &lt;span class="s1"&gt;'[.ServiceResponse.Jobdetails.JobDetailQuestions[] | select(.QuestionName | test("Pay|Salary|Rate")) | {(.QuestionName): .AnswerValue}]'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
br 26173 5197    &lt;span class="c"&gt;# Publix&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That printed &lt;code&gt;[{"Frequency of Pay":"Weekly "},{"Minimum Base Pay":"18.60"},{"Maximum Base Pay":"25.65"},{"Potential Annual Base Pay":"38,688 - 53,352"}]&lt;/code&gt;. Each answer is one &lt;code&gt;JobDetailQuestions&lt;/code&gt; entry: a &lt;code&gt;QuestionName&lt;/code&gt;, an &lt;code&gt;AnswerValue&lt;/code&gt;, and the field it came from (&lt;code&gt;formtext84&lt;/code&gt; and the like).&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Every employer names its pay questions itself
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;br 25539 5313&lt;/code&gt; answered General Atomics' &lt;code&gt;{"Pay Range Low":"69,500"},{"Pay Range High":"116,375"}&lt;/code&gt;. Walgreens asks "Start Rate" and "Max Rate" beside "Pay Type: Hourly"; Best Buy "Minimum Pay" and "Maximum Pay"; the Army and Air Force Exchange Service "Salary Minimum" "$20.32" and "Salary Maximum" "$23.70"; Home Depot one "Pay Range" with one figure, "$20.50". ADM wraps its names in HTML comments, &lt;code&gt;&amp;lt;!--Pay Range--&amp;gt;&lt;/code&gt;, so match on the words inside. On the ten newest jobs at each, Walgreens, General Atomics, Best Buy, ADM and the Exchange had pay on all ten.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Read the question, not only the number
&lt;/h2&gt;

&lt;p&gt;Publix's "Frequency of Pay: Weekly" is how often it pays, not what the rate is per: 18.60 to 25.65 an hour, and its own "Potential Annual Base Pay" of 38,688 to 53,352 is those rates times 2,080 hours. Performance Food Group's "Compensation" says "Earning potential up to $37 per hour", which is a ceiling, not a rate. Edward Jones' "Salary Information" is a sentence about benefits with no figure in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. No pay question, then the posting
&lt;/h2&gt;

&lt;p&gt;Hobby Lobby asks none, and writes it into the posting instead, with no label in front of it: "Starting part-time range - $16.50 - $17.50 per hour", on each of its ten newest jobs. American Greetings writes that "starting pay is $15.30 per hour with two increases during the first year", and Performance Food Group "Pay: $17/base + $2/ premium = $19/hour", where the base is $17.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosted
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/glitchbound/ats-jobs-scraper" rel="noopener noreferrer"&gt;Company Career Page Jobs Scraper&lt;/a&gt; on Apify takes &lt;code&gt;Publix&lt;/code&gt;, &lt;code&gt;General Atomics&lt;/code&gt; or &lt;code&gt;Hobby Lobby&lt;/code&gt; by name. With &lt;code&gt;includeDescription&lt;/code&gt; it reads each job's details for the whole posting and returns the band its pay questions or its text state as &lt;code&gt;salaryMin&lt;/code&gt;, &lt;code&gt;salaryMax&lt;/code&gt;, &lt;code&gt;salaryCurrency&lt;/code&gt; and &lt;code&gt;salaryPeriod&lt;/code&gt;. Every other system's public endpoint, with a tested &lt;code&gt;curl&lt;/code&gt;, is in &lt;a href="https://github.com/danielhagever/job-board-apis" rel="noopener noreferrer"&gt;job-board-apis&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>webscraping</category>
      <category>career</category>
    </item>
    <item>
      <title>BAE Systems, Honda and Dollar Tree jobs API: the Phenom job page and the pay fields each employer names</title>
      <dc:creator>glitchbound</dc:creator>
      <pubDate>Thu, 29 Oct 2026 09:04:19 +0000</pubDate>
      <link>https://dev.to/glitchbound/bae-systems-honda-and-dollar-tree-jobs-api-the-phenom-job-page-and-the-pay-fields-each-employer-2dfc</link>
      <guid>https://dev.to/glitchbound/bae-systems-honda-and-dollar-tree-jobs-api-the-phenom-job-page-and-the-pay-fields-each-employer-2dfc</guid>
      <description>&lt;p&gt;BAE Systems (&lt;code&gt;jobs.baesystems.com&lt;/code&gt;), Honda (&lt;code&gt;careers.honda.com&lt;/code&gt;) and Dollar Tree (&lt;code&gt;careers.dollartree.com&lt;/code&gt;) run their careers sites on Phenom. The site's own search is one &lt;code&gt;POST&lt;/code&gt; to &lt;code&gt;/widgets&lt;/code&gt;, covered in the &lt;a href="https://dev.to/glitchbound/rtx-ups-and-cvs-health-jobs-api-the-phenom-careers-search-past-10000-jobs-42km"&gt;RTX, UPS and CVS Health write-up&lt;/a&gt;. That search returns a list. Five things to know about what the list leaves out, measured on 2026-10-28 and 29 with a few seconds between requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The list's description is a teaser, and not the posting's words
&lt;/h2&gt;

&lt;p&gt;Each job in the list has &lt;code&gt;descriptionTeaser&lt;/code&gt;: 307 to 413 characters on Honda's ten newest jobs, against 3,438 to 8,375 for the postings themselves. It is not the start of the posting either. Honda's newest teaser begins "We are looking for a SAP PP / QM Functional Analyst II", and the posting begins "What Makes a Honda, is Who makes a Honda". Of the newest 100 teasers at Honda, BAE Systems and Dollar Tree, none has a dollar figure.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The posting is on the job's own page
&lt;/h2&gt;

&lt;p&gt;Each job page embeds the site's data as &lt;code&gt;phApp.ddo&lt;/code&gt;, and &lt;code&gt;jobDetail.data.job&lt;/code&gt; in it is the whole record. The page's path comes from the job's &lt;code&gt;locale&lt;/code&gt; ("en_US" is &lt;code&gt;/us/en/&lt;/code&gt;, "en_GLOBAL" is &lt;code&gt;/global/en/&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;page&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  &lt;span class="c"&gt;# a Phenom host: the newest job's record, from the job's own page&lt;/span&gt;
  &lt;span class="nv"&gt;P&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$(&lt;/span&gt;curl -s -X POST &lt;span class="s2"&gt;"https://$1/widgets"&lt;/span&gt; -H &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; \
    -d &lt;span class="s1"&gt;'{"ddoKey":"refineSearch","jobs":true,"from":0,"size":1,"sort":{"order":"desc","field":"dateCreated"}}'&lt;/span&gt; \
    &lt;span class="o"&gt;|&lt;/span&gt; jq -r &lt;span class="s1"&gt;'.refineSearch.data.jobs[0] | (.locale | split("_") | "/\(.[1] | ascii_downcase)/\(.[0])") + "/job/" + .jobSeqNo'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  curl -s &lt;span class="s2"&gt;"https://$1$P"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; grep -o &lt;span class="s1"&gt;'phApp.ddo = {.*}; phApp.experimentData'&lt;/span&gt; \
    &lt;span class="o"&gt;|&lt;/span&gt; sed &lt;span class="s1"&gt;'s/^phApp.ddo = //; s/; phApp.experimentData$//'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; jq &lt;span class="s1"&gt;'.jobDetail.data.job'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
page jobs.baesystems.com &lt;span class="o"&gt;|&lt;/span&gt; jq -c &lt;span class="s1"&gt;'{salaryMin, salaryMax, payRange, chars: (.description | length)}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That printed &lt;code&gt;{"salaryMin":"136000","salaryMax":"231200","payRange":"231200 - 136000 USD per year","chars":9980}&lt;/code&gt;. robots.txt on these sites disallows &lt;code&gt;*/apply&lt;/code&gt;, &lt;code&gt;*/jobcart&lt;/code&gt; and &lt;code&gt;*/chatbot&lt;/code&gt;, not the job pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Every employer names its pay fields itself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;page careers.dollartree.com &lt;span class="o"&gt;|&lt;/span&gt; jq -c &lt;span class="s1"&gt;'{minSalary, maxSalary}'&lt;/span&gt;
page careers.honda.com &lt;span class="o"&gt;|&lt;/span&gt; jq -c &lt;span class="s1"&gt;'{salary, salaryRange}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dollar Tree answered &lt;code&gt;minSalary&lt;/code&gt; "$15.50" and &lt;code&gt;maxSalary&lt;/code&gt; "$16.00"; Honda &lt;code&gt;salary&lt;/code&gt; and &lt;code&gt;salaryRange&lt;/code&gt;, both "$80,900.00 - $101,100.00". Some of it is in the list already: Honda's &lt;code&gt;salary&lt;/code&gt; is filled on 280 of its 310 jobs, UPS's &lt;code&gt;openingPayRate&lt;/code&gt; ("$21.00/hour") on 241 of its newest 500, Dollar Tree's &lt;code&gt;salaryRange&lt;/code&gt; ("$20.50-$21.25") on 116 of its newest 500. BAE Systems keeps its numbers for the job page only.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Read the fields, then read them again
&lt;/h2&gt;

&lt;p&gt;BAE's &lt;code&gt;payRange&lt;/code&gt; writes the maximum first, so take the numbers from &lt;code&gt;salaryMin&lt;/code&gt; and &lt;code&gt;salaryMax&lt;/code&gt;. RTX has a &lt;code&gt;compensationGrade&lt;/code&gt; ("P4"), which is a grade, not an amount; its pay is in the description ("The salary range for this role is 107,500 USD - 204,500 USD"). STERIS fills &lt;code&gt;salaryMin&lt;/code&gt; and &lt;code&gt;salaryMax&lt;/code&gt; with 60,500 and 101,000 on a job whose posting says "Pay range for this opportunity is $68,637.50 - $88,825.00", a narrower band than its own fields: decide which one you mean. Bechtel's &lt;code&gt;salaryRange&lt;/code&gt; holds two quote characters and nothing else.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Without a field, the posting says it its own way
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;CoreCivic opens its postings with the pay and no label: "$86,635.00 / year".&lt;/li&gt;
&lt;li&gt;Trane writes "Annual Base Salary Range or Hourly Base Pay Range: $56,300.00 - $101,394.99", a label that names two periods; the numbers say which.&lt;/li&gt;
&lt;li&gt;Ecolab writes "42,000.00 - 52,500.00 - 63,000.00 USD Annual", a minimum, a midpoint and a maximum. The band is the outer two.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Hosted
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/glitchbound/ats-jobs-scraper" rel="noopener noreferrer"&gt;Company Career Page Jobs Scraper&lt;/a&gt; on Apify takes &lt;code&gt;BAE Systems&lt;/code&gt;, &lt;code&gt;Honda&lt;/code&gt; or &lt;code&gt;Dollar Tree&lt;/code&gt; by name. The list's own pay fields come with every run (Honda, UPS); with &lt;code&gt;includeDescription&lt;/code&gt; it reads each job's page for the whole posting and returns the band its fields or its text state as &lt;code&gt;salaryMin&lt;/code&gt;, &lt;code&gt;salaryMax&lt;/code&gt;, &lt;code&gt;salaryCurrency&lt;/code&gt; and &lt;code&gt;salaryPeriod&lt;/code&gt;. BAE's newest row came back with "136,000 - 231,200 USD (year)". Every other system's public endpoint, with a tested &lt;code&gt;curl&lt;/code&gt;, is in &lt;a href="https://github.com/danielhagever/job-board-apis" rel="noopener noreferrer"&gt;job-board-apis&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>webscraping</category>
      <category>career</category>
    </item>
    <item>
      <title>Vail Resorts, Compass Group and Guitar Center jobs API: the pay is in the posting, not in a field</title>
      <dc:creator>glitchbound</dc:creator>
      <pubDate>Wed, 28 Oct 2026 20:26:08 +0000</pubDate>
      <link>https://dev.to/glitchbound/vail-resorts-compass-group-and-guitar-center-jobs-api-the-pay-is-in-the-posting-not-in-a-field-2ik3</link>
      <guid>https://dev.to/glitchbound/vail-resorts-compass-group-and-guitar-center-jobs-api-the-pay-is-in-the-posting-not-in-a-field-2ik3</guid>
      <description>&lt;p&gt;Vail Resorts (&lt;code&gt;jobs.vailresortscareers.com&lt;/code&gt;) and Compass Group (&lt;code&gt;jobs.compassgroupcareers.com&lt;/code&gt;) publish their jobs on SAP SuccessFactors under their own domains, and Guitar Center (&lt;code&gt;guitarcenterexternal&lt;/code&gt;) on ADP. Neither system's public job list carries a pay amount, and all three employers state pay anyway, in the text of the posting. Five things to know about finding it, measured on 2026-10-28 with a few seconds between requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Vail Resorts: the search page lists, the job page pays
&lt;/h2&gt;

&lt;p&gt;The site's own search is a table that states its total, and each row links to a job page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;H&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://jobs.vailresortscareers.com
&lt;span class="nv"&gt;P&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$(&lt;/span&gt;curl -s &lt;span class="s2"&gt;"$H/search/?q=&amp;amp;sortColumn=referencedate&amp;amp;sortDirection=desc&amp;amp;startrow=0"&lt;/span&gt; \
  &lt;span class="o"&gt;|&lt;/span&gt; grep -o &lt;span class="s1"&gt;'href="/job/[^"]*"'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; head -1 &lt;span class="o"&gt;|&lt;/span&gt; cut -d&lt;span class="s1"&gt;'"'&lt;/span&gt; -f2&lt;span class="o"&gt;)&lt;/span&gt;
curl -s &lt;span class="s2"&gt;"$H$P"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; sed &lt;span class="s1"&gt;'s/&amp;lt;[^&amp;gt;]*&amp;gt;/ /g'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; grep -o &lt;span class="s1"&gt;'Starting Wage: [^A-Z]*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That printed "Starting Wage: $20.00/hr - $23.00/hr". The search page said "Results 1 to 25 of 1275", 25 a page with &lt;code&gt;startrow&lt;/code&gt; as the offset. Each of the newest ten jobs stated a starting wage the same way, under "Job Specifications".&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Compass Group: same system, other paths, four wordings
&lt;/h2&gt;

&lt;p&gt;Compass Group runs the same software and the same command finds nothing: its job links start with the country (&lt;code&gt;/United_States/job/...&lt;/code&gt;), so match &lt;code&gt;/job/&lt;/code&gt; anywhere in the path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;H&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://jobs.compassgroupcareers.com
curl -s &lt;span class="s2"&gt;"$H/search/?q=&amp;amp;sortColumn=referencedate&amp;amp;sortDirection=desc&amp;amp;startrow=0"&lt;/span&gt; \
  &lt;span class="o"&gt;|&lt;/span&gt; grep -o &lt;span class="s1"&gt;'jobTitle-link[^&amp;gt;]*href="[^"]*"'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; grep -o &lt;span class="s1"&gt;'/[^"]*/job/[^"]*'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;'!s[$0]++'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; head -8 \
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; P&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; sleep 3&lt;span class="o"&gt;;&lt;/span&gt; curl -s &lt;span class="s2"&gt;"$H$P"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; sed &lt;span class="s1"&gt;'s/&amp;lt;[^&amp;gt;]*&amp;gt;/ /g'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; tr -s &lt;span class="s1"&gt;' \n'&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; \
      &lt;span class="o"&gt;|&lt;/span&gt; grep -o &lt;span class="s1"&gt;'\(Pay Range\|Salary\|Pay Rate\) *: *\$[^A-Z]*'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; head -1&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four of the eight newest jobs stated pay, and no two the same way: "Salary: $60,000 - $72,000", "Pay Rate: $25.00/hr", "Pay Range: $14.00 per hour to $15.00 per hour." and "Pay Range : $18.98 to $18.98". The first has no period at all; nobody is paid $60,000 an hour, so it is a year. The last is one rate written as a range.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Guitar Center: ADP returns the description with the list
&lt;/h2&gt;

&lt;p&gt;ADP's public job list answers only with the organisation id from the site's own record, and returns every job's description in the same response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://myjobs.adp.com/public/staffing/v1
&lt;span class="nv"&gt;ORG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$(&lt;/span&gt;curl -s &lt;span class="s2"&gt;"$A/career-site/guitarcenterexternal"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; jq -r .orgoid&lt;span class="o"&gt;)&lt;/span&gt;
curl -s -H &lt;span class="s2"&gt;"orgoid: $ORG"&lt;/span&gt; &lt;span class="s2"&gt;"$A/job-requisitions?careerSiteDomain=guitarcenterexternal&amp;amp;\$top=100&amp;amp;\$skip=0&amp;amp;\$select=jobDescription"&lt;/span&gt; \
  &lt;span class="o"&gt;|&lt;/span&gt; jq -r &lt;span class="s1"&gt;'.count, .jobRequisitions[].jobDescription'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; sed &lt;span class="s1"&gt;'s/&amp;lt;[^&amp;gt;]*&amp;gt;/ /g; s/&amp;amp;nbsp;/ /g'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; tr -s &lt;span class="s1"&gt;' '&lt;/span&gt; \
  &lt;span class="o"&gt;|&lt;/span&gt; grep -o &lt;span class="s1"&gt;'^[0-9]*$\|Pay Rate: \$[0-9./hr $-]*'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; head -5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That printed 1763, Guitar Center's open jobs, then "Pay Rate: $15.92/hr - $23.88/hr" and three more. Of the first 100 descriptions, 31 state a pay rate: 30 after "Pay Rate:", one after "Hourly Rate:", and one of the 30 writes it "$16.94phr".&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Lever has a pay field, and Palantir writes it in the closing section
&lt;/h2&gt;

&lt;p&gt;Lever's public API returns a whole board in one GET, and it does have a pay field: &lt;code&gt;salaryRange&lt;/code&gt;, with &lt;code&gt;min&lt;/code&gt;, &lt;code&gt;max&lt;/code&gt;, &lt;code&gt;currency&lt;/code&gt; and an &lt;code&gt;interval&lt;/code&gt; such as "per-hour-wage". Of the 60 largest US Lever boards, 54 fill it in on some or all of their jobs. Palantir is not one of them. The posting comes in parts, &lt;code&gt;descriptionPlain&lt;/code&gt;, the requirement &lt;code&gt;lists&lt;/code&gt;, and &lt;code&gt;additional&lt;/code&gt;, the closing section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl -s &lt;span class="s2"&gt;"https://api.lever.co/v0/postings/palantir?mode=json"&lt;/span&gt; \
  &lt;span class="o"&gt;|&lt;/span&gt; jq &lt;span class="s1"&gt;'length, ([.[] | select(.additionalPlain // "" | test("\\$[0-9]"))] | length), ([.[] | select(.descriptionPlain // "" | test("\\$[0-9]"))] | length)'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That printed 303, 232 and 0: Palantir states a salary on 232 of its 303 postings ("The estimated salary range for this position is estimated to be $60,000 - $97,000/year"), and on every one of them it is in &lt;code&gt;additional&lt;/code&gt;. Read &lt;code&gt;salaryRange&lt;/code&gt; and &lt;code&gt;descriptionPlain&lt;/code&gt; alone, as ours did until this week, and Palantir's pay is on none.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The words around a figure decide whether it is the pay
&lt;/h2&gt;

&lt;p&gt;A dollar figure next to "pay" is not always the job's pay, and these were all in the same day's postings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Guitar Center's Music &amp;amp; Arts: "Pay Rate: $12.77/hr Non-Teaching Rate + $11-30/hr Teaching Rate". The band after the "+" is an addition; the pay is $12.77.&lt;/li&gt;
&lt;li&gt;Spectrum: "Earn $18.00/hour base pay, with the potential to earn $22.50/hour through commission". $22.50 is a ceiling, not the rate.&lt;/li&gt;
&lt;li&gt;A staffing board on Workable: "Pay Rate: $90.00 - $300.00 per case". A rate per case is not a wage, and with no period it cannot be turned into one.&lt;/li&gt;
&lt;li&gt;Guitar Center again: "$15.00/hr - $17.05/hr Non-Teaching Rate and up to $23.60/hr Total Teaching Rate". Take the first band, not the largest number.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Hosted
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/glitchbound/ats-jobs-scraper" rel="noopener noreferrer"&gt;Company Career Page Jobs Scraper&lt;/a&gt; on Apify takes &lt;code&gt;Vail Resorts&lt;/code&gt;, &lt;code&gt;Compass Group&lt;/code&gt; or &lt;code&gt;Guitar Center&lt;/code&gt; by name, and Palantir on the &lt;a href="https://apify.com/glitchbound/lever-jobs-scraper" rel="noopener noreferrer"&gt;Lever Jobs Scraper&lt;/a&gt;. With &lt;code&gt;includeDescription&lt;/code&gt; they read each posting and return the band as &lt;code&gt;salaryMin&lt;/code&gt;, &lt;code&gt;salaryMax&lt;/code&gt;, &lt;code&gt;salaryCurrency&lt;/code&gt; and &lt;code&gt;salaryPeriod&lt;/code&gt;, and none where the posting states none. Lever sends the whole posting and &lt;code&gt;salaryRange&lt;/code&gt; with every job, so Palantir's rows carry pay without it. Every other system's public endpoint, with a tested &lt;code&gt;curl&lt;/code&gt;, is in &lt;a href="https://github.com/danielhagever/job-board-apis" rel="noopener noreferrer"&gt;job-board-apis&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>webscraping</category>
      <category>career</category>
    </item>
  </channel>
</rss>
