All platforms

Eightfold Jobs API.

Pull enterprise job listings, full descriptions, and department data from Eightfold's REST APIs, auto-detecting whether a tenant runs the SmartApply or the newer PCSX endpoint pattern.

Get API access
Eightfold
Live
150K+jobs indexed monthly
<3haverage discovery time
1hrefresh interval
Companies using Eightfold
American ExpressStarbucksPayPalSiemensBayer
Developer tools

Try the API.

Test Jobs and Feed 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 Eightfold.

Data fields
  • Full HTML Job Descriptions
  • Department & Business Unit
  • Work Location & Remote Flexibility
  • Posting & Expiry Dates
  • ATS & Display Job IDs
  • Apply Redirect URLs
Use cases
  1. 01Enterprise Job Aggregation
  2. 02Competitive Hiring Intelligence
  3. 03Talent Market Research
  4. 04Job Board Syndication
Trusted by
American ExpressStarbucksPayPalSiemensBayerEatonMorgan StanleyNTT Data
DIY GUIDE

How to scrape Eightfold.

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

RESTintermediateNo official limit; scraper defaults to 200ms between requests, max 3 concurrent detail fetchesNo auth

Detect the API pattern and fetch listings

Eightfold tenants run one of two JSON APIs (SmartApply or PCSX). Probe the SmartApply endpoint first, then fall back to PCSX, to determine which pattern a company uses before fetching listings.

Step 1: Detect the API pattern and fetch listings
import requests

company = "aexp"  # American Express
domain = "aexp.com"
base_url = f"https://{company}.eightfold.ai"

# Try SmartApply API first
smartapply_url = f"{base_url}/api/apply/v2/jobs?domain={domain}&hl=en&start=0"
headers = {"Accept": "application/json", "Content-Type": "application/json"}

response = requests.get(smartapply_url, headers=headers)
if response.status_code == 200:
    data = response.json()
    jobs = data.get("positions", [])
    total = data.get("totalJobs", 0)
    print(f"SmartApply: Found {total} jobs, showing {len(jobs)}")
else:
    # Try PCSX API as fallback
    pcsx_url = f"{base_url}/api/pcsx/search?domain={domain}&query=&location=&start=0"
    response = requests.get(pcsx_url, headers=headers)
    if response.status_code == 200:
        data = response.json().get("data", {})
        jobs = data.get("positions", [])
        print(f"PCSX: Found {len(jobs)} jobs")

Parse the sitemap for complete job discovery

The sitemap lists every job URL without pagination limits, making it the most reliable way to enumerate the full set of job IDs for a tenant.

Step 2: Parse the sitemap for complete job discovery
import requests
import re
import xml.etree.ElementTree as ET

company = "aexp"
domain = "aexp.com"
sitemap_url = f"https://{company}.eightfold.ai/careers/sitemap.xml?domain={domain}"

response = requests.get(sitemap_url)
root = ET.fromstring(response.content)

# Extract job IDs from URLs
job_ids = []
namespace = {"ns": "http://www.sitemaps.org/schemas/sitemap/0.9"}

for url in root.findall(".//ns:url/ns:loc", namespace):
    match = re.search(r"/careers/job/(d+)", url.text)
    if match:
        job_ids.append(match.group(1))

print(f"Found {len(job_ids)} job IDs in sitemap")

Fetch job details for each listing

The listings response leaves job_description empty, so you must request each job's detail endpoint separately to get the full HTML description and metadata.

Step 3: Fetch job details for each listing
import requests
import time

company = "aexp"
domain = "aexp.com"
job_id = "39751247"
base_url = f"https://{company}.eightfold.ai"
headers = {"Accept": "application/json", "Content-Type": "application/json"}

# SmartApply details endpoint
details_url = f"{base_url}/api/apply/v2/jobs/{job_id}?domain={domain}&hl=en"
response = requests.get(details_url, headers=headers)

if response.status_code == 200:
    job = response.json()
    print({
        "id": job.get("id"),
        "title": job.get("name"),
        "location": job.get("location"),
        "department": job.get("department"),
        "description": job.get("job_description", "")[:200] + "...",
        "url": job.get("canonicalPositionUrl"),
    })

# For PCSX, use: /api/pcsx/position_details?position_id={job_id}&domain={domain}

Handle pagination for large job boards

When enumerating via the API instead of the sitemap, page through results by incrementing the start parameter by 10 per request until an empty positions array is returned.

Step 4: Handle pagination for large job boards
import requests
import time

