All platforms

Homerun Jobs API.

Modern ATS platform with design-focused career pages on .homerun.co subdomains. No public API - uses sitemap-based job discovery.

Homerun
Live
25K+jobs indexed monthly
<3haverage discovery time
1hrefresh interval
Companies using Homerun
Woonstad RotterdamLuneBreezeBoldkingTrait
Developer tools

Try the API.

Test Jobs, Feed, and Auto-Apply endpoints against https://connect.jobo.world with live request/response examples, then copy ready-to-use curl commands.

What's in every response.

Data fields, real-world applications, and the companies already running on Homerun.

Data fields
  • Design-focused career pages
  • Sitemap-based discovery
  • Atom feed support
  • Multi-language jobs (nl, en)
  • Server-side rendering
  • No authentication required
  • Lastmod timestamps for incremental updates
Use cases
  1. 01European job board aggregation
  2. 02Dutch company job monitoring
  3. 03Design-focused startup tracking
  4. 04Multi-language job extraction
Trusted by
Woonstad RotterdamLuneBreezeBoldkingTraitGhostDopper
DIY GUIDE

How to scrape Homerun.

Step-by-step guide to extracting jobs from Homerun-powered career pages—endpoints, authentication, and working code.

HTMLbeginnerNo official limits. Use 1-2 second delays between requests for respectful scraping.No auth

Discover jobs using the sitemap

Homerun provides reliable sitemap.xml files for each company. This is the most efficient way to discover all job URLs with a single HTTP request, eliminating the need for HTML parsing during discovery.

Step 1: Discover jobs using the sitemap
import requests
import xml.etree.ElementTree as ET

company_slug = "woonstad-rotterdam"
sitemap_url = f"https://{company_slug}.homerun.co/sitemap.xml"

response = requests.get(sitemap_url, timeout=10)
response.raise_for_status()

# Parse XML sitemap
root = ET.fromstring(response.content)
namespace = {"sm": "http://www.sitemaps.org/schemas/sitemap/0.9"}
urls = [loc.text for loc in root.findall(".//sm:loc", namespace)]

print(f"Found {len(urls)} URLs in sitemap")

Filter job URLs from sitemap entries

The sitemap contains homepage URLs, job listings, and apply pages. Filter out non-job URLs to get only the job posting links you need.

Step 2: Filter job URLs from sitemap entries
# Filter job URLs from sitemap
job_urls = [
    url for url in urls
    if not url.endswith("/apply")  # Exclude apply pages
    and url.split("/")[-1] != ""   # Exclude homepage
    and len(url.split("/")) > 4    # Has job slug in path
]

# Also extract lastmod dates for incremental updates
job_data = []
for url_elem in root.findall(".//sm:url", namespace):
    loc = url_elem.find("sm:loc", namespace)
    lastmod = url_elem.find("sm:lastmod", namespace)
    if loc is not None and not loc.text.endswith("/apply"):
        job_data.append({
            "url": loc.text,
            "lastmod": lastmod.text if lastmod is not None else None
        })

print(f"Found {len(job_urls)} job URLs")
for url in job_urls[:5]:
    print(f"  - {url}")

Parse job details from HTML

Fetch each job page and extract details using BeautifulSoup. All job content is server-side rendered, so no JavaScript execution is required.

Step 3: Parse job details from HTML
from bs4 import BeautifulSoup

def parse_job_details(url):
    response = requests.get(url, timeout=10)
    soup = BeautifulSoup(response.text, "html.parser")

    # Extract job title from h1 in main content
    title_elem = soup.select_one("main h1")
    title = title_elem.get_text(strip=True) if title_elem else "Unknown"

    # Extract full job description from main element
    main_content = soup.select_one("main")
    description_html = str(main_content) if main_content else ""
    description_text = main_content.get_text(strip=True) if main_content else ""

    # Extract any metadata available
    employment_type = None
    location = None

    # Look for common patterns in job cards
    for tag in soup.select("main a, main div"):
        text = tag.get_text(strip=True).lower()
        if any(t in text for t in ["fulltime", "parttime", "full-time", "part-time"]):
            employment_type = text.title()
            break

    return {
        "url": url,
        "title": title,
        "description_text": description_text[:500],
        "description_html": description_html,
        "employment_type": employment_type,
        "location": location,
    }

# Parse first job as example
if job_urls:
    job = parse_job_details(job_urls[0])
    print(f"Title: {job['title']}")
    print(f"Type: {job['employment_type']}")

Use Atom feed for richer structured data

