Your cart is currently empty!
Author: Xavier Fok
-
Proxy Patterns for App Store Position Tracking (2026)
App store position tracking without proxies is broken by design. The App Store and Google Play serve different rankings based on country, device type, and even carrier — and if your scraper runs from a single datacenter IP, you’re getting one slice of a highly localized picture. Getting this right in 2026 means matching your proxy infrastructure to the way app store crawlers get blocked, rate-limited, and fingerprinted.
Why App Store Ranking Data Is Harder to Collect Than It Looks
Apple and Google both serve ranking data dynamically. A top-10 result for “expense tracker” in the US App Store differs from the same query in Singapore or Germany — and those differences update hourly during peak periods. The naive approach (curl from an AWS instance) gets you either a CAPTCHA wall or stale cached data within minutes.
The core problem is that app store endpoints use a combination of IP reputation scoring, request-rate fingerprinting, and geo-validation. Datacenter IPs are flagged almost immediately. Even well-known residential proxy pools with large subnets get rate-limited once they show consistent scraping patterns.
This is a structural problem, not a solvable-with-headers one. The fix is at the proxy layer, not the request layer.
Proxy Type Selection: Mobile First, Residential Second
For app store rank tracking specifically, mobile proxies outperform residential on every dimension that matters:
Proxy Type Trust Score Geo Accuracy Cost/GB Block Rate (App Stores) Datacenter Low Medium $0.50-1 Very high Residential Medium High $3-8 Medium Mobile (4G/5G) Very high Very high $8-20 Low ISP (static res.) Medium-high High $4-10 Medium Mobile IPs are assigned by carriers and rotate through CGNAT pools — the same infrastructure a real iPhone uses to browse the App Store. That’s exactly the signal app store anti-bot systems look for. The same logic applies to proxy selection for ticket bots, where trust-tier matching between proxy and platform determines whether you get real data or a block.
For rank tracking at scale across 10+ countries, you need country-matched mobile IPs. A Singapore mobile IP hitting the SG App Store will behave like a local user. A US residential IP doing the same thing may work but will show higher block rates over time, particularly for keyword search ranking endpoints.
Rotation Strategy: Request-Level vs. Session-Level
How you rotate matters as much as which proxy type you use. App store ranking checks fall into two patterns:
Request-level rotation (new IP per request) works for:
- Top charts by category
- Featured app lists
- Search result pages (non-personalized)
Session-level rotation (same IP for a sequence of requests) works for:
- Keyword ranking checks that require simulating a search + scroll + click flow
- A/B test visibility sampling
- Store listing page reads with embedded review counts
For session-level work, you want sticky sessions of 2-5 minutes per IP. Most mobile proxy providers support session tokens or sticky endpoints — use them. If you’re running parallel country checks, assign one sticky session per country per keyword batch.
This is the same session discipline that matters in proxy patterns for brand protection monitoring, where per-platform session consistency prevents false negatives from mid-session IP switches.
Building a Rank Tracking Scraper: Minimal Working Config
A basic Google Play rank tracker in Python using a rotating mobile proxy:
import httpx import time PROXY = "http://user-country-us-session-kw001:pass@mobile.proxypool.io:8080" def check_play_rank(keyword: str, app_id: str) -> int | None: url = "https://play.google.com/store/search" params = {"q": keyword, "c": "apps", "hl": "en", "gl": "US"} headers = { "User-Agent": "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36", "Accept-Language": "en-US,en;q=0.9", } try: r = httpx.get(url, params=params, headers=headers, proxies={"https://": PROXY}, timeout=15) r.raise_for_status() # parse position of app_id from result HTML return parse_rank(r.text, app_id) except httpx.HTTPStatusError as e: if e.response.status_code == 429: time.sleep(30) return NoneKey decisions in this config: Android UA paired with a mobile proxy (consistent fingerprint), session token in the proxy auth string for sticky behavior per keyword, and explicit 429 handling before retry. Running this with a datacenter proxy and a desktop Chrome UA is a common mismatch that inflates your block rate for no gain.
Multi-Country Tracking: Geo Assignment and Coverage
Running rank checks across 10+ countries introduces a prioritization problem. Not all markets need the same check frequency. A useful breakdown:
- Tier 1 markets (US, UK, DE, JP, AU) — check every 4-6 hours, mobile proxies required
- Tier 2 markets (SG, FR, BR, CA, KR) — check every 12 hours, residential proxies acceptable
- Tier 3 markets (remaining) — daily checks, ISP proxies usually sufficient
For Tier 1, budget for mobile proxies from carriers that match the market. US AT&T or T-Mobile IPs for the US App Store outperform generic residential on trust scoring. For Tier 2 and below, the cost savings from using residential are meaningful and the block rate difference is tolerable at 12+ hour intervals.
This tiered approach is similar to what proxy patterns for ad verification uses — high-trust proxies for high-value markets, cost-optimized tiers everywhere else.
When you scale past 20 countries, proxy health becomes a real operational concern. Stale IPs, misconfigured sticky sessions, and provider outages will silently corrupt your ranking data. The Build a Proxy Health Monitor guide covers how to build real-time status checks that catch these failures before they become data quality problems.
Common Failure Modes and Fixes
- Silent 200s with bad data: App stores sometimes return a 200 with a CAPTCHA page or empty results. Always validate that the response contains expected app count or known app IDs.
- Geo mismatch: Your proxy reports US location, but the App Store serves UK results. Verify geo assignment by hitting an IP-check endpoint before each session batch.
- Rate limit drift: Block rates increase gradually over days, not immediately. Monitor your 429 rate on a rolling 24-hour window, not just current session.
- UA inconsistency: Mobile proxies with desktop UAs get higher scrutiny. Match Android/iOS UA to mobile proxy type.
The proxy selection guide for sneaker bots has a useful breakdown of fingerprint consistency requirements that applies directly here — the same platform-signal matching logic that works for checkout flows works for app store API calls.
Bottom line
For app store position tracking in 2026, use mobile proxies for Tier 1 markets, session-level rotation for keyword flows, and a tiered geo strategy to keep costs in check without sacrificing data quality. Validate response content — not just status codes — at every step. DRT covers proxy infrastructure patterns like these in depth; if you’re building a tracking pipeline from scratch, the health monitoring and proxy selection guides are the right starting points before you write a line of scraping code.
Related guides on dataresearchtools.com
- Proxy Patterns for Ad Verification at Scale (2026)
- Proxy Patterns for Brand Protection Monitoring (2026)
- Proxy Selection for Sneaker Bots in 2026: Mobile vs ISP vs Residential
- Proxy Selection for Ticket Bots: Why Residential Wins (2026)
- Pillar: Build a Proxy Health Monitor: Real-Time Status Tracking
-
Proxy Patterns for Brand Protection Monitoring (2026)
Brand protection monitoring is one of the few scraping use cases where getting caught isn’t just inconvenient — it’s catastrophically bad for the data. Counterfeit listings disappear, grey-market sellers rotate domains, and unauthorized resellers swap pricing the moment they detect a crawler. Proxy patterns for brand protection monitoring have to be built around stealth, geographic precision, and high rotation frequency, or the data you collect is already stale by the time it lands in your pipeline.
Why Standard Residential Proxies Fall Short
Most brand protection teams start with a residential proxy pool and call it done. That works for basic price monitoring, but it breaks down fast when you need to:
- Detect unauthorized resellers across 15+ marketplaces simultaneously
- Verify geo-restricted counterfeit listings that only surface in specific countries
- Monitor social commerce platforms (TikTok Shop, Instagram Shopping) that fingerprint proxy ASNs aggressively
- Catch trademark violations on ad networks that rotate creatives by IP geolocation
Residential proxies from large pools (Bright Data, Oxylabs, Smartproxy) share exit IPs across thousands of customers. A single IP used for scraping Amazon one hour and brand-checking Shopify the next carries cross-contamination risk. Platforms that flag scraper IPs share blocklists — your “clean” residential IP may already be poisoned before you use it.
The better approach is proxy segmentation by platform tier.
Proxy Type Selection by Platform Tier
Not every marketplace deserves the same proxy budget. Tier your infrastructure to match detection sophistication:
Platform Type Recommended Proxy Rotation Frequency Typical Cost Amazon, eBay, Walmart Mobile (4G/5G) Per request $8-15/GB Shopify storefronts ISP residential Per session (5-10 min) $2-4/GB Social commerce (TikTok Shop) Mobile with sticky sessions Per product page $10-20/GB Brand registry portals Datacenter (authenticated) Per login session $0.10-0.50/GB Telegram/Discord grey markets SOCKS5 mobile Per conversation thread $12-18/GB Mobile proxies outperform residential for high-value targets because they present real carrier ASNs (Singtel, Verizon, EE) rather than ISP ranges that platforms have learned to profile. For the same reason they dominate in proxy selection for sneaker bots — the underlying fingerprint logic is identical: platforms want to see a real device on a real carrier network.
Geographic Targeting and the Country-IP Mismatch Problem
Counterfeit goods listings are often geo-fenced. A fake luxury handbag listing on a Southeast Asian marketplace may never appear to a US IP. Your monitoring stack has to request pages from within the target country, not from a proxy provider’s nearest PoP.
The critical mistake is using a provider’s “country targeting” feature without verifying actual exit geography. Many residential pools label IPs by billing country, not by where the device physically connects. Run a validation sweep before any monitoring campaign:
import httpx import asyncio async def verify_exit_geo(proxy_url: str, expected_country: str) -> dict: async with httpx.AsyncClient(proxies={"all://": proxy_url}, timeout=10) as client: r = await client.get("https://ipapi.co/json/") data = r.json() return { "proxy": proxy_url, "actual_country": data.get("country_code"), "asn": data.get("org"), "match": data.get("country_code") == expected_country } # Run against your pool before campaign launch results = asyncio.run(asyncio.gather(*[ verify_exit_geo(p, "SG") for p in proxy_pool[:50] ])) mismatches = [r for r in results if not r["match"]] print(f"{len(mismatches)} geo mismatches out of 50 sampled")In practice, 10-25% of “country-targeted” residential IPs fail this check with budget providers. For brand monitoring campaigns tied to specific markets (EU luxury goods enforcement, SG parallel imports), this mismatch rate produces false negatives — you conclude a listing doesn’t exist because your IP was routed through a different country.
Session and Fingerprint Management
Brand protection crawls have a different session lifecycle than standard price scrapers. You’re not just fetching a product page — you’re often navigating seller profiles, clicking through to contact pages, and capturing screenshots for legal evidence.
Key session rules:
- Bind one IP to one seller investigation workflow from start to finish. Never rotate mid-session on a seller profile page.
- Use browser-level fingerprinting (Playwright with real Chrome, not httpx) for any platform that serves React or Next.js storefronts.
- Set realistic viewport, locale, and timezone headers that match the exit IP’s country.
- Add 2-8 second jitter between page transitions — brand protection crawls are slower than price scrapers because the evidence chain matters.
- Archive full HTTP response headers alongside screenshots for legal defensibility.
This is the same session discipline required in ad verification at scale, where a session that switches IPs mid-audit produces evidence that opposing counsel can challenge in court.
Marketplace-Specific Patterns
Amazon Brand Registry Monitoring
Amazon’s Brand Registry portal itself uses authenticated sessions, so datacenter proxies are fine there. The risk surface is the public marketplace, where you’re scraping ASIN pages to detect unauthorized third-party sellers. Use residential or ISP proxies per ASIN batch, and rotate after every 15-20 requests per IP.
Watch for Amazon’s “dogs of war” pattern: they serve honeypot ASINs to scrapers with slightly wrong pricing data. If your monitoring pipeline shows wildly inconsistent prices that don’t match manual checks, you’re likely getting served poisoned responses.
App Store and Social Platform Monitoring
App store brand monitoring (fake apps impersonating your brand) uses a different proxy pattern to what’s needed for position tracking — you’re not tracking rank, you’re doing bulk search sweeps for trademark variations. Mobile proxies with country rotation work best here because Apple and Google serve different search results by device locale, not just IP geolocation.
Affiliate and Reseller Channel Audits
Unauthorized affiliate channels are the hardest to catch because they’re often behind branded subdomains or redirect chains. The scraping pattern has to follow the full redirect chain before rendering. A proxy that blocks CONNECT tunneling will silently drop the affiliate referral parameter, giving you a false “clean” result. This overlaps with the challenge covered in affiliate network validation — the proxy stack has to preserve redirect fidelity end to end.
Building a Rotation Architecture That Holds Up
A solid brand protection proxy architecture follows the same layered design as any serious scraping system. The proxy server architecture patterns guide covers the component breakdown in depth, but for brand protection the critical additions are:
- Evidence archiving layer: every request that captures a counterfeit listing must log the full response, exit IP, timestamp (UTC), and geo verification result. Store this in append-only object storage, not a mutable database.
- Dedup by seller fingerprint, not URL: the same counterfeit seller will rotate product URLs. Your dedup key should be seller ID + marketplace, not listing URL.
- Separate pools for evidence capture vs. discovery: use cheap residential for broad discovery sweeps, reserve mobile IPs for the evidence capture requests you’ll present to brand registry or legal counsel.
Failing to separate these pools is expensive. Mobile proxy bandwidth at $12-18/GB burns fast when you’re doing discovery sweeps across thousands of search queries. Keep mobile strictly for the 5-10% of findings that need defensible screenshots.
Bottom Line
For brand protection monitoring in 2026, use mobile proxies for evidence capture on tier-1 marketplaces, ISP residential for session-heavy storefronts, and datacenter only for authenticated brand registry portals. Always geo-verify your exit IPs before campaign launch — mislabeled country pools are the single biggest source of false negatives. DRT covers proxy infrastructure patterns for these exact production use cases, and the tradeoffs here hold across every marketplace vertical where legal defensibility of the captured data actually matters.
Related guides on dataresearchtools.com
-
Proxy Patterns for Continuous SEO Rank Tracking 2026: Cost vs Coverage
Continuous SEO rank tracking is deceptively expensive at scale. Pull 500 keywords across 10 locales daily and you are firing 5,000 requests every 24 hours — enough to trigger Google’s bot detection within minutes unless your proxy layer is designed for it. The proxy patterns for SEO rank tracking in 2026 are not the same ones you would use for e-commerce or social scraping, and the cost-vs-coverage tradeoff is sharper than most teams realize when they first spec out a tracker.
Why SEO Rank Tracking Has Unique Proxy Requirements
Google SERPs are the most aggressively defended scrape target on the web. Cloudflare, reCAPTCHA v3, and Google’s own fingerprinting stack all run in parallel. The failure mode is not a 403 — it is a degraded SERP that looks real but returns a CAPTCHA page or a country-mismatch result, silently poisoning your rank data.
The requirements that follow from this:
- Residential or mobile IPs only. Datacenter ranges are blocked or heavily throttled on Google Search within hours of high-volume use.
- Geo-matched IPs. A Singapore IP returning results for a UK keyword query will pull a geo-biased SERP. You need IPs in the same city or region as your target locale.
- Low request rate per IP. Google’s session model tolerates roughly 3-5 SERP requests per IP per hour before score degradation. Burst above that and you burn the IP.
- Consistent User-Agent + cookie jar pairing. Rotating UA without rotating cookies (or vice versa) creates a fingerprint mismatch that triggers detection faster than either alone.
Proxy Type Comparison: Residential vs Mobile vs ISP
Not all residential proxies behave the same way for rank tracking. Here is how the main categories compare in 2026 practice:
Type Pass Rate (Google) Cost per GB Best Use Case Residential rotating ~85-92% $3-8 Broad keyword sets, multi-locale Mobile (4G/5G) ~96-99% $12-25 High-value keywords, local packs ISP (static residential) ~88-94% $2-5 Consistent session tracking Datacenter ~30-55% $0.20-0.80 Not recommended for Google SERPs Mobile proxies have the highest pass rates because the IP ASN maps to a carrier, not a hosting provider, but the cost is 3-5x residential. The practical split most teams land on is mobile for the top 10-20% of keywords by revenue importance, and residential rotating for the long tail. If you are also running ad verification workflows, the same mobile pool can pull double duty — this overlap is discussed in Proxy Patterns for Ad Verification at Scale (2026).
Request Scheduling: The Architecture That Determines Cost
The biggest cost lever is not which proxy provider you pick — it is how you schedule requests. Naive rank trackers fire all keywords in parallel bursts at the same time each day. This maximizes IP burn rate and forces you to buy more bandwidth to compensate.
A better pattern:
- Spread requests across a 6-hour window. Pick a window that matches off-peak search activity in your target locale (typically 2am-8am local time). Fewer concurrent queries from the same IP pool means lower per-IP load.
- Group keywords by locale first, then by query intent. Brand keywords, local pack queries, and informational queries hit different SERP layouts. Grouping them lets you reuse the same proxy session for similar fingerprint patterns.
- Implement exponential backoff on 429s, not just retries. A flat retry loop burns IPs. Back off 30s, 2min, 8min before rotating to a fresh IP.
- Cache static SERP elements. Knowledge panels, featured snippets, and “People also ask” boxes change slowly. Pull them once daily at full fidelity, not on every keyword cycle.
A minimal Python scheduler config that applies jitter to avoid predictable timing:
import random, time def throttled_request(session, url, base_delay=12): jitter = random.uniform(0.5, 1.8) time.sleep(base_delay * jitter) return session.get(url, timeout=15)The
base_delayof 12 seconds keeps you under 5 requests per IP per hour at a single-thread level. Run 4 threads per proxy session and you hit roughly 20 requests per hour per IP — still within safe range for most residential providers.Locale Coverage Without Breaking the Budget
Multi-locale tracking is where budgets blow up. The temptation is to buy geo-targeted residential pools in every country you track. In practice, most teams only need city-level accuracy for local pack keywords — country-level IPs are sufficient for standard organic rankings.
A tiered coverage model keeps costs manageable:
- Tier 1 (city-level mobile): Primary revenue markets, local pack tracking, Google Maps rank checks
- Tier 2 (country-level residential): Secondary markets, informational keyword monitoring
- Tier 3 (shared rotating residential): Long-tail, low-priority, or experimental keyword sets
For affiliate sites doing competitive rank monitoring across many verticals, this same tiering logic applies to proxy selection decisions more broadly, as covered in Proxy Patterns for Affiliate Network Validation in 2026.
One underused tactic: for locales where you have low keyword volume (under 50 keywords), use a proxy provider’s on-demand geo-targeting rather than a dedicated pool. Providers like Oxylabs, Bright Data, and Smartproxy all support country+city targeting on their rotating residential endpoints with no minimum commitment.
Handling Detection and Result Validation
A proxy that returns a 200 is not the same as a proxy that returned a valid SERP. Google serves different page structures to suspected bots, and if your parser is not validating SERP structure before storing rank data, you are silently ingesting garbage.
Validation checks to run before writing rank data:
- Confirm the
#searchdiv or equivalent organic results container is present - Check that the number of organic results is within expected range (7-10 for standard queries)
- Verify the result URLs are real domains, not redirect traps
- Flag any response where the page title contains “unusual traffic” or CAPTCHA patterns
The same challenge of detecting degraded or fraudulent responses shows up in survey and earn-app proxy setups, where providers serve fake completion pages to suspected automation — Proxy Selection for Survey Sites and Earn Apps (2026) covers that validation pattern in detail.
For video and YouTube SERP tracking, the fingerprinting stack is slightly more lenient than Google Search but locale-matching matters even more because YouTube’s ranking algorithm is heavily localized. If you are tracking video properties alongside web rankings, YouTube SEO and Video Rank Tracking with Proxies (2026) lays out the specific IP requirements for YouTube SERP pulls.
The sneaker-drop proxy community solved rotating IP exhaustion under high detection pressure years before the SEO tracking world caught up — Proxy Selection for Limited Drop Sneaker Releases (2026) has relevant patterns for IP recycling and session warm-up that translate directly to rank tracker architecture.
Bottom Line
For most teams running 500-2,000 keywords daily, the right setup is residential rotating for the long tail plus a small mobile pool for local and high-value terms, with request jitter and SERP validation baked in from day one. Skipping validation is the most common reason rank data becomes unreliable under scale. DRT covers proxy infrastructure and scraping patterns across SEO, ad tech, and data collection — if this architecture is relevant to your stack, the other proxy use-case guides in this series are worth a read.
Related guides on dataresearchtools.com
-
How to Scrape Taleo Career Sites at Scale (2026)
Oracle Taleo powers job listings for thousands of enterprise employers — Fortune 500s, healthcare systems, government contractors — and scraping it at scale is genuinely harder than scraping most ATS platforms. The challenge is not just pagination; Taleo’s hosted endpoints vary by tenant subdomain, its JavaScript-heavy requisition pages resist simple HTTP fetches, and its rate limiting is aggressive enough to block naive scrapers within minutes.
How Taleo’s Architecture Works (and Why It Matters)
Taleo deployments fall into two patterns. The older “hosted” model puts job listings at a subdomain like
company.taleo.net/careersection/, while newer Oracle Recruiting Cloud (ORC) tenants serve listings underfa-xxxx.oraclecloud.com. Both use server-side rendered pages for the job list but load full requisition details via internal API calls.The key discovery: every Taleo instance exposes a semi-public REST endpoint at
/careersection/rest/jobboard/searchjobs(older hosted) or a GraphQL-style endpoint in ORC. Hitting this directly returns JSON, bypassing HTML parsing entirely and cutting scrape complexity by half.import httpx TENANT = "companyname" BASE = f"https://{TENANT}.taleo.net/careersection/rest/jobboard/searchjobs" params = { "multiln": "false", "lang": "en", "start": 0, "limit": 25, "portal": "1", } headers = { "Accept": "application/json", "Referer": f"https://{TENANT}.taleo.net/careersection/joblist.ftl", } resp = httpx.get(BASE, params=params, headers=headers, timeout=15) data = resp.json() jobs = data.get("requisitionList", [])Increment
startby 25 per page untilrequisitionListis empty. For ORC tenants, the endpoint changes to av2/jobspath — inspect XHR calls in DevTools to find it.Tenant Discovery at Scale
Scraping one company is trivial. Scraping thousands requires a tenant enumeration strategy. There is no official registry, so you build it from:
- LinkedIn job postings that include
taleo.netin the apply URL - Google dork:
site:taleo.net "careersection" "apply now" - Common Crawl extracts filtered by
taleo.nethostnames - Job board APIs (Indeed, ZipRecruiter) that leak the ATS apply URL
Once you have a list of subdomains, check liveness with a HEAD request to
/careersection/joblist.ftl. Expect 10–30% to return 404 (abandoned tenants) or redirect to the parent company’s careers page after an acquisition.If you are scraping Ashby or iCIMS tenants alongside Taleo for a talent pipeline, the same discovery pattern applies — see How to Scrape Ashby Career Sites for Talent Pipelines (2026) for a comparable approach, and How to Scrape iCIMS Career Sites (2026) for iCIMS-specific quirks.
Rate Limiting and Anti-Bot Behavior
Taleo hosted instances run Oracle’s WAF in front of a JBoss application server. The rate limits are tenant-configurable but typical defaults are:
Behavior Threshold Response Rapid sequential requests >10 req/s per IP 429 or silent 503 Missing Referer/Accept headers Any rate 403 Session cookie absence First request Redirect to login ORC tenants (FA-series) >5 req/s per IP Akamai bot challenge The session cookie issue is the most common failure mode. Taleo hosted requires a valid
JSESSIONIDplus aTaleoSIDcookie acquired from the initial page load. The REST endpoint will return a 302 to the login page if these are absent.Fix this by doing a single GET to
/careersection/joblist.ftlbefore hitting the REST endpoint, capturing cookies from the response, and forwarding them on all subsequent requests. Withhttpx, use aClientwith cookie jar enabled:with httpx.Client(follow_redirects=True) as client: client.get(f"https://{TENANT}.taleo.net/careersection/joblist.ftl") # cookies now populated resp = client.get(BASE, params=params, headers=headers)For ORC tenants on Akamai, residential proxies are unavoidable. Datacenter IPs get challenged immediately. The proxy rotation pattern is the same one covered in How Proxies Help Scrape Reviews at Scale: Yelp, Google, Trustpilot (2026) — one IP per session, rotate on 429, minimum 2-second delay between requisition fetches.
Parsing Requisition Detail Pages
The job list API returns metadata (title, location, req ID, posting date) but not the full description. To get the JD body, you need to hit the requisition detail page:
/careersection/10000/jobdetail.ftl?job={reqId}&lang=enThis is an HTML page. The description sits inside a
container. Parse with BeautifulSoup:from bs4 import BeautifulSoup detail_resp = client.get( f"https://{TENANT}.taleo.net/careersection/10000/jobdetail.ftl", params={"job": req_id, "lang": "en"}, ) soup = BeautifulSoup(detail_resp.text, "lxml") desc_div = soup.find("div", id="requisitionDescriptionInterface") description = desc_div.get_text(separator="\n").strip() if desc_div else ""Note: the
10000in the URL is the career section ID, not a real number. Different tenants use different IDs (10000, 10200, 5001, etc.). Check the job list page source for the correct value before scraping.Key fields to extract from detail pages:
- Job title, requisition ID, posting date
- Location (often structured as city, state, country separately)
- Employment type (full-time / contract / internship)
- Department and business unit
- Full JD text (HTML preserved for downstream parsing)
Infrastructure for Multi-Tenant Runs
Running this against hundreds of tenants in parallel requires a queue, not a loop. A simple architecture:
- Queue: Redis or SQS with tenant subdomains as items
- Workers: 4–8 async Python workers per machine, each managing its own httpx
Clientwith cookie jar - Proxy pool: Rotate IPs at the worker level, not per-request. Sticky sessions per tenant reduces cookie re-acquisition overhead.
- Storage: Write raw JSON and HTML to S3 or local disk first, parse separately. Parsing bugs should not require re-fetching.
- Dedup: Hash on
(tenant, reqId)to skip already-seen requisitions on incremental runs.
For comparison, the same async worker pattern applies well when scraping product data at scale — the worker isolation model described here is similar to what you would use for How to Scrape Amazon Best Sellers Across 18 Marketplaces (2026). For brand and company intelligence use cases that combine job data with public registry data, see How to Scrape Amazon Brand Registry Public Pages (2026) for a comparable enrichment workflow.
For incremental runs, check
postingDateagainst your last-seen timestamp per tenant rather than re-fetching the full listing. Most enterprise tenants post under 50 new roles per week, so a daily incremental pull with a 7-day lookback window covers 99% of new postings.Bottom Line
Taleo is scrapeable at scale once you handle the session cookie requirement and split your scrape into a fast list API call plus a slower detail HTML fetch. Target the
/rest/jobboard/searchjobsendpoint first; fall back to HTML parsing only if the tenant blocks it. Use residential proxies for ORC (FA-series) tenants and rate-limit to one requisition fetch every 2–3 seconds per IP. DRT covers patterns like this across the ATS and e-commerce scraping landscape for engineers who need production-grade pipelines, not toy examples.Related guides on dataresearchtools.com
- How to Scrape Ashby Career Sites for Talent Pipelines (2026)
- How to Scrape iCIMS Career Sites (2026)
- How to Scrape Amazon Best Sellers Across 18 Marketplaces (2026)
- How to Scrape Amazon Brand Registry Public Pages (2026)
- Pillar: How Proxies Help Scrape Reviews at Scale: Yelp, Google, Trustpilot (2026)
How to Scrape iCIMS Career Sites (2026)
iCIMS powers career portals for thousands of mid-to-enterprise employers, and scraping it at scale is harder than it looks. The platform serves job listings through JavaScript-rendered pages, enforces bot detection via Cloudflare and custom fingerprinting, and rotates URL structures across tenant subdomains. if you’re building a talent pipeline, competitive intelligence feed, or labor market dataset, here’s what actually works in 2026.
How iCIMS Structures Its Job Data
iCIMS career sites follow a predictable tenant subdomain pattern:
https://{company}.icims.com/jobs/search. each company gets its own subdomain, but the underlying HTML skeleton is consistent across tenants. job detail pages live at/jobs/{job_id}/joband contain the full description, location, department, and requisition metadata in both rendered HTML and a partially-hydrated JSON blob embedded in atag.that structured data block is your fastest extraction path. it follows the
JobPostingschema.org spec and includestitle,hiringOrganization,jobLocation,datePosted, anddescriptionwithout needing a headless browser for the detail page. the search listing page is the hard part -- it's React-rendered and paginated via XHR calls to a private REST API.Reverse-Engineering the Search API
the real extraction leverage comes from the iCIMS job search XHR endpoint, not the DOM. open DevTools on any
{tenant}.icims.com/jobs/searchpage, filter by XHR, and you'll see calls to:GET /jobs/search?ss=1&searchLocation=&searchCategory=&searchZip=&searchRadius=50&searchPositionType=&applyOnline=1&in_iframe=1that endpoint returns paginated JSON with
searchResultscontaining job IDs, titles, locations, and department labels. you can drive pagination withstartrowandmaxrowsparameters (default 10, max 25 per call). extract the IDs, then hit the detail endpoint for full content.import httpx, time TENANT = "yourcompany" BASE = f"https://{TENANT}.icims.com/jobs" def fetch_jobs(start=0, max_rows=25): params = { "ss": 1, "startrow": start, "maxrows": max_rows, "searchPositionType": "", "applyOnline": 1, "in_iframe": 1 } r = httpx.get(f"{BASE}/search", params=params, headers={ "User-Agent": "Mozilla/5.0 (compatible; research-bot/1.0)", "Referer": f"https://{TENANT}.icims.com/jobs/search" }, timeout=15) r.raise_for_status() return r.json() def fetch_detail(job_id): r = httpx.get(f"{BASE}/{job_id}/job", timeout=15) from bs4 import BeautifulSoup import json, re soup = BeautifulSoup(r.text, "html.parser") ld = soup.find("script", {"type": "application/ld+json"}) return json.loads(ld.string) if ld else {}add a 1-2 second delay between requests per tenant. iCIMS rate-limits by IP and will return 429s if you hammer a single subdomain.
Anti-Bot Layers and How to Route Around Them
iCIMS deployments vary in how aggressively they're protected. here's a practical breakdown:
protection layer frequency bypass approach Cloudflare challenge page ~40% of tenants residential proxy + TLS fingerprint match IP rate limiting (429) universal throttle + rotating proxies User-Agent fingerprinting moderate browser-like UA string + accept headers CAPTCHA on search rare (<5%) headless browser + solver Referrer checking common always set Refererto the search pagefor most tenants, httpx with a realistic User-Agent and a proper Referer header is enough. for Cloudflare-protected tenants, you'll need residential IPs. datacenter IPs get challenged or blocked outright on ~40% of iCIMS deployments.
the scraping patterns here are similar to what you'd encounter with How to Scrape Taleo Career Sites at Scale (2026) -- both platforms sit behind enterprise-grade CDN layers with tenant-level variance in protection strictness.
Scaling Across Hundreds of Tenants
scraping one iCIMS tenant is straightforward. scraping 500 of them for a labor market feed requires a different architecture:
- build a tenant discovery list -- iCIMS doesn't publish a directory, but you can source subdomains from job board aggregators, LinkedIn company pages, and certificate transparency logs (
crt.shquery:%.icims.com). - deduplicate subdomains and validate them with a HEAD request before adding to your queue.
- run per-tenant scrapers in parallel, but cap concurrency per IP to 2-3 tenants at a time.
- use a rotating residential proxy pool so each subdomain sees requests from varied IPs. per-tenant sticky sessions for 5-10 minutes prevent cookie invalidation mid-crawl.
- store raw JSON from the search API separately from parsed detail records so you can re-parse without re-fetching.
key fields to extract per job:
job_id,tenant,title,location.city,location.state,location.country,department,employment_type,date_posted,description_html. if you want structured skill extraction, run the raw description through an LLM after collection.for teams building similar pipelines against ATS platforms, the approaches covered in How to Scrape Ashby Career Sites for Talent Pipelines (2026) and How to Scrape Personio Career Sites (2026) show how the same tenant-discovery and schema-extraction pattern generalizes across vendors.
Common Errors and What They Mean
- 429 Too Many Requests -- you're hitting the rate limiter. back off 30-60 seconds, add jitter, reduce concurrency on that tenant.
- 403 Forbidden -- IP is blocked or Cloudflare challenge triggered. rotate to a residential proxy and retry with a fresh session.
- Empty
searchResultsarray -- tenant uses a custom iCIMS build with a different API path. fall back to DOM scraping the listing page directly. - Malformed JSON in ld+json block -- some older iCIMS tenants have invalid JSON in the schema.org tag. use
json.loadsinside a try/except and fall back to BeautifulSoup field extraction. - Redirect to login page -- the job is no longer active. log it as expired and skip.
this error taxonomy overlaps significantly with what you see in other structured-data scraping contexts. if you're also pulling product catalog data, the same proxy rotation and error handling patterns apply in environments like How to Scrape Amazon Best Sellers Across 18 Marketplaces (2026), where IP reputation is equally critical.
one underrated issue: iCIMS occasionally injects a
redirect for bots that don't execute JavaScript. if your scraper returns a page with zero job listings but a valid 200 status, check whether you landed on the noscript fallback. the giveaway is atag in the response body.the same JavaScript-rendering challenge comes up in entirely different verticals -- the JS-heavy listing pages in How to Scrape Latin American Real Estate Sites (Imovelweb, Mercado Libre) use almost identical anti-scrape patterns at the CDN layer.
Bottom Line
for most iCIMS tenants, the XHR search API plus schema.org ld+json extraction gets you clean, structured job data without a headless browser. residential proxies are only necessary for the ~40% of tenants running Cloudflare. scale across hundreds of tenants with a discovery pipeline built on crt.sh and careful per-IP throttling. this site covers these ATS and structured-data scraping targets in depth -- bookmark it if you're building any kind of labor market or recruiting intelligence feed.
Related guides on dataresearchtools.com
How to Scrape Ashby Career Sites for Talent Pipelines (2026)
Ashby has quietly become the ATS of choice for fast-growing startups and Series B+ companies, which makes scraping Ashby career sites a high-signal move for talent pipeline builders, recruiting agencies, and competitive intelligence teams. The problem is that Ashby’s job boards aren’t served from a single domain — each company self-hosts under a pattern like
jobs.ashbyhq.com/— and the rendering is React-based, which trips up naive scrapers.How Ashby Job Pages Are Structured
Every Ashby career site follows a predictable URL schema:
https://jobs.ashbyhq.com/{company-slug} https://jobs.ashbyhq.com/{company-slug}/{job-id}The listing page renders a JSON payload into the DOM, but Ashby also exposes a public API endpoint that returns structured job data without JavaScript rendering:
GET https://api.ashbyhq.com/posting-api/job-board/{company-slug}This is the cleanest extraction path. The response is JSON with fields like
title,team,location,isRemote,employmentType, andapplicationFormDefinition. No authentication required, no browser needed.import httpx, json SLUG = "linear" # replace with target company slug resp = httpx.get(f"https://api.ashbyhq.com/posting-api/job-board/{SLUG}", timeout=15) data = resp.json() for job in data.get("jobs", []): print(job["title"], "|", job.get("location", {}).get("name"), "|", job["id"])Run this against a list of target slugs and you have a structured talent pipeline feed in minutes.
Finding Company Slugs at Scale
The slug discovery problem is where most pipelines break. There’s no public directory of all Ashby customers, so you need to build your own list.
Three approaches that work in 2026:
- Google dork:
site:jobs.ashbyhq.comreturns thousands of indexed subpaths. Paginate through results and extract the slug from the URL path. - LinkedIn scrape: Filter companies by ATS tech stack using tools like Clay or Phantom Buster, which surface ATS provider from careers page redirects.
- Common Crawl: Query the March 2026 crawl for
jobs.ashbyhq.comhostnames and extract unique slugs from theurlcolumn in Athena or BigQuery.
For a talent agency scraping 500+ companies, a seeded Common Crawl query gives the highest coverage per compute dollar.
Anti-Bot Behaviour and Rate Limits
The posting API (
api.ashbyhq.com/posting-api) is intentionally public and low-friction. Ashby wants jobs indexed. That said, hammering it with concurrent requests will get your IP soft-blocked within minutes.Realistic limits from testing:
Behaviour Observed limit Concurrent requests (same IP) ~5 before 429s appear Requests per minute (single IP) ~60 sustained Cooldown after 429 30-90 seconds User-agent rejection Not enforced on API Bot detection on HTML pages Cloudflare Turnstile (varies by company) The HTML job listing pages (
jobs.ashbyhq.com) are a different story. Some companies enable Cloudflare Turnstile on the front-end, which means rendering them requires a headless browser or a Turnstile solver. For bulk data extraction, stick to the API — avoid the HTML path entirely unless you need application form fields that aren’t exposed in the JSON.Rotate IPs per company slug, not per request. A residential proxy pool with 1 request per slug per session keeps your fingerprint clean and stays well within Ashby’s tolerance. If you’re also scraping other ATS platforms in the same pipeline — say, Recruitee or Personio — use separate proxy sessions per provider to avoid cross-contamination of block signals.
Normalising Ashby Data for Cross-ATS Pipelines
Raw Ashby output doesn’t map cleanly to other ATS schemas. If you’re building a unified talent intelligence feed that also pulls from iCIMS or Taleo, normalisation is the unglamorous work that determines whether your pipeline is actually useful.
Ashby-specific fields to watch:
location.namecan be"Remote", a city, or a hybrid string like"New York, NY (Hybrid)"— parse these consistentlyemploymentTypeuses Ashby’s own enum:"FullTime","PartTime","Contract","Temporary"— remap to your schemateamis a nested object withidandname, not a flat stringcompensationTierappears only when the company has salary transparency enabled — treat it as optional
A canonical schema across ATS providers should use ISO 3166-1 alpha-2 for country codes, a
remote_typeenum (full,hybrid,none), and Unix timestamps forposted_at. Ashby’screatedAtfield is UTC ISO 8601, which is straightforward to convert.The same normalisation discipline applies when you’re pulling structured data from completely different verticals — the schema design lessons in How to Scrape Latin American Real Estate Sites cover multi-source field unification patterns that transfer directly to multi-ATS pipelines.
Running This at Scale
For a production pipeline covering 1,000+ Ashby companies, the architecture is straightforward:
- Orchestration: Temporal or a simple cron on a VPS — Ashby jobs don’t change by the minute, so daily or twice-daily refreshes are enough
- Queue: Redis or SQS with one task per company slug
- Workers: 10-20 concurrent workers, each with a dedicated residential IP session
- Storage: Postgres with a
ats_jobstable and a(company_slug, job_id, scraped_at)composite key for deduplication - Change detection: Hash the job list per slug on each run and only emit events when the hash changes
Short bullet checklist before going to production:
- Confirm the slug list covers your target company set (test 10 manually)
- Set
httpxtimeout to 15s and retry twice with exponential backoff on 5xx - Log 429s with the slug and timestamp — patterns reveal which companies have extra rate protection
- Store raw JSON alongside normalised rows — Ashby’s schema has changed twice in the past 18 months
For monitoring, track the ratio of slugs returning zero jobs vs. a non-empty list. A sudden spike in zero-job responses usually means your IP pool is blocked, not that all your targets froze hiring simultaneously.
Bottom Line
The Ashby posting API is genuinely scraper-friendly — use it instead of rendering HTML, rotate IPs at the slug level, and invest the saved complexity into normalisation and deduplication. If you’re building a serious multi-ATS talent pipeline, Ashby is one of the easier integrations; the harder work is schema consistency across providers. DRT covers ATS scraping patterns, proxy infrastructure, and data pipeline design in depth — the same principles apply whether you’re pulling from five job boards or five hundred.
Related guides on dataresearchtools.com
How to Scrape Personio Career Sites (2026)
Personio career sites are a goldmine for recruiting intelligence and job market analysis — and because Personio powers HR for thousands of European SMBs, scraping their public job boards gives you structured hiring data that isn’t reliably indexed on LinkedIn or Indeed. This guide covers how to scrape Personio career sites in 2026, including the two main URL patterns, anti-bot posture, and a working Python approach.
How Personio Career Pages Are Structured
Personio hosts career pages on two URL patterns:
https://{company}.jobs.personio.de/(German-hosted, common for EU companies)https://{company}.jobs.personio.com/(global variant)
Some companies embed the widget on their own domain via an iframe or JavaScript snippet, but the underlying data still comes from Personio’s API. Each job listing URL follows the pattern
/job/{id}, and the index page loads a full JSON payload on the client side — which is your primary extraction target.The job listing data is injected into the page as a
window.__NUXT__server-side rendered object, or in newer deployments, fetched from a JSON API endpoint athttps://{company}.jobs.personio.com/api/v1/jobs(no auth required). That API endpoint is the clean path — you bypass HTML parsing entirely.Extracting the JSON API Directly
The undocumented but stable endpoint returns a JSON array of all open roles:
import httpx import json COMPANY = "yourcompany" BASE = f"https://{COMPANY}.jobs.personio.com" def fetch_jobs(): r = httpx.get( f"{BASE}/api/v1/jobs", headers={"Accept": "application/json", "Accept-Language": "en"}, timeout=15, ) r.raise_for_status() return r.json() # list of job dicts jobs = fetch_jobs() for job in jobs: print(job["id"], job["name"], job.get("department", {}).get("name"))Each job object contains
id,name,department,office,employment_type,created_at, and ajob_descriptionsarray with HTML content blocks. For full JD text, you’ll need a second call to/job/{id}or parse thejob_descriptionskey already in the response.If the
/api/v1/jobsendpoint returns 404, fall back to scraping the rendered HTML and extracting theJSON blob, then parsing the nested state tree.Anti-Bot Posture and Rate Limits
Personio's job pages are relatively permissive compared to enterprise ATS platforms. There's no Cloudflare challenge on most subdomains, and Akamai is not present. That said, a few behaviors to watch for:
- Rate limiting: Aggressive crawling (more than ~30 req/min) triggers 429s from their CDN. Space requests with a 2-4 second jitter.
- User-Agent checks: The bare
python-httpxUA gets blocked on some subdomains. Use a realistic browser UA string. - Geo-blocking: Some Personio customers restrict their career page to specific regions. If you're scraping a German Mittelstand company from a US IP, you may get a redirect or empty results.
For the geo issue, residential proxies from a German or European IP pool resolve it cleanly. This is the same pattern used when scraping ATS platforms like SmartRecruiters hiring pages -- the target's CDN sees a local visitor rather than a datacenter range.
Issue Symptom Fix 429 Too Many Requests Burst of requests blocked Add jitter (2-4s), reduce concurrency Empty JSON array []response but page shows jobsSwitch Accept-Language header to de404 on /api/v1/jobsOlder Personio tenant Parse __NUXT_DATA__from HTMLGeo-block redirect 302 to /not-availableUse EU residential proxy iframe embed Jobs on company domain, not Personio Trace network tab for Personio API origin Parsing Job Detail Pages
If you need structured job descriptions (not just titles), here's the hierarchy to expect inside
job_descriptions:- Each object has a
namefield (section heading like "Your Role", "Requirements") - The
valuefield contains raw HTML - Nested
job_descriptionsarrays appear for grouped sections
A clean parse using BeautifulSoup:
from bs4 import BeautifulSoup def extract_text_blocks(job: dict) -> dict: sections = {} for block in job.get("job_descriptions", []): heading = block.get("name", "body") html = block.get("value", "") text = BeautifulSoup(html, "html.parser").get_text(separator="\n").strip() sections[heading] = text return sectionsThis is cleaner than scraping the rendered page and avoids the flaky CSS selectors that break when Personio updates their frontend. Similar structured-extraction approaches work well for Recruitee job pages, which also expose a JSON-first data layer.
Scaling Across Multiple Companies
If you're building a broader job market dataset -- tracking hiring velocity, team growth, or competitive intelligence -- you'll need to crawl many Personio tenants, not just one.
The main challenge is discovery. There's no public Personio company directory. Practical sourcing approaches:
- Search
site:jobs.personio.deorsite:jobs.personio.comon Google to surface active tenants - Pull from LinkedIn company pages (many link directly to their Personio career site)
- Use Crunchbase or Apollo to filter EU SMBs, then probe the Personio subdomain pattern
Once you have a list of subdomains, the
/api/v1/jobscall is identical across all tenants. A simple async crawler withhttpx.AsyncClientand a semaphore of 5-10 concurrent workers handles hundreds of companies per hour without triggering rate limits.For pipelines that need to stay current, schedule daily or weekly crawls and diff against your previous snapshot. New
created_attimestamps flag fresh postings; disappearing job IDs signal closed roles. Platforms like Ashby career sites use a similar versioned-listing model, making diff-based freshness detection a reusable pattern across ATS targets.If your use case extends beyond job data into broader HR intelligence -- org structure signals, headcount trends, or location expansion -- consider pairing Personio data with iCIMS career site scrapes to cover the mid-market US segment that Personio doesn't reach.
One note on scope: Personio job data is B2B recruiting intelligence. If your pipeline is pivoting toward real estate or property listings across emerging markets, the extraction patterns here generalize, though the tooling differs -- DRT has separate coverage on scraping Latin American real estate sites like Imovelweb and Mercado Libre for those use cases.
Handling Edge Cases
A few Personio-specific quirks that'll bite you in production:
- Multilingual listings: A job may have separate entries for
deandenlanguage variants. They share the same base ID but differ by locale parameter. Filter by?language=enquery param on the API call. - Department normalization: Department names are free-text set by the employer. "Engineering", "Tech", "R&D", and "Product & Engineering" all need to be mapped in your downstream schema.
- Employment type inconsistency: Some tenants use "Full-time" vs "Vollzeit" vs custom strings. Normalize to a canonical enum before storing.
- Deleted vs. filled roles: Personio removes filled roles from the index immediately. If you need historical data, snapshot on every crawl.
Bottom Line
Personio's
/api/v1/jobsendpoint is the right entry point -- skip the HTML, go straight to the JSON, add a realistic UA and EU residential proxy for geo-sensitive tenants, and run async crawls with conservative concurrency. The data is clean, consistently structured, and requires no authentication. For broader ATS coverage or freshness-detection patterns across European hiring markets, dataresearchtools.com covers the full stack of platforms engineers actually encounter in production pipelines.Related guides on dataresearchtools.com
How to Scrape Recruitee Pages for Lead Sourcing (2026)
Recruitee powers career pages for thousands of mid-market companies across Europe and North America, and its consistent URL structure makes it one of the more approachable ATS targets for lead sourcing at scale. If you’re building a list of companies actively hiring in a specific role, location, or tech stack, scraping Recruitee pages gives you a real-time signal that job boards like LinkedIn lag by days.
Understanding Recruitee’s URL and API Structure
Every Recruitee career site follows the same pattern:
https://{company}.recruitee.com/for the public jobs page, andhttps://{company}.recruitee.com/api/offers/for the JSON feed. That API endpoint is the main event — it returns structured job data without any rendering requirement.A typical response from
/api/offers/looks like this:{ "offers": [ { "id": 182934, "title": "Senior Data Engineer", "department": "Engineering", "location": "Amsterdam, Netherlands", "remote": true, "created_at": "2026-04-12T08:00:00Z", "career_url": "https://acme.recruitee.com/o/senior-data-engineer" } ] }No authentication, no token rotation, just a clean GET. For most companies this endpoint returns 200 with
Content-Type: application/json. It doesn’t paginate (all offers come back in one call), which keeps the scraper simple.The harder part is getting the list of company subdomains to query. There’s no public Recruitee directory, so you need to seed your target list from a separate source: Apollo, Crunchbase, or a curated vertical list. This is conceptually similar to the approach covered in How to Scrape Yellow Pages Business Data, where you build a domain list first, then loop your scraper over it.
Building the Scraper
Use
httpxwith async for throughput. Recruitee doesn’t aggressively rate-limit individual company subdomains, but if you’re hitting 1,000+ subdomains in one run, you’ll want concurrency caps and retry logic.import asyncio import httpx async def fetch_offers(client: httpx.AsyncClient, slug: str) -> dict: url = f"https://{slug}.recruitee.com/api/offers/" try: r = await client.get(url, timeout=10) if r.status_code == 200: return {"slug": slug, "offers": r.json().get("offers", [])} except (httpx.TimeoutException, httpx.RequestError): pass return {"slug": slug, "offers": []} async def main(slugs: list[str]): async with httpx.AsyncClient(follow_redirects=True) as client: tasks = [fetch_offers(client, s) for s in slugs] return await asyncio.gather(*tasks)Run this with a semaphore (limit to 20-30 concurrent) and you can process 5,000 companies in under 10 minutes on a standard VPS. The
follow_redirects=Truematters — some companies migrate away from Recruitee and the old subdomain 301s somewhere unhelpful.Useful fields to extract per offer:
title,department,location,remote,created_at,career_url. Thecreated_atfield is the most valuable for freshness filtering — jobs posted in the last 14 days indicate active hiring, which is a strong lead qualifier.Anti-Bot Considerations and Proxy Use
The JSON API endpoint is low-friction for most companies, but if you’re scraping the HTML career pages (to capture structured data not in the API, like required skills parsed from job descriptions), you’ll hit Cloudflare on some subdomains. Recruitee’s default configuration doesn’t block the API path, but aggressive crawling of the HTML listings will get your IP flagged.
For the API-only approach, residential proxies are overkill — a rotating datacenter pool at ~3 req/s per IP is fine. If you’re parsing HTML job descriptions at scale, use residential or mobile IPs and add a randomized delay between 1.5 and 4 seconds. Keep your User-Agent consistent with a recent Chrome build.
Compared to more heavily protected ATS platforms, Recruitee is relatively open:
ATS Platform API Available Cloudflare Present Auth Required JS Rendering Needed Recruitee Yes ( /api/offers/)Sometimes (HTML only) No No (API) Workday No public API Yes Yes Yes SmartRecruiters Partial Yes Sometimes Yes Personio No public API Varies No No Ashby Yes ( /api/job-board/)Minimal No No Workday is the most locked down by far — as covered in How to Scrape Workday Career Sites at Scale (2026), it requires full browser automation and per-tenant URL discovery. Recruitee is closer to Ashby in permissiveness, which is why it’s a good starting point if you’re new to ATS scraping.
Enriching and Qualifying the Lead Data
Raw job posting data alone is a weak lead signal. You need to layer on company-level attributes to prioritize outreach. A useful enrichment stack:
- Resolve the company slug to a domain using the Clearbit or Hunter enrichment API
- Cross-reference against your CRM to filter out existing customers or known churned accounts
- Pull headcount and funding stage from Apollo or Crunchbase to segment by company size
- Score by job recency — offers posted within the last 7 days get the highest priority
- Filter by department if you’re targeting specific buyers (e.g., only “Engineering” or “Data” roles indicate a technical buyer)
What the Recruitee API doesn’t give you is department headcount or seniority distribution across all open roles. For that, you’d need to aggregate across multiple job posts. If a company has 8 open engineering roles across data/ML/backend, that’s a much stronger signal than one generic posting.
This enrichment workflow mirrors what’s needed when targeting other ATS sources. How to Scrape SmartRecruiters Hiring Pages (2026) and How to Scrape Personio Career Sites (2026) cover similar enrichment approaches for those platforms — the enrichment logic is largely portable once you have normalized offer records.
Handling Edge Cases
A few things that will break a naive scraper:
- Subdomain not found (404/NXDOMAIN): Some companies deactivate their Recruitee account but the subdomain persists in your seed list. Catch DNS failures separately from HTTP errors and flag them for removal.
- Empty offers array: A company may have a valid Recruitee account with zero active postings. Log these separately — they’re worth re-checking in 30 days rather than discarding.
- Non-English job descriptions: Recruitee is popular in the Netherlands, Germany, and Poland. If your downstream NLP pipeline assumes English, add a language detection step (langdetect or fasttext) before parsing.
- Custom domains: Some companies configure a custom domain (e.g.,
jobs.acme.com) that proxies to Recruitee. The API path still works:https://jobs.acme.com/api/offers/. Check the page source for the Recruitee widget script to confirm.
How to Scrape Ashby Career Sites for Talent Pipelines (2026) documents a nearly identical custom-domain issue — it’s a common pattern across modern ATS platforms that support white-labeling.
Bottom Line
Recruitee’s
/api/offers/endpoint is the cleanest ATS scraping target available right now — no auth, no JS rendering, structured JSON out of the box. The real work is in building a quality seed list of company subdomains and enriching the output into actionable lead records. Start with a focused vertical (SaaS companies in the Netherlands, for example), validate your pipeline on 500 companies before scaling, and re-run the scrape weekly for fresh hiring signals. DRT covers this class of infrastructure scraping targets in depth — if you’re building a full multi-ATS pipeline, bookmark the full series.Related guides on dataresearchtools.com
How to Scrape SmartRecruiters Hiring Pages (2026)
It looks like write permission to
~/Desktop/drt-articles/is being blocked. can you grant access to that path, or let me know an alternative folder to save to?Related guides on dataresearchtools.com
How to Scrape Workday Career Sites at Scale (2026)
Workday career sites are some of the most frustrating scrape targets in the job data space — heavily JavaScript-rendered, rate-limited per IP, and protected by Cloudflare or Akamai depending on the employer. If you’re building a job aggregator, a recruiting intelligence tool, or a competitive headcount tracker, you need a reliable pipeline that handles Workday’s quirks without burning through proxies or getting your IP ranges blocked inside 48 hours.
How Workday Serves Job Data
Workday career pages follow a consistent pattern. The public-facing URL is typically
https://. The page shell loads via a React-based SPA, then fetches job listings through a GraphQL-like REST endpoint:.wd1.myworkdayjobs.com/en-US/ /jobs GET https://<company>.wd1.myworkdayjobs.com/wday/cxs/<company>/<tenant>/jobsThe payload is a POST with a JSON body:
{ "limit": 20, "offset": 0, "searchText": "", "locations": [], "categories": [] }This endpoint is unauthenticated for most public career sites, which makes it the cleanest extraction path. Skip Selenium entirely for the initial crawl — hit the API directly with
httpxorrequests, paginate by incrementingoffsetby 20, and parse thejobPostingsarray in the response. You’ll get title, location, requisition ID, and a relative URL per listing.The detail page for each job is a second request:
GET /jobDetails?jobPostingId=. This returns full description HTML in a JSON field. Parse it withBeautifulSouporlxmland you’re done.Anti-Bot Layers and Where They Kick In
The direct API approach works until it doesn’t. Workday deploys different protection stacks depending on the employer’s contract tier:
Protection Layer Trigger Symptoms Cloudflare Bot Management High request velocity from single IP 403 with CF ray header Akamai Bot Manager Headless browser fingerprint Empty response body or redirect loop Workday rate limiting >50 req/min per IP 429 with Retry-AfterheaderTenant-level blocks Repeated scraping of same tenant 503 or silent empty results For Fortune 500 employers — think Salesforce, JPMorgan, or Deloitte — you will hit Cloudflare or Akamai. For mid-market companies, basic IP rotation is usually sufficient.
Rotate residential or mobile proxies, not datacenter IPs. Workday’s bot scoring is sensitive to ASN reputation. A Singapore or US residential pool with 5-10 second request delays per IP handles the majority of mid-market tenants without triggering blocks. Unlike simpler ATS platforms such as Lever and Greenhouse, Workday applies consistent bot scoring across all employer tenants, so you can’t exploit per-tenant gaps.
Scaling Across Thousands of Workday Tenants
The harder engineering problem is discovery: finding all Workday tenants worth scraping. There is no public tenant directory.
Three practical approaches:
- Seed from LinkedIn company pages. Filter companies by ATS using tools like Apify’s LinkedIn Company Scraper or a custom crawler, then check for the
wd1.myworkdayjobs.compattern in theCareerslink. - Use Google dorks:
site:wd1.myworkdayjobs.com -site:myworkday.comreturns indexed tenant subdomains. Export 100-200 at a time, deduplicate, and build your tenant list. - Buy a commercial dataset. Revelio Labs and Coresignal both maintain ATS-tagged company datasets. $500-2000 gets you a CSV with Workday tenant slugs for 15,000+ companies.
Once you have tenants, the crawl architecture matters. A naive sequential crawler will take weeks at scale. Use a job queue (Celery + Redis, or RQ) with per-tenant rate limiting. Set a max concurrency of 1 request per tenant per minute and run 50-100 workers. At that rate, 10,000 tenants with an average of 30 job postings each is a ~6-hour full crawl.
import httpx import time def fetch_jobs(tenant: str, company: str, offset: int = 0) -> dict: url = f"https://{company}.wd1.myworkdayjobs.com/wday/cxs/{company}/{tenant}/jobs" payload = {"limit": 20, "offset": offset, "searchText": "", "locations": [], "categories": []} headers = { "Content-Type": "application/json", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36", } r = httpx.post(url, json=payload, headers=headers, timeout=15) r.raise_for_status() return r.json()For tenants behind Akamai, swap
httpxfor a Playwright or Camoufox session that passes browser fingerprinting. Keep headless sessions warm across multiple requests to the same tenant rather than spinning up a new context per page — cold browser fingerprints score worse than warm ones.Storing and Deduplicating Job Postings
Job postings are volatile. The same requisition ID appears across multiple crawl cycles, and companies close and reopen roles. Your schema needs a few things:
requisition_id+tenant_slugas a composite unique keyfirst_seen_atandlast_seen_attimestamps for freshness trackingis_activeboolean flipped to false when a job disappears from the feed- A
raw_jsoncolumn for the full response payload so you can reparse without re-crawling
PostgreSQL with a partial index on
(tenant_slug, is_active)handles tens of millions of rows without issue. If you’re running this alongside other ATS scrapers — SmartRecruiters or Recruitee for example — normalize job records into a single canonical schema with anats_sourcefield. Cross-ATS analysis gets much easier when the data model is unified from day one.Proxy Selection for Workday at Scale
Not all proxy types perform equally against Workday’s bot stack:
- Residential rotating proxies: best default. US residential pools (Brightdata, Oxylabs, Smartproxy) handle 90% of tenants. Expect $3-8 per GB.
- Mobile proxies: highest trust score, best for Cloudflare-protected Fortune 500 tenants. More expensive at $15-25/GB, but failure rates drop significantly. The same logic applies when scraping boutique recruitment sites that use shared Cloudflare plans.
- Datacenter proxies: avoid entirely for Workday. Block rates exceed 60% even on premium providers.
- ISP/static residential: middle ground. Good for low-volume, high-fidelity scraping of a fixed tenant list.
Proxy rotation strategy matters as much as proxy type. The same principles that apply to review site scraping hold here: use sticky sessions per tenant (not per request), keep session duration under 10 minutes, and retire any IP that returns a 429 or 403 for a minimum of 30 minutes before reassignment.
Key failure signals to handle in your retry logic:
429withRetry-After: back off for the specified duration, then retry with a fresh IP403with CF ray header: rotate IP immediately, add 5s jitter before retry- Empty
jobPostingsarray with HTTP 200: silent block, treat as soft failure, retry after 15 minutes - Connection timeout: infrastructure issue or hard IP block, retire IP for 1 hour
Bottom Line
Workday’s direct JSON API is your fastest path to structured job data — skip the browser automation unless you’re targeting the Cloudflare tier of employers. Pair it with residential or mobile proxy rotation, a per-tenant rate limiter, and a deduplication schema built around
requisition_id. At 50-100 workers, you can maintain a fresh, full-coverage dataset across 10,000+ tenants on a single cloud instance. DRT covers the full ATS scraping stack across all major platforms if you’re building a multi-source job data pipeline.Related guides on dataresearchtools.com
- How to Scrape Boutique Recruitment Site Postings (2026)
- How to Scrape Lever and Greenhouse Job Boards Programmatically (2026)
- How to Scrape SmartRecruiters Hiring Pages (2026)
- How to Scrape Recruitee Pages for Lead Sourcing (2026)
- Pillar: How Proxies Help Scrape Reviews at Scale: Yelp, Google, Trustpilot (2026)
- LinkedIn job postings that include