company = "aexp"
domain = "aexp.com"
base_url = f"https://{company}.eightfold.ai"
headers = {"Accept": "application/json", "Content-Type": "application/json"}

all_jobs = []
start = 0
batch_size = 10

while True:
    url = f"{base_url}/api/apply/v2/jobs?domain={domain}&hl=en&start={start}"
    response = requests.get(url, headers=headers)

    if response.status_code != 200:
        break

    data = response.json()
    positions = data.get("positions", [])

    if not positions:
        break

    all_jobs.extend(positions)
    print(f"Fetched {len(positions)} jobs (total: {len(all_jobs)})")

    start += batch_size
    time.sleep(0.5)  # Rate limiting

print(f"Total jobs collected: {len(all_jobs)}")

Resolve the required domain parameter

Every API call needs a domain query param that matches the tenant's registered domain, not its subdomain slug. Use the known overrides or the {slug}.com fallback for eightfold.ai subdomains; for custom/vanity hosts, read the domain value embedded in the careers page HTML.

Step 5: Resolve the required domain parameter
import re
import requests
from urllib.parse import urlparse

# Known company -> domain overrides where the subdomain differs from the domain
KNOWN_DOMAINS = {
    "aexp": "aexp.com",
    "americanexpress": "aexp.com",
    "starbucks": "starbucks.com",
    "paypal": "paypal.com",
    "siemens": "siemens.com",
}

def infer_domain(careers_url: str) -> str | None:
    """Best-effort domain guess from an eightfold.ai subdomain."""
    host = urlparse(careers_url).netloc.lower()
    if host.endswith(".eightfold.ai"):
        slug = host[: -len(".eightfold.ai")]
        return KNOWN_DOMAINS.get(slug, f"{slug}.com")
    return None  # custom/vanity host -> resolve from the page instead

def extract_embedded_domain(careers_url: str) -> str | None:
    """Vanity hosts embed the real domain param in the careers page HTML."""
    html = requests.get(careers_url).text
    match = re.search(r'"domain"s*:s*"([A-Za-z0-9.-]+)"', html)
    return match.group(1).lower() if match else None

def resolve_domain(careers_url: str) -> str | None:
    return infer_domain(careers_url) or extract_embedded_domain(careers_url)

# Standard subdomain
print(resolve_domain("https://aexp.eightfold.ai/careers"))   # aexp.com
# Custom / vanity host (e.g. talent.bayer.com) -> read from page HTML
print(resolve_domain("https://talent.bayer.com/careers"))
Common issues
highTwo different API patterns exist (SmartApply vs PCSX)

Probe the SmartApply endpoint first (/api/apply/v2/jobs). If it 404s or returns an unexpected shape, fall back to the PCSX endpoint (/api/pcsx/search). Tenants run one or the other, so cache the detected type per company.

highListings API returns empty job descriptions

The listings endpoint omits descriptions. Make a separate call to the job details endpoint (/api/apply/v2/jobs/{id} or /api/pcsx/position_details) for each job to retrieve the full HTML description.

mediumWrong domain parameter causes 404 errors

The domain query param must match the tenant's configured domain, not the subdomain slug. Read it from the sitemap URL in robots.txt (it carries ?domain=...) or from the "domain" value embedded in the careers page HTML.

mediumCustom / vanity hosts require special handling

Some tenants run on vanity hosts (e.g. talent.bayer.com) instead of a *.eightfold.ai subdomain. Match against an allowlist of approved hosts and resolve the domain param from the page rather than guessing it from host segments.

mediumRate limiting on high-volume requests

Space requests out (200ms+ between calls) and cap concurrent detail fetches. Use the sitemap to collect all IDs first, then pull details at a controlled rate to avoid 403/429 responses.

lowSitemap job count differs from API totalJobs

The sitemap and API refresh on different schedules, so counts drift slightly (e.g. 529 vs 534). Use the sitemap for discovery and treat API totalJobs as a soft validation check.

Best practices
  1. 1Use the sitemap for complete job discovery without pagination
  2. 2Probe SmartApply first, fall back to PCSX, and cache the detected type per tenant
  3. 3Fetch job details separately since listings omit descriptions
  4. 4Add a 200ms+ delay between requests and cap concurrent detail fetches
  5. 5Resolve the domain param from robots.txt or the embedded page value, not the slug
  6. 6Handle both standard eightfold.ai subdomains and approved vanity hosts
Or skip the complexity

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

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

Access Eightfold
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