<?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: Damien Alleyne</title>
    <description>The latest articles on DEV Community by Damien Alleyne (@dalleyne).</description>
    <link>https://dev.to/dalleyne</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3748529%2F28d6f35f-9dc8-4abf-9573-3274a2f19a03.jpeg</url>
      <title>DEV Community: Damien Alleyne</title>
      <link>https://dev.to/dalleyne</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dalleyne"/>
    <language>en</language>
    <item>
      <title>The Job Isn't Writing Code. It's Knowing When the AI Is Wrong.</title>
      <dc:creator>Damien Alleyne</dc:creator>
      <pubDate>Fri, 20 Feb 2026 21:32:29 +0000</pubDate>
      <link>https://dev.to/dalleyne/the-job-isnt-writing-code-its-knowing-when-the-ai-is-wrong-4fek</link>
      <guid>https://dev.to/dalleyne/the-job-isnt-writing-code-its-knowing-when-the-ai-is-wrong-4fek</guid>
      <description>&lt;p&gt;I use an AI coding agent for almost everything on my job board &lt;a href="https://jobs.alleyne.dev/" rel="noopener noreferrer"&gt;GlobalRemote&lt;/a&gt;. It writes my scrapers, builds my CI pipelines, architects my database schemas. It's written the vast majority of the codebase.&lt;/p&gt;

&lt;p&gt;After a few months of building this way, I've noticed a pattern: the most valuable thing I do isn't writing code. It's catching where the AI gets it wrong — specifically the cases where the output looks correct but doesn't hold up once you think about it.&lt;/p&gt;

&lt;p&gt;Here are three recent examples.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Wrong Tool for the Job
&lt;/h2&gt;

&lt;p&gt;My pipeline extracts tech stack requirements from job postings using regex. A role showed up on the board with no tech stack listed. The AI investigated, found the regex wasn't matching that posting's format, and proposed expanding the regex pattern.&lt;/p&gt;

&lt;p&gt;Fair enough. But we already had LLMs classifying and extracting other fields from these same job descriptions. Why maintain a brittle regex when we could use the LLM we're already paying for?&lt;/p&gt;

&lt;p&gt;The agent agreed and built the LLM-based extraction instead. More resilient, handles edge cases the regex never would have caught.&lt;/p&gt;

&lt;p&gt;The AI optimized within the current approach. I questioned whether the approach itself was right. That's a pattern I keep seeing — AI agents are excellent at solving the problem you give them, but they don't question whether you're solving the right problem. That's still on you.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Technically Correct, Actually Misleading
&lt;/h2&gt;

&lt;p&gt;My pipeline extracted geographic data from a GitLab job posting — a role open in the US, Canada, France, Germany, Ireland, Netherlands, Spain, and the UK — and tagged it as &lt;code&gt;multi-region&lt;/code&gt; with regions &lt;code&gt;Americas&lt;/code&gt; and &lt;code&gt;Europe&lt;/code&gt;. I asked the agent to verify. It confirmed the data was accurate — the posting listed countries across both regions.&lt;/p&gt;

&lt;p&gt;The problem: if a user from Brazil sees "Americas", they'll assume they can apply. Someone in Hungary sees "Europe", same thing. But this job is only open in 8 specific countries.&lt;/p&gt;

&lt;p&gt;The agent hadn't considered this. It checked my existing data, found I already had a &lt;code&gt;select-countries&lt;/code&gt; badge for this situation, updated the job, and then updated the LLM extraction prompt so the system would get this distinction right on future runs.&lt;/p&gt;

&lt;p&gt;I caught this because I've been the person in a non-obvious country getting excluded from roles that say "Americas" or "Global Remote." I've had Zapier, Outliant, and others reject me on location after their postings implied I was eligible.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Silent Failure
&lt;/h2&gt;

&lt;p&gt;My pipeline ran on schedule. Scraped 39 jobs. Processed them. Reported: "No new entries to add." No errors, clean exit.&lt;/p&gt;

&lt;p&gt;Zero new jobs from 39 listings didn't seem right. I pulled the raw data and asked the agent to audit its own pipeline's decisions.&lt;/p&gt;

&lt;p&gt;It found two bugs. One was a dedup rule incorrectly matching a new job against a discontinued listing with a similar title — different posting, different job ID, valid salary data, silently dropped. The other was a salary field that the pipeline never parsed, so jobs with visible salary data were being dropped for "no salary transparency."&lt;/p&gt;