Homerun also provides Atom feeds at feed.homerun.co with additional structured data including department, location, employment type, and salary information that may not be easily parsable from HTML.

Step 4: Use Atom feed for richer structured data
def fetch_atom_feed(company_slug):
    feed_url = f"https://feed.homerun.co/{company_slug}"

    try:
        response = requests.get(feed_url, timeout=10)
        response.raise_for_status()
    except requests.RequestException:
        print(f"Atom feed not available for {company_slug}")
        return []

    soup = BeautifulSoup(response.text, "xml")

    jobs = []
    for entry in soup.find_all("entry"):
        title_elem = entry.find("title")
        summary_elem = entry.find("summary")
        link_elem = entry.find("link")
        updated_elem = entry.find("updated")

        job = {
            "title": title_elem.get_text(strip=True) if title_elem else None,
            "summary": summary_elem.get_text(strip=True)[:300] if summary_elem else None,
            "url": link_elem.get("href") if link_elem else None,
            "updated": updated_elem.get_text(strip=True) if updated_elem else None,
        }

        # Extract custom namespace elements if available
        # Homerun may include department, location, salary in extensions
        jobs.append(job)

    return jobs

# Example usage
atom_jobs = fetch_atom_feed("breeze")
print(f"Found {len(atom_jobs)} jobs from Atom feed")
for job in atom_jobs[:3]:
    print(f"  - {job['title']}")

Handle rate limiting and errors

Implement respectful rate limiting and robust error handling when scraping multiple job pages. Add delays between requests and handle network failures gracefully.

Step 5: Handle rate limiting and errors
import time

def scrape_all_jobs(job_urls, delay=1.0):
    """Scrape all jobs with rate limiting and error handling."""
    jobs = []

    for i, url in enumerate(job_urls, 1):
        try:
            print(f"Scraping {i}/{len(job_urls)}: {url}")
            job = parse_job_details(url)
            jobs.append(job)
            time.sleep(delay)  # Respectful rate limiting
        except requests.RequestException as e:
            print(f"Network error on {url}: {e}")
            continue
        except Exception as e:
            print(f"Parse error on {url}: {e}")
            continue

    return jobs

# Scrape with 1 second delay between requests
all_jobs = scrape_all_jobs(job_urls, delay=1.0)
print(f"Successfully scraped {len(all_jobs)} jobs")
Common issues
criticalWrong domain extension (.homerun.hr instead of .homerun.co)

Homerun uses the .homerun.co domain for all company career pages. Always use https://{company}.homerun.co format. The .hr extension is for a different service.

highSitemap returns 404 for unknown company slugs

Not all company slugs are valid. Verify the company subdomain exists by checking if the homepage (https://{company}.homerun.co/) returns HTTP 200 before attempting to scrape jobs.

mediumNo master directory to discover all Homerun companies

Homerun does not provide a central sitemap listing all companies. Use web searches (site:*.homerun.co), monitor Homerun's customer showcase page, or check DNS records to find company subdomains.

mediumCustom domains redirect to homerun.co subdomains

Some companies use custom domains (e.g., careers.company.com) that redirect to homerun.co. Follow redirects and extract the actual homerun.co URL for consistent scraping. Use allow_redirects=True in requests.

lowLanguage variants create duplicate job entries

The sitemap shows base URLs without language codes. Jobs may appear in multiple languages at /en, /nl, etc. Deduplicate by extracting the job slug from the URL and storing only one language variant.

mediumAtom feed returns 404 for some companies

Not all companies have Atom feeds enabled. Always check the response status code before parsing. Fall back to sitemap + HTML parsing if the feed is unavailable.

Best practices
  1. 1Use sitemap.xml as primary discovery method - single request finds all jobs
  2. 2Check the Atom feed at feed.homerun.co first for richer structured data
  3. 3Implement 1-2 second delays between requests to be respectful
  4. 4Use sitemap lastmod dates for incremental updates instead of re-scraping
  5. 5Handle language variants by specifying preferred language (en, nl, etc.)
  6. 6Verify company slugs exist before bulk scraping to avoid 404 errors
Or skip the complexity

One endpoint. All Homerun jobs. No scraping, no sessions, no maintenance.

Get API access
cURL
curl "https://enterprise.jobo.world/api/jobs?sources=homerun" \
  -H "X-Api-Key: YOUR_KEY"
Ready to integrate

Access Homerun
job data today.

One API call. Structured data. No scraping infrastructure to build or maintain — start with the free tier and scale as you grow.

99.9%API uptime
<200msAvg response
50M+Jobs processed