- highVacancy text comes back as escape sequences
- Connexys stores its rich-text fields inside a JavaScript preDecodedValue assignment, so a straight innerHTML read returns escaped markup. Extract the quoted value and run a unicode-escape decode before joining the four narrative sections.
- highTwo boards on the same org return different vacancies
- The mediaChannel parameter partitions a Connexys board, and the same org routinely publishes several channels with intentionally different sets. Include mediaChannel in the board key and in every detail URL, or one channel's snapshot will keep expiring another's jobs.
- mediumDetail links built from the href go nowhere
- Row anchors call a showPosition JavaScript function rather than pointing at a URL. Read the record ID and media channel out of that call and construct cxsrec__cxsSearchDetail yourself, resolved against the same directory as the search page.
- mediumAn empty board reads as a scrape failure
- Dutch tenants render 'geen vacatures' and English ones 'no vacancies' instead of an empty table. Check for those phrases when no dataRow rows are found and record an empty board, rather than raising a parse error and retrying forever.
Connexys Recruitment Jobs API.
Connexys is a Salesforce-based recruitment suite popular with Dutch and Belgian employers. Its public vacancy search renders on Salesforce Sites, with each vacancy keyed by a native Salesforce record ID.
What's in every response.
Data fields, real-world applications, and the companies already running on Connexys Recruitment.
Data fields
- Full Vacancy Narratives
- Native Salesforce Record IDs
- Media Channel Scoping
- Location Fields
- Direct Apply Targets
- Next-Page Traversal
Use cases
- 01Benelux Job Aggregation
- 02Salesforce Careers Page Ingestion
- 03Recruitment Marketing Analytics
- 04Careers Page Monitoring
Trusted by
- Ortec
- Acerta
- Dunea
- De Lijt
How to scrape Connexys Recruitment.
Step-by-step guide to extracting jobs from Connexys Recruitment-powered career pages—endpoints, authentication, and working code.
from urllib.parse import urlparse, parse_qs
SUFFIX = ".my.salesforce-sites.com"
PAGES = {"cxsrec__cxssearch", "cxsrec__cxssearchdetail"}
def parse_connexys(url: str) -> dict | None:
parsed = urlparse(url)
host = parsed.netloc.lower()
if parsed.scheme != "https" or not host.endswith(SUFFIX):
return None
path = parsed.path
slash = path.rfind("/")
page = path[slash + 1:] if slash >= 0 else path.lstrip("/")
if page.lower() not in PAGES:
return None
site_path = path[:slash] if slash > 0 else ""
query = {k.lower(): v[0] for k, v in parse_qs(parsed.query).items()}
media = query.get("mediachannel")
board = f"https://{host}{site_path}/cxsrec__cxsSearch"
if media:
board = f"{board}?mediaChannel={media}"
return {
"org": host.split(".")[0],
"site_path": site_path,
"media_channel": media,
"board_url": board,
"position_id": query.get("id"),
}
print(parse_connexys("https://dunea.my.salesforce-sites.com/dunearecruitment/apex/"
"cxsrec__cxsSearch?mediaChannel=a0o090000016dv9AAA"))import re
import requests
from bs4 import BeautifulSoup
from urllib.parse import urlparse, quote
SHOW_POSITION = re.compile(
"showPosition[(][ ]*'([A-Za-z0-9]{15,18})'[ ]*,[ ]*'([^']*)'", re.IGNORECASE)
ARTIFACTS = re.compile("cxsrec__cxsSearch|@connexys[.]com", re.IGNORECASE)
def parse_rows(html: str, page_url: str) -> list[dict]:
if not ARTIFACTS.search(html):
raise RuntimeError("page did not contain Connexys package artifacts")
parsed = urlparse(page_url)
directory = parsed.path[: parsed.path.rfind("/") + 1]
origin = f"{parsed.scheme}://{parsed.netloc}"
soup = BeautifulSoup(html, "html.parser")
rows = []
for row in soup.select("tr.dataRow"):
anchor = row.select_one("a[href*='showPosition']")
if not anchor:
continue
match = SHOW_POSITION.search(anchor.get("href") or "")
title = anchor.get_text(strip=True)
if not match or not title:
continue
position_id, media = match.group(1), match.group(2)
detail = f"{origin}{directory}cxsrec__cxsSearchDetail?id={quote(position_id)}"
if media:
detail = f"{detail}&mediaChannel={quote(media)}"
location = row.select_one(".cxsrec__Location__c")
rows.append({
"id": position_id,
"title": title,
"media_channel": media or None,
"location": location.get_text(strip=True) if location else None,
"url": detail,
})
return rows
session = requests.Session()
board = "https://ortec.my.salesforce-sites.com/cxsrec__cxsSearch?mediaChannel=a0o2p00000Zdm21AAB"
resp = session.get(board, timeout=30)
resp.raise_for_status()
rows = parse_rows(resp.text, board)from urllib.parse import urljoin
import time
NEXT_LABELS = {"volgende", "next"}
def next_page_url(html: str, page_url: str) -> str | None:
soup = BeautifulSoup(html, "html.parser")
for anchor in soup.select("a.pageLink[href]"):
if anchor.get_text(strip=True).lower() in NEXT_LABELS:
candidate = urljoin(page_url, anchor["href"])
if parse_connexys(candidate):
return candidate
return None
def scrape_board(session, board_url: str, max_pages: int = 20) -> list[dict]:
url, jobs, seen = board_url, [], set()
for _ in range(max_pages):
resp = session.get(url, timeout=30)
resp.raise_for_status()
for row in parse_rows(resp.text, url):
if row["id"] not in seen:
seen.add(row["id"])
jobs.append(row)
url = next_page_url(resp.text, url)
if not url:
break
time.sleep(0.3)
return jobsimport codecs
ENCODED_VALUE = re.compile(
"var[ ]+preDecodedValue[ ]*=[ ]*'((?:[^'])*)'[ ]*;", re.IGNORECASE | re.DOTALL)
NARRATIVE_FIELDS = [
".cxsrec__Company_information__c",
".cxsrec__Job_description__c",
".cxsrec__Job_requirements__c",
".cxsrec__Compensation_benefits__c",
]
def decode(value: str) -> str:
try:
return codecs.decode(value, "unicode_escape")
except (UnicodeDecodeError, ValueError):
return value
def fetch_detail(session, listing: dict) -> dict | None:
resp = session.get(listing["url"], timeout=30)
if resp.status_code in (404, 410):
return None
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
sections = []
for selector in NARRATIVE_FIELDS:
element = soup.select_one(selector)
if element is None:
continue
raw = element.decode_contents()
match = ENCODED_VALUE.search(raw)
text = decode(match.group(1)) if match else raw
if len(BeautifulSoup(text, "html.parser").get_text(strip=True)) >= 30:
sections.append(text.strip())
heading = soup.select_one("h1.cxsPageTitle")
apply_anchor = soup.select_one("a[href*='cxsSearchApply']")
location = soup.select_one(".cxsrec__Location__c p, .cxsrec__Location__c")
return {
"id": listing["id"],
"title": heading.get_text(strip=True) if heading else listing["title"],
"description_html": "".join(sections),
"location": location.get_text(strip=True) if location else listing.get("location"),
"url": listing["url"],
"apply_url": (urljoin(listing["url"], apply_anchor["href"])
if apply_anchor else listing["url"]),
}- 1Treat the org plus mediaChannel pair as the board identity
- 2Read the record ID from the showPosition call rather than the anchor href
- 3Resolve detail URLs against the search page's own directory, which varies per org
- 4Unescape preDecodedValue before joining the four narrative fields
- 5Recognise the Dutch and English empty-board phrases before flagging a failure
- 6Throttle to ~300ms between requests with at most two concurrent detail fetches
One endpoint. All Connexys Recruitment jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=connexys recruitment" \
-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 Connexys Recruitment
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.