TalNet (Oleeo) Jobs API.
Pull government and enterprise vacancies from Oleeo-powered tal.net boards by reading each tenant's advertised Atom feed, then enriching from server-rendered detail pages.
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 TalNet (Oleeo).
- Native Vacancy IDs
- Full Job Descriptions
- Salary Ranges
- City, Region & Country
- Closing & Start Dates
- Department & Section
- 01Government Vacancy Tracking
- 02Enterprise Job Monitoring
- 03Public-Sector Recruitment Data
- 04Multi-Region Talent Feeds
How to scrape TalNet (Oleeo).
Step-by-step guide to extracting jobs from TalNet (Oleeo)-powered career pages—endpoints, authentication, and working code.
import requests
from bs4 import BeautifulSoup
import re
company = "fcdo"
base_url = f"https://{company}.tal.net/candidate"
response = requests.get(base_url, timeout=15)
soup = BeautifulSoup(response.text, "html.parser")
# Look for job board links
job_links = soup.find_all("a", href=re.compile(r"/jobboard/vacancy/\d+/adv"))
if job_links:
print(f"Found job board: {job_links[0]['href']}")import requests
from bs4 import BeautifulSoup
import xml.etree.ElementTree as ET
company = "royalvacancies"
base_url = f"https://{company}.tal.net"
# Preferred: read the advertised Atom feed link from the candidate page.
discovery = requests.get(f"{base_url}/vx/appcentre-ext/candidate/", timeout=15)
soup = BeautifulSoup(discovery.text, "html.parser")
link = soup.find("link", attrs={"type": "application/atom+xml"})
if link and link.get("href"):
feed_url = requests.compat.urljoin(base_url, link["href"])
else:
# Fallback: build the feed URL from the tenant's appcentre, brand and board
# IDs. The mobile-0 and brand- segments are required.
feed_url = f"{base_url}/vx/mobile-0/appcentre-1/brand-2/candidate/jobboard/vacancy/1/feed"
resp = requests.get(feed_url, timeout=10)
if resp.status_code == 200 and "<feed" in resp.text:
root = ET.fromstring(resp.content)
entries = root.findall(".//{http://www.w3.org/2005/Atom}entry")
print(f"Found {len(entries)} jobs in feed")import requests
from bs4 import BeautifulSoup
import re
job_board_url = "https://fcdo.tal.net/vx/appcentre-ext/candidate/jobboard/vacancy/1/adv/"
response = requests.get(job_board_url, timeout=15)
soup = BeautifulSoup(response.text, "html.parser")
# Extract job links using the /opp/ pattern
job_links = soup.find_all("a", href=re.compile(r"/opp/\d+"))
jobs = []
for link in job_links:
href = link.get("href", "")
# Extract job ID from URL pattern: /opp/{job_id}-{slug}
match = re.search(r"/opp/(\d+)", href)
if match:
jobs.append({
"id": match.group(1),
"title": link.get_text(strip=True),
"url": href if href.startswith("http") else f"https://fcdo.tal.net{href}"
})
print(f"Found {len(jobs)} jobs")import requests
from bs4 import BeautifulSoup
import re
import time
base_url = "https://fcdo.tal.net/vx/appcentre-ext/candidate/jobboard/vacancy/1/adv/"
all_jobs = []
offset = 0
while True:
url = f"{base_url}?start={offset}"
response = requests.get(url, timeout=15)
soup = BeautifulSoup(response.text, "html.parser")
job_links = soup.find_all("a", href=re.compile(r"/opp/\d+"))
if not job_links:
break
for link in job_links:
match = re.search(r"/opp/(\d+)", link.get("href", ""))
if match:
all_jobs.append({
"id": match.group(1),
"title": link.get_text(strip=True),
"url": link.get("href")
})
offset += 50
time.sleep(1) # Be respectful
if len(job_links) < 50: # Last page
break
print(f"Total jobs found: {len(all_jobs)}")import requests
from bs4 import BeautifulSoup
import re
def parse_job_details(job_url: str) -> dict:
response = requests.get(job_url, timeout=15)
soup = BeautifulSoup(response.text, "html.parser")
details = {"url": job_url}
# Extract title from h1
h1 = soup.find("h1")
if h1:
title_text = h1.get_text(strip=True)
details["title"] = title_text.replace("View Vacancy - ", "")
# Extract sections by looking for label patterns
page_text = soup.get_text()
patterns = {
"location": r"Location[^:]*:\s*([^\n]+)",
"city": r"Location \(City\)[^:]*:\s*([^\n]+)",
"salary": r"Salary[^:]*:\s*([^\n]+)",
"deadline": r"(?:Application deadline|Closing Date)[^:]*:\s*([^\n]+)",
"job_type": r"Type of Position[^:]*:\s*([^\n]+)",
"region": r"Region[^:]*:\s*([^\n]+)",
"grade": r"Grade[^:]*:\s*([^\n]+)",
}
for field, pattern in patterns.items():
match = re.search(pattern, page_text, re.IGNORECASE)
if match:
details[field] = match.group(1).strip()
# Extract full job description section
desc_match = re.search(
r"Job Description[^:]*:\s*(.+?)(?=Essential qualifications|Application deadline|$)",
page_text,
re.DOTALL | re.IGNORECASE
)
if desc_match:
details["description"] = desc_match.group(1).strip()[:2000]
return details
# Usage example with a real FCDO job URL
job = parse_job_details(
"https://fcdo.tal.net/vx/lang-en-GB/mobile-0/appcentre-1/brand-2/"
"candidate/so/pm/4/pl/1/opp/26301-example-job-title/en-GB"
)
print(job)import requests
from bs4 import BeautifulSoup
import re
import json
def extract_config(company: str) -> dict:
url = f"https://{company}.tal.net/candidate"
response = requests.get(url, timeout=15)
soup = BeautifulSoup(response.text, "html.parser")
config = {}
# Find embedded WCN configuration
scripts = soup.find_all("script")
for script in scripts:
if script.string and "WCN.global_config" in script.string:
# Extract baseUrl
base_match = re.search(r'baseUrl:\s*["']([^"']+)["']', script.string)
if base_match:
config["baseUrl"] = base_match.group(1)
# Extract rootPath
root_match = re.search(r'rootPath:\s*["']([^"']+)["']', script.string)
if root_match:
config["rootPath"] = root_match.group(1)
# Extract appcentre ID from rootPath
appcentre_match = re.search(r'appcentre-(\d+)', script.string)
if appcentre_match:
config["appcentreId"] = appcentre_match.group(1)
# Extract brand ID
brand_match = re.search(r'brand-(\d+)', script.string)
if brand_match:
config["brandId"] = brand_match.group(1)
break
return config
# Usage
config = extract_config("fcdo")
print(f"Appcentre ID: {config.get('appcentreId')}")
print(f"Brand ID: {config.get('brandId')}")
print(f"Base URL: {config.get('baseUrl')}")Send the Accept-Encoding: identity header. TalNet's origin mis-frames compressed chunked responses; requesting an unencoded body returns clean HTML and Atom XML.
Request the scoped board path https://{company}.tal.net/vx/appcentre-ext/candidate/ (or the tenant's Atom feed) directly instead of the bare subdomain, which some tenants (e.g. Evercore, Lazard) gate behind sign-in.
appcentre, brand, and board IDs differ between tenants, so a single hard-coded URL will not work everywhere. Resolve the feed scope per tenant by reading the application/atom+xml link, or extract the appcentre/brand/board IDs from the page before building the feed URL.
The observed /api/v1/cms/adv XHR endpoints are for tracking/analytics only and return empty data. Use the Atom feed for listings and the server-rendered detail pages for full vacancy data.
Fall back to extracting the appcentre/brand/board IDs from the page HTML and construct the feed URL: /vx/mobile-0/appcentre-{id}/brand-{id}/candidate/jobboard/vacancy/{board}/feed (the mobile-0 and brand- segments are required).
Field labels and markup differ across tenants and languages (English, Spanish, French, German, Portuguese, Italian, Dutch). Match on normalized, accent-stripped labels, read values from .form-control-static blocks, and fall back to case-insensitive text regex for older skins.
- 1Send Accept-Encoding: identity - TalNet returns malformed chunked responses when gzip/deflate is advertised
- 2Resolve each tenant's Atom feed first (link[type=application/atom+xml] or the mobile-0/appcentre/brand/vacancy/feed pattern) before falling back to HTML
- 3Extract job IDs with the /opp/(\d+) or /vacancy/(\d+) URL patterns
- 4Space requests out (~500ms) and cap detail concurrency (about 3) to stay under the radar
- 5Parse detail fields from .form-group label / .form-control-static value pairs, tolerating multilingual labels
- 6Validate against several tenants (UAL, Korn Ferry, Environment Agency, The Royal Household) to cover URL and skin variations
One endpoint. All TalNet (Oleeo) jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=talnet (oleeo)" \
-H "X-Api-Key: YOUR_KEY" Access TalNet (Oleeo)
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.