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.
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 Eploy.
- Full HTML Descriptions
- Displayed Salary Bands
- Location Details
- Industry & Vacancy Type
- Reference & Position Codes
- Posted & Created Dates
- 01UK Job Market Tracking
- 02Salary Benchmarking
- 03Nonprofit Hiring Monitoring
- 04Recruitment Aggregation
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)}")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.
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.
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.
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.
Use /feeds/datafeed.ashx as the production contract; do not depend on /live-jobs.xml being present.
- 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" Access Eploy
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.