AppliTrack (Frontline Education) Jobs API.
Reach thousands of US K-12 school-district job boards through one interface — Jobo unwraps AppliTrack's JavaScript posting feed into clean jobs with titles, locations, and full descriptions.
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 AppliTrack (Frontline Education).
- Job Title
- Full Job Description
- School / District Location
- Position Type
- Date Posted
- Direct Apply URL
- 01K-12 Job Aggregation
- 02Education-Sector Hiring Trends
- 03District Vacancy Monitoring
- 04Public-Sector Talent Mapping
How to scrape AppliTrack (Frontline Education).
Step-by-step guide to extracting jobs from AppliTrack (Frontline Education)-powered career pages—endpoints, authentication, and working code.
import re
import requests
from urllib.parse import quote
HOST = "www.applitrack.com"
def tenant_from_url(url: str) -> str | None:
# Matches /{tenant}/onlineapp in the path (e.g. /mcminnville/onlineapp).
m = re.search(r"/([^/]+)/onlineapp(?:/|$)", url, re.IGNORECASE)
return m.group(1) if m else None
def listings_url(tenant: str) -> str:
return f"https://{HOST}/{tenant}/onlineapp/jobpostings/Output.asp?all=1"
tenant = tenant_from_url("https://www.applitrack.com/mcminnville/onlineapp/default.aspx?all=1")
print(listings_url(tenant))DOC_WRITE = re.compile(r"document\.write\('((?:\\.|[^'\\])*)'\)", re.DOTALL)
UNESCAPE = {"n": "\n", "r": "\r", "t": "\t", "'": "'", '"': '"', "\\": "\\", "/": "/"}
def js_unescape(s: str) -> str:
out, i = [], 0
while i < len(s):
if s[i] == "\\" and i + 1 < len(s):
out.append(UNESCAPE.get(s[i + 1], s[i + 1]))
i += 2
else:
out.append(s[i])
i += 1
return "".join(out)
resp = requests.get(listings_url(tenant), timeout=20)
resp.raise_for_status()
html = "".join(js_unescape(m.group(1)) for m in DOC_WRITE.finditer(resp.text))POSTING = re.compile(
r"<ul\s+class=['\"]postingsList['\"]\s+id=['\"]p(\d+)_['\"]\s*>",
re.IGNORECASE,
)
matches = list(POSTING.finditer(html))
blocks = []
for i, m in enumerate(matches):
job_id = m.group(1) # numeric AppliTrackJobId
if not job_id.isdigit():
continue
end = matches[i + 1].start() if i + 1 < len(matches) else len(html)
blocks.append((job_id, html[m.start():end]))
print(f"{len(blocks)} open postings")from bs4 import BeautifulSoup
APPLY_ARGS = re.compile(
r"applyFor\(\s*'([^']*)'\s*,\s*'([^']*)'\s*,\s*'([^']*)'\s*\)"
)
def parse_block(tenant: str, job_id: str, chunk: str) -> dict:
soup = BeautifulSoup(chunk, "html.parser")
title_cell = soup.select_one("table.title td#wrapword")
title = title_cell.get_text(" ", strip=True) if title_cell else None
fields = {}
for li in soup.select("li"):
label = li.select_one("span.label")
if not label:
continue
key = label.get_text(strip=True).rstrip(":").strip()
value = "/".join(
n.get_text(" ", strip=True)
for n in li.select("span.normal")
if n.get_text(strip=True)
)
fields[key] = value or None
first_choice = specialty = ""
apply_btn = soup.select_one("input.ApplyButton, input[onclick*='applyFor']")
if apply_btn:
args = APPLY_ARGS.search(apply_btn.get("onclick", ""))
if args:
first_choice, specialty = args.group(2), args.group(3)
return {
"external_id": job_id,
"title": title,
"position_type": fields.get("Position Type"),
"date_posted": fields.get("Date Posted"),
"location": fields.get("Location"),
"description": soup.get_text(" ", strip=True),
"listing_url": f"https://{HOST}/{tenant}/onlineapp/jobpostings/view.asp?AppliTrackJobId={job_id}",
"apply_url": (
f"https://{HOST}/{tenant}/onlineapp/_application.aspx"
f"?posJobCodes={job_id}"
f"&posFirstChoice={quote(first_choice)}"
f"&posSpecialty={quote(specialty)}"
),
}
for job_id, chunk in blocks:
job = parse_block(tenant, job_id, chunk)
print(job["external_id"], job["title"], job["location"])import time
def fetch_postings(tenant: str) -> str:
resp = requests.get(listings_url(tenant), timeout=20)
if resp.status_code == 404:
raise LookupError(f"Tenant '{tenant}' not found")
if resp.status_code == 401:
# Only the private /onlineapp/api/jobs endpoint requires auth;
# the public Output.asp feed should not return 401.
raise PermissionError(f"Tenant '{tenant}' requires authentication")
if resp.status_code in (403, 429):
raise RuntimeError(f"Tenant '{tenant}' throttled or blocked (HTTP {resp.status_code})")
resp.raise_for_status()
time.sleep(0.5) # single connection, ~500ms between requests
return resp.textThe feed ships as text/javascript document.write('...') calls — a plain HTML or JSON parse yields nothing. Extract each document.write payload, JS-unescape it, and concatenate the results before parsing markup.
That path is authenticated-only and was the sole JSON candidate observed in recon. Read postings from the public jobpostings/Output.asp?all=1 feed instead.
An HTML5 parser re-parents the per-posting <table>/<div> blocks. Slice the raw HTML on the <ul class="postingsList" id="p{jobId}_"> boundary and parse each posting chunk in isolation.
Keep only blocks whose id (the digits in p{jobId}_) are all-numeric; that value is the AppliTrackJobId used for the view.asp and apply URLs.
These come from optional labeled <li> blocks and are not always present. Treat them as nullable and fall back to the tenant/title fields rather than skipping the posting.
- 1Read jobs from jobpostings/Output.asp?all=1, never the private /onlineapp/api/jobs endpoint (returns 401).
- 2Unwrap the document.write('...') payloads and JS-unescape them before parsing any markup.
- 3Split the concatenated HTML on the <ul class="postingsList" id="p{jobId}_"> boundary so nested tables don't re-nest.
- 4Keep only postings whose id is all-numeric — that value is the AppliTrackJobId.
- 5Throttle to one connection with a ~500ms delay; AppliTrack serves thousands of small district tenants.
- 6Cache per tenant — district postings change slowly, usually daily at most.
One endpoint. All AppliTrack (Frontline Education) jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=applitrack (frontline education)" \
-H "X-Api-Key: YOUR_KEY" Access AppliTrack (Frontline Education)
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.