- lowThe datafeed returns the entire vacancy set in one response and rejects pagination.
- Fetch the feed once per tenant and treat the response as a complete snapshot — do not add page or offset parameters. The root Count attribute tells you how many vacancies to expect.
- mediumThe root Count exceeds the number of usable Item rows because Eploy emits rows with a non-numeric ID, missing link, or empty description.
- Reconcile Count against the rows you keep, drop any item failing the numeric-ID, absolute-link, and non-empty title/description checks, and log the delta rather than aborting the run.
- mediumMany employers front Eploy on a custom domain (e.g. careers.alzheimers.org.uk) rather than {tenant}.eploy.net, and unrelated hosts can share a /vacancies/ path.
- Resolve the datafeed against the exact host the careers site is served from, and confirm it is Eploy via the $Eploy runtime marker or the eploy.co.uk attribution link before trusting an arbitrary /vacancies/ URL.
- mediumNot every tenant exposes the anonymous feed; requests can return 404, 401, or 403/429.
- Branch on the status code: treat 404 as 'no public feed', 401 as auth-gated, and 403/429 as a signal to back off and retry with the recommended delay.
- lowSome tenants also serve /live-jobs.xml, but tenants such as Aldi and Travelodge reject or omit it.
- Use /feeds/datafeed.ashx as the production contract; do not depend on /live-jobs.xml being present.
Eploy Jobs API.
A single anonymous XML datafeed exposes every advertised vacancy from UK employers on this platform — full descriptions, salary bands, and locations returned in one request, with no keys or pagination.
What's in every response.
Data fields, real-world applications, and the companies already running on Eploy.
Data fields
- Full HTML Descriptions
- Displayed Salary Bands
- Location Details
- Industry & Vacancy Type
- Reference & Position Codes
- Posted & Created Dates
Use cases
- 01UK Job Market Tracking
- 02Salary Benchmarking
- 03Nonprofit Hiring Monitoring
- 04Recruitment Aggregation
Trusted by
- Alzheimer's Society
- British Heart Foundation
- Aldi
- Morrisons
- RAC
- Wren Kitchens
How to scrape Eploy.
Step-by-step guide to extracting jobs from Eploy-powered career pages—endpoints, authentication, and working code.
import requests
from urllib.parse import urlsplit
DATAFEED_PATH = "/feeds/datafeed.ashx?Format=xml&ExtApp=true&IntApp=false"
def feed_url(host_or_url: str) -> str:
# Accepts "alzheimerssocietyweb.eploy.net" or a full careers-site URL.
parts = urlsplit(host_or_url if "//" in host_or_url else f"https://{host_or_url}")
return f"https://{parts.netloc}{DATAFEED_PATH}"
print(feed_url("alzheimerssocietyweb.eploy.net"))
# https://alzheimerssocietyweb.eploy.net/feeds/datafeed.ashx?Format=xml&ExtApp=true&IntApp=falseresp = requests.get(
feed_url("alzheimerssocietyweb.eploy.net"),
headers={"Accept": "application/xml,text/xml"},
timeout=30,
)
if resp.status_code == 404:
raise SystemExit("No public Eploy datafeed for this tenant")
if resp.status_code == 401:
raise SystemExit("Datafeed requires authentication")
if resp.status_code in (403, 429):
raise SystemExit("Blocked or rate limited — back off and retry")
resp.raise_for_status()
xml = resp.textimport xml.etree.ElementTree as ET
root = ET.fromstring(xml)
if root.tag.rsplit("}", 1)[-1] != "Vacancies":
raise ValueError("Unexpected feed root — not an Eploy datafeed")
advertised_total = int(root.attrib.get("Count", "0"))
def text(item, name):
el = item.find(name)
return el.text.strip() if el is not None and el.text and el.text.strip() else None
jobs = []
for item in root.findall("Item"):
vacancy_id = text(item, "VacancyID")
link = text(item, "Link")
title = text(item, "Title")
if not (vacancy_id and vacancy_id.isdigit()
and link and link.startswith("http") and title):
continue # skip placeholder / malformed rows
jobs.append({
"external_id": vacancy_id,
"title": title,
"location": text(item, "Location"),
"salary": text(item, "DisplaySalary"),
"employment_type": text(item, "VacancyType"),
"industry": text(item, "Industry"),
"reference": text(item, "Reference"),
"posted_at": text(item, "DatePosted") or text(item, "DateCreated"),
"description_html": text(item, "Description"),
"link": link,
})
print(f"Advertised {advertised_total}, parsed {len(jobs)}")for job in jobs:
parts = urlsplit(job["link"])
origin = f"{parts.scheme}://{parts.netloc}"
job["listing_url"] = f"{origin}{parts.path}" # drop query + fragment
job["apply_url"] = f"{origin}/registration.aspx?vacancyID={job['external_id']}"
if advertised_total != len(jobs):
print(f"Count mismatch: feed advertised {advertised_total}, parsed {len(jobs)}")- 1Fetch /feeds/datafeed.ashx once per tenant — it returns every advertised vacancy in a single XML snapshot.
- 2Send an Accept: application/xml,text/xml header so the endpoint serves XML rather than an HTML fallback.
- 3Trust the root Count attribute as the authoritative total and reconcile it against the rows you parse.
- 4Drop rows whose VacancyID is non-numeric or whose link or description is empty — they are placeholders, not live jobs.
- 5Self-throttle to about two requests per second (~500ms apart) when sweeping many tenants.
- 6Confirm a custom domain is genuinely Eploy via the $Eploy runtime marker before scraping arbitrary /vacancies/ paths.
One endpoint. All Eploy jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=eploy" \
-H "X-Api-Key: YOUR_KEY"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.
Access Eploy
job data today.
One API call. Structured data. No scraping infrastructure to build or maintain — start with the $5 free starting balance and scale as you grow.