&lt;p&gt;The pipeline didn't error or warn. It reported success while quietly dropping valid jobs.&lt;/p&gt;

&lt;p&gt;I didn't catch this by reading code. I caught it because the output didn't pass a gut check.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Ben Shoemaker wrote a &lt;a href="https://www.benshoemaker.us/writing/in-defense-of-not-reading-the-code/" rel="noopener noreferrer"&gt;piece recently&lt;/a&gt; arguing that engineers should stop reading code line-by-line and invest in the "harness" — specs, tests, verification layers, trust boundaries. OpenAI calls this &lt;a href="https://openai.com/index/harness-engineering/" rel="noopener noreferrer"&gt;Harness Engineering&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Looking at these three examples through that lens, that's what I've been doing without realizing it. The AI handles production. I handle specification, trust boundaries, and the "does this actually make sense for my users?" layer.&lt;/p&gt;

&lt;p&gt;If you're an engineer building with AI tools right now, I'd suggest paying attention to the moments where you override the AI's suggestions. Those moments aren't interruptions to your workflow — they're the most valuable part of it. That's the skill set the market is shifting toward, and it's worth documenting for yourself even if you never publish it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm a Senior Software Engineer with over a decade of experience, including building internationalization systems serving 50M+ users. I write about building with AI at&lt;/em&gt; &lt;a href="http://blog.alleyne.dev" rel="noopener noreferrer"&gt;&lt;em&gt;blog.alleyne.dev&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>career</category>
      <category>coding</category>
    </item>
    <item>
      <title>I Benchmarked 6 LLMs to Automate My Job Board for $0.35/Month</title>
      <dc:creator>Damien Alleyne</dc:creator>
      <pubDate>Tue, 10 Feb 2026 14:00:26 +0000</pubDate>
      <link>https://dev.to/dalleyne/i-benchmarked-6-llms-to-automate-my-job-board-for-035month-3j3a</link>
      <guid>https://dev.to/dalleyne/i-benchmarked-6-llms-to-automate-my-job-board-for-035month-3j3a</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;I run a curated remote job board (&lt;a href="https://jobs.alleyne.dev" rel="noopener noreferrer"&gt;GlobalRemote&lt;/a&gt;) focused on established remote-first companies with transparent salaries that hire globally — companies like GitLab, Automattic, Buffer, and Zapier. I &lt;a href="https://blog.alleyne.dev/from-interview-surprise-to-mvp-testing-developer-job-transparency" rel="noopener noreferrer"&gt;started it last September&lt;/a&gt; to scratch my own itch, manually curating every listing — researching interview processes, verifying geographic restrictions, and cross-referencing salary data. That worked for a small board, but didn't scale.&lt;/p&gt;

&lt;p&gt;So I &lt;a href="https://blog.alleyne.dev/i-built-2-job-scrapers-in-one-weekend-to-avoid-paying-for-data" rel="noopener noreferrer"&gt;built custom Apify scrapers&lt;/a&gt; with department-level filtering to pull only engineering, product, design, and data roles from Greenhouse and Ashby boards. That cut the noise by 80%, but I still needed to automatically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Classify&lt;/strong&gt; whether a scraped job is a relevant tech role (engineer, designer, data scientist, PM) — department filtering catches the obvious non-tech roles, but borderline titles like "Integrations Consultant" or "Senior Sales Engineer" still slip through&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract&lt;/strong&gt; details that aren't in the job board's structured fields — geographic eligibility, regional variants, and salary data when it's buried in the description text&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Previously, this pipeline ran locally using Ollama with qwen3:8b. I wanted to move it entirely to the cloud (GitHub Actions) using cheap API models, so it runs automatically twice a week without my local machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Question
&lt;/h2&gt;

&lt;p&gt;Which cloud LLM models give the best accuracy for classification and extraction, at the lowest cost? Should we use the same model for both tasks, or different models for each?&lt;/p&gt;

&lt;h2&gt;
  
  
  Methodology
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Ground Truth Dataset
&lt;/h3&gt;

