- highLegacy API returns an empty array for both a wrong Referer and a genuinely empty board
- The legacy filter endpoint scopes solely by the Referer header. Always send Referer = https://{host}/, and treat [] as authoritative only after fetching the careers homepage and confirming the PostingPanda runtime fingerprint (JobSearch-angular.js or the clientpublicassets blob).
- highLegacy responses are XML, not JSON, on current deployments
- Sniff the first non-whitespace character: '[' means JSON, '<' means DataContract XML. Parse the XML by local tag name (ArrayOf... wrapper, ...ResultsQueryFilterLiveAdverts children) because the namespaces vary.
- criticalChoosing the wrong generation for a host
- Route strictly by host suffix. Only *.talosats-careers.com uses the two-step Talos360 config+search API; *.postingpanda.uk subdomains and verified white-label domains use the legacy Referer-scoped filter GET.
- mediumUK expiry dates misparse as month-first
- Legacy adverts carry the closing date as an 'Expiry_dd/MM/yyyy' token inside ExtraData. Parse it explicitly with the day-first format ('%d/%m/%Y') instead of relying on locale-default parsing.
- lowSending pagination parameters
- Both APIs return the tenant's complete snapshot in a single response and reject continuation cursors. Make one call per board and do not add page or offset params.
PostingPanda Jobs API.
Pull complete job snapshots — full descriptions, salary bands, and apply links — from UK careers sites running on PostingPanda and its newer Talos360 platform.
What's in every response.
Data fields, real-world applications, and the companies already running on PostingPanda.
Data fields
- Full Job Descriptions
- Salary Ranges & Currency
- Employment Type
- Location & Geo-Coordinates
- Apply URLs
- Posting & Closing Dates
Use cases
- 01UK employer job tracking
- 02Salary benchmarking
- 03Careers page aggregation
- 04Recruitment market research
Trusted by
- Cats Protection
- easyHotel
- Shoe Zone
- PizzaExpress
- Betfred
DIY GUIDE
How to scrape PostingPanda.
Step-by-step guide to extracting jobs from PostingPanda-powered career pages—endpoints, authentication, and working code.
Step 1: Route by host suffix
from urllib.parse import urlparse
def resolve_generation(careers_url: str) -> str:
host = (urlparse(careers_url).hostname or "").lower()
return "talos" if host.endswith(".talosats-careers.com") else "legacy"
# host is the tenant key across both DNS namespaces
host = (urlparse("https://thescoutassociation.postingpanda.uk/").hostname or "").lower()
generation = resolve_generation(f"https://{host}/")Step 2: Fetch the legacy generation with a Referer-scoped GET
import requests
LEGACY_FILTER_URL = (
"https://postingpandaapi-live.azurewebsites.net"
"/api/liveadverts/filter/-in-"
"?country=United+Kingdom&distanceMiles=10"
"&extraDataFilters=&location=&searchTerm="
)
def fetch_legacy(host: str) -> list[dict]:
resp = requests.get(
LEGACY_FILTER_URL,
headers={"Referer": f"https://{host}/"}, # the only tenant scope
timeout=15,
)
resp.raise_for_status()
body = resp.text.lstrip()
if body.startswith("["):
return resp.json() # older deployments emit JSON
return parse_legacy_xml(resp.text) # current deployments emit XMLStep 3: Parse the legacy DataContract XML
import xml.etree.ElementTree as ET
def _local(tag: str) -> str:
return tag.rsplit("}", 1)[-1]
def parse_legacy_xml(xml_text: str) -> list[dict]:
root = ET.fromstring(xml_text)
if not _local(root.tag).startswith("ArrayOf"):
return []
adverts = []
for item in root:
if "ResultsQueryFilterLiveAdverts" not in _local(item.tag):
continue
fields = {_local(c.tag): (c.text or "").strip() for c in item}
adverts.append(fields) # PascalCase keys, same as the JSON path
return advertsStep 4: Fetch the Talos generation via config then search
import requests
TALOS_BASE = "https://api-careers-sites.talos360.com"
def fetch_talos(host: str) -> list[dict]:
cfg = requests.get(
f"{TALOS_BASE}/api/careerssites/site/config/get",
params={"host": host},
timeout=15,
).json()
obfuscated_id = (cfg.get("siteConfig") or {}).get("obfuscatedId")
if not obfuscated_id:
raise ValueError(f"No obfuscatedId for {host}")
resp = requests.post(
f"{TALOS_BASE}/api/careerssite/vacancies/search",
headers={"Origin": f"https://{host}", "Referer": f"https://{host}/"},
json={
"careersSiteObfuscatedId": obfuscated_id,
"metadataFilters": [],
"preFilters": [],
"siteType": "External",
},
timeout=15,
)
resp.raise_for_status()
return resp.json().get("careersSiteVacancies") or []Step 5: Normalize records and confirm an empty legacy board
import requests
def normalize(host: str, generation: str, rec: dict) -> dict:
if generation == "talos":
job_id, title = rec.get("jobPostId"), rec.get("jobTitle")
description, salary = rec.get("jobDescription"), rec.get("salaryDisplay")
else:
job_id, title = rec.get("AdvertId"), rec.get("JobTitle")
description, salary = rec.get("JobDescription"), rec.get("SalaryDisplay")
return {
"external_id": job_id,
"title": title,
"description": description,
"salary": salary,
"listing_url": f"https://{host}/job/{job_id}",
}
def legacy_empty_is_authoritative(host: str) -> bool:
html = requests.get(
f"https://{host}/", headers={"Accept": "text/html"}, timeout=15
).text
default_runtime = "/Scripts/Views/Home/default/JobSearch-angular.js" in html
custom_runtime = (
"postingpanda.blob.core.windows.net/clientpublicassets/" in html
and "/Scripts/" in html
)
return default_runtime or custom_runtime Common issues
Best practices
- 1Route by host suffix before choosing an endpoint (Talos vs legacy).
- 2Always set Referer to https://{host}/ on the legacy filter call — it is the only tenant scope.
- 3Sniff XML vs JSON on the legacy response instead of assuming a format.
- 4Parse UK dates day-first with %d/%m/%Y.
- 5Space requests ~250ms apart and back off on HTTP 403 and 429.
- 6Treat an empty legacy array as real only after confirming the PostingPanda runtime fingerprint.
Or skip the complexity
One endpoint. All PostingPanda jobs. No scraping, no sessions, no maintenance.
Get API accesscURL
curl "https://connect.jobo.world/api/jobs?sources=postingpanda" \
-H "X-Api-Key: YOUR_KEY"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.
Ready to integrate Access PostingPanda
Access PostingPanda
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.
99.9%API uptime
<200msAvg response
50M+Jobs processed