- highWhy does XML parsing fail with an encoding error?
- The feed's prolog declares encoding="utf-16" while the transport actually serves UTF-8. Strip the XML declaration from the already-decoded response body before parsing, rather than letting the stale declaration drive the decoder.
- highWhy does a district's feed return HTML instead of XML?
- A decommissioned district keeps answering on its host but redirects every /hire request to /hire/404.html. Check the final URL after redirects and report that as a dead board, so an HTML error page never reaches the XML parser as a parse failure.
- mediumShould an applicant-account page mint a district?
- No. Only /hire, /hire/index.aspx, /hire/JobList.ashx and /hire/ViewJob.aspx carry board identity. A stale ViewJob link redirects to pages such as /hire/ProfileErrorPage.aspx, and a naive parser would create districts from those error pages.
- lowWhy do TalentEd Hire URLs have a doubled slash?
- The vendor's own board emits links like https://{district}.tedk12.com//hire/ViewJob.aspx?JobID=3212. Accept the doubled form rather than repairing it at ingest — those are the real URLs the district publishes and links back to.
- lowIs an empty feed a scrape failure?
- No. An empty board returns a well-formed <jobs /> document, which is an authoritative empty snapshot. Both audited Canadian tenants and two US districts were in that state at audit time with fully healthy boards.
PowerSchool TalentEd Hire Jobs API.
Pull a school district's entire TalentEd Hire board in one request from the vendor's own JobList XML feed, which already carries full descriptions, structured locations, and closing dates.
What's in every response.
Data fields, real-world applications, and the companies already running on PowerSchool TalentEd Hire.
Data fields
- Complete Board In One Request
- Full Job Descriptions
- Structured Location Records
- Posted & Close Dates
- Job Codes & Categories
- Full-Time / Part-Time Flags
Use cases
- 01K-12 Education Job Boards
- 02School District Hiring Trackers
- 03Teacher Recruitment Research
- 04Regional Education Feeds
How to scrape PowerSchool TalentEd Hire.
Step-by-step guide to extracting jobs from PowerSchool TalentEd Hire-powered career pages—endpoints, authentication, and working code.
from urllib.parse import urlparse
VALID_TLDS = {"com", "ca"}
def parse_tedk12(url: str) -> dict:
parsed = urlparse(url)
labels = parsed.netloc.lower().split(".")
# Exactly {district}.tedk12.{com|ca} — nothing deeper, nothing shallower.
if len(labels) != 3 or labels[1] != "tedk12" or labels[2] not in VALID_TLDS:
raise ValueError("not a TalentEd Hire host")
if labels[0] in {"www", ""}:
raise ValueError("the marketing host carries no district")
# Boards emit a doubled slash in real links: //hire/ViewJob.aspx?JobID=3212
path = "/" + parsed.path.lstrip("/").lower()
if not path.startswith("/hire"):
raise ValueError("only /hire routes carry board identity")
return {"district": labels[0], "tld": labels[2]}
print(parse_tedk12("https://alleganymd.tedk12.com//hire/ViewJob.aspx?JobID=3212"))
# {'district': 'alleganymd', 'tld': 'com'}import requests
def feed_url(district: str, tld: str) -> str:
return f"https://{district}.tedk12.{tld}/hire/JobList.ashx"
def fetch_feed(district: str, tld: str) -> str:
resp = requests.get(feed_url(district, tld), timeout=30, allow_redirects=True)
# A decommissioned district still answers on its host but bounces /hire
# requests to /hire/404.html, which is HTML. Catch it before parsing XML.
if resp.url.rstrip("/").endswith("/404.html"):
raise LookupError(f"district '{district}' no longer exists")
resp.raise_for_status()
return resp.text
xml = fetch_feed("garfieldco", "com")
print(len(xml), "bytes")import re
import xml.etree.ElementTree as ET
DECLARATION = re.compile(r"^\s*<\?xml[^>]*\?>", re.IGNORECASE)
def parse_feed(xml: str) -> list[ET.Element]:
if not xml.strip():
raise ValueError("TalentEd Hire feed returned an empty body")
root = ET.fromstring(DECLARATION.sub("", xml, count=1))
if root.tag != "jobs":
raise ValueError(f"unexpected feed root element <{root.tag}>")
# An empty board returns a well-formed <jobs /> document. That is an
# authoritative empty snapshot, not a failure.
return list(root.findall("job"))
jobs = parse_feed(xml)
print(f"{len(jobs)} postings")def text(node, path: str) -> str | None:
found = node.find(path)
return found.text.strip() if found is not None and found.text else None
def to_job(node: ET.Element, district: str, tld: str) -> dict:
location = node.find("location")
return {
"title": text(node, "title"),
"job_code": text(node, "job-code"),
"category": text(node, "job-category"),
"description_html": text(node, "description/summary"),
"full_time": text(node, "full-time"),
"part_time": text(node, "part-time"),
"posted_at": text(node, "posted-date"),
"closes_at": text(node, "close-date"),
"company": text(node, "company"),
"contact": text(node, "contact"),
"location": {
"name": text(location, "name") if location is not None else None,
"city": text(location, "city") if location is not None else None,
"state": text(location, "state") if location is not None else None,
"postal_code": text(location, "zip") if location is not None else None,
"country": text(location, "country") if location is not None else None,
},
"listing_url": text(node, "detail-url"),
"district": district,
"tld": tld,
}
for node in jobs[:3]:
job = to_job(node, "garfieldco", "com")
print(job["title"], "|", job["category"], "|", job["closes_at"])def validate(node: ET.Element, district: str, tld: str) -> bool:
for field in ("job-board-url", "detail-url"):
url = text(node, field)
if not url:
continue
try:
claimed = parse_tedk12(url)
except ValueError:
return False
if claimed["district"] != district or claimed["tld"] != tld:
return False
return True
accepted, rejected = [], 0
for node in jobs:
if validate(node, "garfieldco", "com"):
accepted.append(to_job(node, "garfieldco", "com"))
else:
rejected += 1
if rejected:
print(f"snapshot incomplete: {rejected} rows named another district")
print(f"{len(accepted)} of {len(jobs)} rows accepted")- 1Use /hire/JobList.ashx instead of parsing the WebForms board at /hire/index.aspx
- 2Take the district from the leftmost host label and keep the TLD as a separate scope
- 3Strip the XML declaration before parsing; the utf-16 claim is wrong
- 4Check the post-redirect URL for /hire/404.html before treating a response as a feed
- 5Validate each row's own job-board-url and detail-url against the district you requested
- 6Skip per-job requests entirely — the feed already carries the full description
One endpoint. All PowerSchool TalentEd Hire jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=powerschool talented hire" \
-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 PowerSchool TalentEd Hire
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.