&lt;p&gt;I built a test set from my own production data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Classification (50 tests):&lt;/strong&gt; 25 jobs that ARE on my board (known relevant, with expected categories) + 25 jobs that are NOT relevant (sales, marketing, HR, finance, legal titles from the same companies)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extraction (5 tests):&lt;/strong&gt; Jobs with known geographic badges and salary ranges, covering &lt;code&gt;open-globally&lt;/code&gt;, &lt;code&gt;multi-region&lt;/code&gt;, &lt;code&gt;us-canada-only&lt;/code&gt;, &lt;code&gt;americas-only&lt;/code&gt;, and &lt;code&gt;null&lt;/code&gt; salary cases&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Models Tested
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Pricing (input/output per 1M tokens)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Haiku 4.5&lt;/td&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;Fast inference&lt;/td&gt;
&lt;td&gt;$0.80 / $4.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5 Mini&lt;/td&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;Reasoning&lt;/td&gt;
&lt;td&gt;~$0.15 / ~$0.60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5 Nano&lt;/td&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;Reasoning (smallest)&lt;/td&gt;
&lt;td&gt;~$0.05 / ~$0.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 2.5 Flash&lt;/td&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;Fast inference&lt;/td&gt;
&lt;td&gt;$0.15 / $0.60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 3 Flash (preview)&lt;/td&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;Fast inference (preview)&lt;/td&gt;
&lt;td&gt;~$0.15 / ~$0.60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3 8B&lt;/td&gt;
&lt;td&gt;Alibaba (via Ollama)&lt;/td&gt;
&lt;td&gt;Open-source&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; GPT-4o-mini and Gemini 2.0 Flash were also tested initially but replaced with their successors (GPT-5 Mini, Gemini 2.5 Flash) for the final benchmarks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompts
&lt;/h3&gt;

&lt;p&gt;Same prompts used across all models — the exact prompts from my production pipeline:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Classification prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Classify this job title for a tech job board. Respond with JSON only.

Title: {title}
Company: {company}

{"isRelevant": true/false, "category": "engineering|product|design|data|other", "reason": "1-3 words"}

Rules:
- engineering: Software Engineer, Platform Engineer, SRE, DevOps, QA, Solutions Engineer, Design Engineer, Developer Advocate, Security Engineer
- product: Product Manager ONLY (not Growth Manager, not Renewals Manager)
- design: Product Designer, UX Designer, Visual Designer, Brand Designer
- data: Data Scientist, Data Engineer, ML Engineer, AI Engineer, Research Scientist
- ALL other roles (sales, marketing, HR, support, finance, legal) → isRelevant: false, category: "other"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Extraction prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Extract job details from this posting. Respond with JSON only.

Title: {title}
Company: {company}
Text: {description}

