All platforms

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.

Get API access
Eploy
Live
<3haverage discovery time
1hrefresh interval
Companies using Eploy
Alzheimer's SocietyBritish Heart FoundationAldiMorrisonsRAC
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 Eploy.

Data fields
  • Full HTML Descriptions
  • Displayed Salary Bands
  • Location Details
  • Industry & Vacancy Type
  • Reference & Position Codes
  • Posted & Created Dates
Use cases
  1. 01UK Job Market Tracking
  2. 02Salary Benchmarking
  3. 03Nonprofit Hiring Monitoring
  4. 04Recruitment Aggregation
Trusted by
Alzheimer's SocietyBritish Heart FoundationAldiMorrisonsRACWren Kitchens
DIY GUIDE

How to scrape Eploy.

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

RESTbeginnerNo published limit; self-throttle to ~2 req/s (500ms between requests)No auth

Build the datafeed URL from a tenant host

Vendor boards live at {tenant}.eploy.net, while many employers front Eploy on a custom domain such as careers.alzheimers.org.uk. Both expose the same anonymous XML datafeed at a fixed path.

Step 1: Build the datafeed URL from a tenant host
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=false

Fetch the XML feed and branch on status

Request the feed with an XML Accept header. Not every tenant exposes a public feed, so handle 404 (no feed), 401 (auth required), and 403/429 (blocked or rate limited) explicitly.

Step 2: Fetch the XML feed and branch on status
resp = 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.text

Parse the Vacancies root and validate each item

The feed root is <Vacancies> with an authoritative Count attribute and one <Item> per vacancy. Keep only rows with a numeric VacancyID, an absolute Link, and a non-empty Title and Description — Eploy occasionally emits placeholder rows. Qualifications and Benefits are separate sibling elements you can append to the description.

Step 3: Parse the Vacancies root and validate each item
import 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)}")

Derive listing and apply URLs, then reconcile the count

The canonical listing URL is the link with its query string stripped; the apply URL is registration.aspx keyed by the vacancy ID on the same host. Treat Count as authoritative and log any gap between it and the rows you parsed instead of failing the run.

Step 4: Derive listing and apply URLs, then reconcile the count
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)}")
Common issues
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.

Best practices
  1. 1Fetch /feeds/datafeed.ashx once per tenant — it returns every advertised vacancy in a single XML snapshot.
  2. 2Send an Accept: application/xml,text/xml header so the endpoint serves XML rather than an HTML fallback.
  3. 3Trust the root Count attribute as the authoritative total and reconcile it against the rows you parse.
  4. 4Drop rows whose VacancyID is non-numeric or whose link or description is empty — they are placeholders, not live jobs.
  5. 5Self-throttle to about two requests per second (~500ms apart) when sweeping many tenants.
  6. 6Confirm a custom domain is genuinely Eploy via the $Eploy runtime marker before scraping arbitrary /vacancies/ paths.
Or skip the complexity

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

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

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.

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