{
  "geoBadge": "open-globally|americas-only|emea-only|...|multi-region|...",
  "regionalVariants": ["Americas", "EMEA", "APAC"] or null,
  "salaryMin": number or null,
  "salaryMax": number or null,
  "salaryCurrency": "USD" or "EUR" or "GBP" or "CAD" or null,
  "reasoning": "brief explanation"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Full Comparison Table
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;F1 (overall score)&lt;/th&gt;
&lt;th&gt;Precision (% flagged that were correct)&lt;/th&gt;
&lt;th&gt;Recall (% of relevant jobs caught)&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Geography&lt;/th&gt;
&lt;th&gt;Salary&lt;/th&gt;
&lt;th&gt;Cost/run&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GPT-5 Mini&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;94.1%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;92.3%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;96.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;96.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;80.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0.008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Claude Haiku 4.5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;91.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;95.7%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;88.0%&lt;/td&gt;
&lt;td&gt;88.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0.019&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 2.5 Flash&lt;/td&gt;
&lt;td&gt;89.8%&lt;/td&gt;
&lt;td&gt;91.7%&lt;/td&gt;
&lt;td&gt;88.0%&lt;/td&gt;
&lt;td&gt;88.0%&lt;/td&gt;
&lt;td&gt;80.0%&lt;/td&gt;
&lt;td&gt;80.0%&lt;/td&gt;
&lt;td&gt;$0.003&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 3 Flash (preview)&lt;/td&gt;
&lt;td&gt;89.4%&lt;/td&gt;
&lt;td&gt;95.5%&lt;/td&gt;
&lt;td&gt;84.0%&lt;/td&gt;
&lt;td&gt;84.0%&lt;/td&gt;
&lt;td&gt;40.0%&lt;/td&gt;
&lt;td&gt;40.0%&lt;/td&gt;
&lt;td&gt;$0.003&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3 8B&lt;/td&gt;
&lt;td&gt;85.7%&lt;/td&gt;
&lt;td&gt;77.4%&lt;/td&gt;
&lt;td&gt;96.0%&lt;/td&gt;
&lt;td&gt;92.0%&lt;/td&gt;
&lt;td&gt;60.0%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5 Nano&lt;/td&gt;
&lt;td&gt;23.3%&lt;/td&gt;
&lt;td&gt;27.8%&lt;/td&gt;
&lt;td&gt;20.0%&lt;/td&gt;
&lt;td&gt;20.0%&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;td&gt;$0.006&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Key Findings
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. GPT-5 Mini is the best classifier
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;F1: 94.1%&lt;/strong&gt; with 96% recall — it catches nearly every relevant job&lt;/li&gt;
&lt;li&gt;Only 1 false negative: "Senior Marketing Data Analyst" (ambiguous title)&lt;/li&gt;
&lt;li&gt;2 false positives: "Integrations Consultant" and "Senior Sales Engineer" (borderline roles)&lt;/li&gt;
&lt;li&gt;96% category accuracy — correctly distinguishes engineering vs. design vs. data&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. Claude Haiku 4.5 is the best extractor
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;100% geography badge accuracy&lt;/strong&gt; — correctly identifies open-globally, multi-region, us-canada-only, americas-only&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;100% salary accuracy&lt;/strong&gt; — extracts exact numbers and currency, handles null correctly&lt;/li&gt;
&lt;li&gt;Classification is good (91.7% F1) but misses some edge cases like "Growth Designer"&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3. GPT-5 Nano is unusable
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;23.3% F1, 0% extraction accuracy — massive JSON parse errors&lt;/li&gt;
&lt;li&gt;Classified most jobs as irrelevant, couldn't extract structured data&lt;/li&gt;
&lt;li&gt;Despite being cheapest, it costs MORE than Gemini 2.5 Flash while being terrible&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verdict: Do not use GPT-5 Nano for structured extraction tasks&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  4. Gemini 3 Flash (preview) has JSON reliability issues
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Classification is decent (89.4% F1) but extraction fails 60% of the time with parse errors&lt;/li&gt;
&lt;li&gt;The preview model wraps JSON in markdown code blocks or adds commentary&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not production-ready yet&lt;/strong&gt; — wait for GA&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  5. Gemini 2.5 Flash is the budget option
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Cheapest cloud model at $0.003/run&lt;/li&gt;
&lt;li&gt;Decent classification (89.8% F1) but weaker extraction (80% geography, 80% salary)&lt;/li&gt;
&lt;li&gt;One parse error on a EUR salary extraction test&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  6. Qwen3 8B is surprisingly capable
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Free and runs locally&lt;/li&gt;
&lt;li&gt;85.7% F1 classification — decent but too many false positives (7)&lt;/li&gt;
&lt;li&gt;100% salary extraction but only 60% geography badge accuracy&lt;/li&gt;
&lt;li&gt;Misclassifies "multi-region" as "open-globally" consistently&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Error Pattern Analysis
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Common false negatives across models:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Senior Marketing Data Analyst" — every model flagged this as marketing, not data. Ambiguous title.&lt;/li&gt;
&lt;li&gt;"Data Analyst, Customer Intelligence" — "Customer" in title triggers exclusion&lt;/li&gt;
&lt;li&gt;"Senior Growth Designer" — "Growth" confuses classification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Common false positives:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Integrations Consultant - Americas" — every model said "engineering" (borderline role)&lt;/li&gt;
&lt;li&gt;"Senior Sales Engineer" — GPT-5 Mini incorrectly treated as engineering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Extraction patterns:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Americas" region consistently extracted correctly by Claude Haiku&lt;/li&gt;
&lt;li&gt;"US and Europe" → "multi-region" was the hardest badge to get right&lt;/li&gt;
&lt;li&gt;EUR salary with different format than USD tripped up Gemini models&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Decision: Hybrid Model Strategy
&lt;/h2&gt;

&lt;p&gt;Based on benchmarks, I implemented &lt;strong&gt;task-based model routing&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Primary Model&lt;/th&gt;
&lt;th&gt;Fallback&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Classification&lt;/td&gt;
&lt;td&gt;GPT-5 Mini&lt;/td&gt;
&lt;td&gt;Claude Haiku → Ollama&lt;/td&gt;
&lt;td&gt;Best F1 (94.1%), best recall (96%), best category accuracy (96%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extraction&lt;/td&gt;
&lt;td&gt;Claude Haiku&lt;/td&gt;
&lt;td&gt;GPT-5 Mini → Ollama&lt;/td&gt;
&lt;td&gt;Perfect geography + salary (100%)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Cost Estimate
&lt;/h3&gt;

&lt;p&gt;Per twice-weekly ingestion run (~50-100 jobs to classify, ~10-20 to extract):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Estimated tokens&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Classification&lt;/td&gt;
&lt;td&gt;GPT-5 Mini&lt;/td&gt;
&lt;td&gt;~30K input, ~5K output&lt;/td&gt;
&lt;td&gt;~$0.008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extraction&lt;/td&gt;
&lt;td&gt;Claude Haiku&lt;/td&gt;
&lt;td&gt;~20K input, ~5K output&lt;/td&gt;
&lt;td&gt;~$0.036&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total per run&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$0.044&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monthly (8 runs)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$0.35&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;The routing is handled in a small LLM abstraction layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TASK_ROUTING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claude&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ollama&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;   &lt;span class="c1"&gt;// GPT-5 Mini first&lt;/span&gt;
  &lt;span class="na"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claude&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ollama&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;   &lt;span class="c1"&gt;// Claude Haiku first&lt;/span&gt;
  &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claude&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ollama&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Pipeline calls specify the task:&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;batchGenerateJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;classificationPrompts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;classify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;batchGenerateJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;extractionPrompts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;extract&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each task resolves to the best available model in priority order. If OpenAI is down, classification falls back to Claude Haiku. If Anthropic is down, extraction falls back to GPT-5 Mini. Ollama is available as a fallback for local development, but isn't used in the cloud pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  GPT-5 API Gotchas
&lt;/h2&gt;

&lt;p&gt;If you're migrating from GPT-4o-mini to GPT-5 models, watch out for:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No &lt;code&gt;temperature&lt;/code&gt; parameter&lt;/strong&gt; — GPT-5 models are reasoning models (like o1/o3). They don't accept &lt;code&gt;temperature&lt;/code&gt;. Remove it entirely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;max_completion_tokens&lt;/code&gt; not &lt;code&gt;max_tokens&lt;/code&gt;&lt;/strong&gt; — The parameter name changed for reasoning models.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;response_format: { type: 'json_object' }&lt;/code&gt; still works&lt;/strong&gt; — JSON mode is supported via Chat Completions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chat Completions API still works&lt;/strong&gt; — Despite OpenAI promoting the new Responses API, Chat Completions hasn't been deprecated. For simple single-turn JSON extraction, Chat Completions is fine.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Timeline
&lt;/h2&gt;

&lt;p&gt;I ran a local pipeline with Ollama for a while, which worked but required my Mac to be on. Moving everything to the cloud was another weekend:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Benchmarked 6 cloud models to find the best fit for classification and extraction&lt;/li&gt;
&lt;li&gt;Built the cloud pipeline on GitHub Actions with hybrid model routing&lt;/li&gt;
&lt;li&gt;Hardened data quality — HTML-based requirements extraction, fuzzy title dedup, paid-trial badge detection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; Fully automated, runs twice a week, creates PRs for review, costs ~$0.35/month&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>webscraping</category>
      <category>llm</category>
    </item>
    <item>
      <title>I Built 2 Job Scrapers in One Weekend to Avoid Paying for Data</title>
      <dc:creator>Damien Alleyne</dc:creator>
      <pubDate>Mon, 02 Feb 2026 17:26:51 +0000</pubDate>
      <link>https://dev.to/dalleyne/i-built-2-job-scrapers-in-one-weekend-to-avoid-paying-for-data-4njp</link>
      <guid>https://dev.to/dalleyne/i-built-2-job-scrapers-in-one-weekend-to-avoid-paying-for-data-4njp</guid>
      <description>&lt;p&gt;I run &lt;a href="https://jobs.alleyne.dev" rel="noopener noreferrer"&gt;GlobalRemote&lt;/a&gt;, a curated job board that shows interview processes and hiring transparency upfront. To keep it relevant, I needed to update it &lt;strong&gt;2x per week&lt;/strong&gt; with fresh jobs from Greenhouse and Ashby boards.&lt;/p&gt;

&lt;p&gt;The problem? The scraper I was using fetched &lt;em&gt;every&lt;/em&gt; job from each company — Sales, HR, Support, everything — and stored it all in my Apify dataset. With 6-8 companies, that's 300-400 jobs per scrape, but only 5-10 were actually relevant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I was burning through my Apify free tier ($5/month, ~2000 dataset operations) on irrelevant data.&lt;/strong&gt; Two scrapes per week would blow past my quota. I wasn't ready to pay for a higher tier just to subsidize wasteful scraping.&lt;/p&gt;

&lt;p&gt;So my options were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Update infrequently (once every 2-3 weeks) and let the board go stale&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pay for a higher Apify tier to subsidize wasteful scraping&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Build my own scrapers with department filtering&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I chose #3.&lt;/p&gt;

&lt;p&gt;The scrapers are now &lt;a href="https://apify.com/dalleyne" rel="noopener noreferrer"&gt;live on Apify Store&lt;/a&gt;, open-source, and I'm dogfooding them on GlobalRemote right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: I Couldn't Update Frequently Enough
&lt;/h2&gt;

&lt;p&gt;The scraper I was using worked like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Fetch all jobs from a company's job board&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Store everything in an Apify dataset&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I filter locally for the jobs I actually want&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This makes sense if you want &lt;em&gt;all&lt;/em&gt; the jobs. But for a curated board like GlobalRemote, I only wanted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Engineering roles (not Sales, Marketing, HR)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;From specific departments (e.g., "Code Wrangling" at Automattic, "Engineering" at GitLab)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Recent postings (not 6-month-old listings)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With 300-400 jobs stored per scrape and only 5-10 relevant, I was wasting my dataset quota. &lt;strong&gt;Two scrapes per week would exceed my free tier limit.&lt;/strong&gt; The choice was: pay for a higher tier or update less frequently. Neither was ideal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Per-URL Department Filtering
&lt;/h2&gt;

&lt;p&gt;I built two Apify actors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://apify.com/dalleyne/greenhouse-job-scraper" rel="noopener noreferrer"&gt;&lt;strong&gt;Greenhouse Job Scraper&lt;/strong&gt;&lt;/a&gt; (Automattic, GitLab, Speechify, etc.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://apify.com/dalleyne/ashby-job-scraper" rel="noopener noreferrer"&gt;&lt;strong&gt;Ashby Job Scraper&lt;/strong&gt;&lt;/a&gt; (Buffer, Zapier, RevenueCat, etc.)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both support &lt;strong&gt;per-URL configuration&lt;/strong&gt;, meaning each company can have different filters:&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;"urls"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://job-boards.greenhouse.io/automatticcareers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"departments"&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="mi"&gt;307170&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"maxJobs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"daysBack"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&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;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://job-boards.greenhouse.io/gitlab"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"departments"&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="mi"&gt;4011044002&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"maxJobs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&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;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;The scraper:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Fetches department metadata&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Filters jobs by department ID &lt;em&gt;before&lt;/em&gt; storing them&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Only stores jobs that match your criteria&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You only pay for the jobs you actually get (not the ones filtered out)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; I went from storing 300-400 jobs per scrape to 30-50 jobs — an 80% reduction in dataset usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Apify platform&lt;/strong&gt; — handles hosting, scheduling, dataset storage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Greenhouse + Ashby APIs&lt;/strong&gt; — public APIs for job boards&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI (Claude)&lt;/strong&gt; — for rapid development&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How the APIs Work
&lt;/h3&gt;

&lt;p&gt;Both platforms expose public APIs for their job boards. This meant I could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Fetch departments/teams programmatically&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Filter by department/team ID before fetching job details&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Only pull full job data for matches&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No browser automation or HTML scraping needed&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is key: I'm filtering &lt;em&gt;before&lt;/em&gt; fetching details, not after. Most scrapers fetch everything, then you filter locally. Mine filters first, then only fetches what you need.&lt;/p&gt;

&lt;h3&gt;
  
  
  Development Process
&lt;/h3&gt;

&lt;p&gt;I built both scrapers over one weekend using AI (Claude).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Saturday (Jan 31):&lt;/strong&gt; Greenhouse scraper&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Prompt: "Build an Apify actor that scrapes Greenhouse job boards with department filtering"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AI figured out the API structure&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I tested on Automattic and GitLab job boards&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Sunday (Feb 1):&lt;/strong&gt; Ashby scraper&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Prompt: "Build an Apify actor for Ashby job boards with department filtering (similar structure to the existing Greenhouse scraper)"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AI figured out Ashby's API&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tested on Buffer, Zapier, RevenueCat&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What AI handled:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Reading API documentation (Greenhouse, Ashby, Apify actor structure)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Writing the scraper logic and Apify boilerplate&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Handling edge cases (null departments, missing dates)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generating input/output schemas&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What I did:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Product decisions (per-URL config vs global config)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Testing on real job boards&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Iterating when things didn't work&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Catching issues (e.g., updated Node 20 → 22 in Dockerfile)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;I never opened:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developers.greenhouse.io/job-board.html" rel="noopener noreferrer"&gt;Greenhouse API documentation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developers.ashbyhq.com/docs/public-job-posting-api" rel="noopener noreferrer"&gt;Ashby API documentation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.apify.com/platform/actors" rel="noopener noreferrer"&gt;Apify's actor documentation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Total development time: One weekend.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI is a co-pilot, not autopilot - but it handled all the research and boilerplate so I could focus on testing and product decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dogfooding on GlobalRemote
&lt;/h2&gt;

&lt;p&gt;I'm using both scrapers to populate &lt;a href="https://jobs.alleyne.dev" rel="noopener noreferrer"&gt;GlobalRemote&lt;/a&gt; right now.&lt;/p&gt;

&lt;p&gt;When I need fresh data, I trigger both scrapers. They return 30-50 relevant jobs instead of 300-400, keeping me well within my Apify free tier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I've learned from dogfooding:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Department filtering reduced dataset usage by ~80%&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I can now update regularly without exceeding my quota&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the scrapers break, GlobalRemote breaks. That's a strong incentive to keep them working.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Filter before storing, not after
&lt;/h3&gt;

&lt;p&gt;For curated job boards, filtering &lt;em&gt;before&lt;/em&gt; storage is way more cost-effective. The scraper I was using didn't do this.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Per-URL config beats global config
&lt;/h3&gt;

&lt;p&gt;My first version had global department filters (same filter for all companies). That was a mistake. Different companies organize departments differently. Per-URL config gives users way more flexibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Real examples &amp;gt; Fake examples
&lt;/h3&gt;

&lt;p&gt;In my README, I used &lt;em&gt;real&lt;/em&gt; companies (Automattic, GitLab) and &lt;em&gt;real&lt;/em&gt; department IDs (307170 = "Code Wrangling" at Automattic). Fake examples would've been useless for someone trying to replicate this.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. AI accelerates weekend projects into production tools
&lt;/h3&gt;

&lt;p&gt;I shipped two working scrapers in one weekend without reading a single API doc. AI handled research and implementation; I handled product decisions and testing. That's the real power of AI in 2026.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Open-sourcing on Apify was easy
&lt;/h3&gt;

&lt;p&gt;Publishing to Apify Store took ~10 minutes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add README&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set pricing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add input/output schemas&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add Banking information (they prefer PayPal)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click "Publish"&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Both scrapers are live and stable. I will be using them on GlobalRemote twice a week, well within my free tier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Potential improvements:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add automated tests (right now it's just manual verification)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add salary parsing to Ashby scraper (Greenhouse already extracts salary ranges)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Build a Lever scraper (if there's demand)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;But honestly?&lt;/strong&gt; I built these to solve my own problem. If other people find them useful, great. If not, I'm still updating GlobalRemote 2x/week without blowing my budget.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Greenhouse scraper:&lt;/strong&gt; &lt;a href="http://apify.com/dalleyne/greenhouse-job-scraper" rel="noopener noreferrer"&gt;apify.com/dalleyne/greenhouse-job-scraper&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ashby scraper:&lt;/strong&gt; &lt;a href="http://apify.com/dalleyne/ashby-job-scraper" rel="noopener noreferrer"&gt;apify.com/dalleyne/ashby-job-scraper&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;GlobalRemote:&lt;/strong&gt; &lt;a href="http://jobs.alleyne.dev" rel="noopener noreferrer"&gt;jobs.alleyne.dev&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; Both scrapers are MIT licensed - &lt;a href="https://github.com/d-alleyne/greenhouse-job-scraper" rel="noopener noreferrer"&gt;Greenhouse&lt;/a&gt; | &lt;a href="https://github.com/d-alleyne/ashby-job-scraper" rel="noopener noreferrer"&gt;Ashby&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building a job board or need ATS data, feel free to use them. And if you have feedback or find bugs, I'm on &lt;a href="https://linkedin.com/in/damienalleyne" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or reachable via Apify.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>showdev</category>
      <category>sideprojects</category>
      <category>webscraping</category>
    </item>
  </channel>
</rss>
