AppliTrack Jobs API
Query jobs from AppliTrack boards through one API with ?source=applitrack — plus how AppliTrack's Output.asp endpoint really behaves.
AppliTrack is Frontline Education's applicant tracking product, and it is what US
K-12 school districts hire through: teachers, aides, bus drivers, custodians,
coaches, cafeteria staff. Each district's board lives under
applitrack.com/<district>/onlineapp, where <district> is a short path segment
like wvde or jcps. It's 41,110 postings in this dataset — a number that moves
hourly, so read it from /v1/meta rather than
trusting this sentence.
It is also, by some distance, the cheapest source in this pool to fetch. One unauthenticated GET returns an entire district's board with the descriptions already in it. No pagination, no detail pass, no token.
Try it#
curl -G https://api.hyperjobs.io/v1/jobs \
-H "Authorization: Bearer $HYPERJOBS_KEY" \
--data-urlencode "source=applitrack" \
--data-urlencode "time_frame=7d" \
--data-urlencode "limit=2"{
"data": [
{
"id": "applitrack:wvde_48210",
"title": "Special Education Teacher",
"company": {
"slug": "wvde",
"name": "West Virginia Department of Education",
"domain": "wvde.us",
"website": "https://wvde.us",
"logo": "https://logo.hyperjobs.io/wvde.us",
"industry": "Education Administration Programs",
"employee_count": null
},
"location": {
"remote": false,
"countries": ["United States"],
"cities": null,
"regions": null,
"raw": ["United States"]
},
"employment_type": ["FULL_TIME"],
"work_arrangement": null,
"department": "Elementary School Teaching",
"team": "Nimitz High School",
"seniority": null,
"salary": {
"min": null,
"max": null,
"currency": null,
"unit": null,
"annual_min": null,
"annual_max": null,
"summary": null
},
"skills": ["special education", "iep"],
"taxonomies": ["Education"],
"apply_url": "https://www.applitrack.com/wvde/onlineapp/jobpostings/Output.asp?AppliTrackJobId=48210&AppliTrackLayoutMode=detail&AppliTrackViewPosting=1",
"url": "https://www.applitrack.com/wvde/onlineapp/jobpostings/Output.asp?AppliTrackJobId=48210&AppliTrackLayoutMode=detail&AppliTrackViewPosting=1",
"source": "applitrack",
"posted_at": "2026-07-09T00:00:00.000Z",
"expires_at": "2026-08-15T00:00:00.000Z",
"updated_at": "2026-07-14T06:41:12.000Z"
}
],
"total": 2,
"limit": 2,
"offset": 0
}Two things in there are unusual and both come straight from the platform.
expires_at is populated: districts post a hard application deadline, and most
ATS expose none at all. And cities is null while countries is ["United States"] — that is not a gap in our geocoding, it's the honest answer. See
below.
How AppliTrack boards actually work#
One GET, no auth, no pagination, the whole board including every description:
curl "https://www.applitrack.com/<district>/onlineapp/jobpostings/Output.asp?all=1"<district> is the path segment from the board URL. That's the entire fetch
surface. ?all=1 is the whole thing — there is no page two, and there is no
detail request, because the bodies are already inline.
The best jobs-per-request source we have
Measured 2026-07: wvde returned 1,078 jobs in 9.4MB from one call;
jcps returned 32 from one call. Every other platform on this site costs you
either pagination, an N+1 detail pass, or both. AppliTrack costs you one
request per district and nothing else.
Budget for the size, though: a big district's response is ~9MB, so a 20-second timeout is not enough. Ours allows 90.
The response is not HTML#
This is the one real wrinkle, and it stops a naive parser dead. Output.asp
does not return a document. It returns a JavaScript program whose markup is
embedded inside document.write('…') string literals, which means every quote
in the payload arrives backslash-escaped:
document.write('<ul class=\'postingsList\' id=\'p48210_\'>…');The board's own view.asp bootstraps this by letting the browser execute it. You
don't need to. Unescape \' and \" and you have ordinary markup you can parse
with ordinary tools — no JS engine, no headless browser. But feed the raw
response to an HTML parser and you get a document containing one <script> and
zero jobs, at HTTP 200.
Fields are label/value span pairs#
Inside each <ul class='postingsList' id='p<JOBID>_'> block, the structured
fields are pairs:
<span class="label">Location:</span><br/> <span class="normal">Nimitz High School</span>| Label | What it becomes |
|---|---|
Position Type | The district's own category — Elementary School Teaching, Substitute, District |
Date Posted | The posting date, US M/D/YYYY, no zone |
Closing Date | A real application deadline |
Location | The school or building, not a place |
Additional Information | The description body |
Read those by matching label → normal span pairs. The tempting shortcut is a
flat text scan that grabs everything after a label until the next known label,
and it bleeds badly — see below.
What bites you#
77% of postings have a description, and that is the ceiling#
This is the number to know about AppliTrack, and it is not a parse bug we haven't got round to fixing. It's the platform.
Plenty of districts don't type the posting into the form. They attach it as a
PDF and leave the body reading "Please click the link to view the job description. Attachment(s): Licensed Journeyman Plumber.pdf". At aldineisd,
199 of 290 postings are that. The per-job detail page carries no body either
— we checked, it's PDF-only there too. There is nothing to parse, so a null
description is the correct answer rather than a missing one.
A single district will flatter you
Sampled on two small districts, AppliTrack looks like 100% descriptions —
jcps is 32 of 32. Across a real 24-board rotation it is 919 jobs, 712 with
a body: 77%.
If you build this and measure it on the first district you find, you will believe you have a perfect source, ship it, and discover the gap in production. Measure on a rotation, not on a board.
Recovering the rest means running a PDF text extractor over the attachments. That's a real option and not a wall, but it is a different project from scraping a board, and until someone does it, 77% is what AppliTrack is.
"Location" is a building, never a place#
The field is labelled Location: and it never contains one. Across three
districts, all 76 distinct values were school and facility names — Nimitz High, Special Education Building, District-Wide, Various. 0 of 115
postings geocoded. Feeding those to a geocoder produced 3% country fill and 8,979
unresolved rows.
So the school goes in team, where it's true, and the location field gets the
one geographic fact the platform actually supports: the country. AppliTrack is
Frontline's US K-12 product. Of 1,486 discovered districts, none carries a
non-US signal and 352 use US-exclusive terms (ISD = Independent School
District, USD = Unified School District). The country is asserted. The city is
not invented — the posting names no city, state or country anywhere, and the
board page carries no district address either.
Districts invent their own labels#
There is no fixed label vocabulary. Districts add their own: Date Available:,
SALARY/STIPEND:, Attachment(s):, STARTING DATE:. A parser that scans flat
text and stops at the next label it knows about will sail straight past an
unlisted one and swallow it, producing locations like:
"District Date Available: 09/01/2026"Reading label → normal span pairs needs no list of labels at all, which is
why that's what we do. Quoting and padding vary per district too ('label' vs
"label", " Closing Date: "), so the match has to tolerate both.
JobIDs are district-local#
JobID: 5 exists on more than one board. Key a posting by the bare id and
districts silently overwrite each other's jobs. The key has to carry the
district: wvde_48210.
Dates are bare US calendar dates#
Date Posted and Closing Date are M/D/YYYY with no zone, in district-local
time. Date.parse reads them at your local midnight, which shifts them a day
in either direction depending on where your server is. They need a bare-date
parser, not a timestamp parser.
Or use ours#
The fetch is genuinely one line. It's everything around it that isn't: the
document.write unescape, the label-pair reader that survives a district
inventing SALARY/STIPEND:, the school going to team instead of poisoning the
geocoder, the district-scoped key, and the bare-date handling.
You get all of that, plus dedup against the same
role on LinkedIn, and expires_at fed from the district's real
closing date rather than waiting for a board diff to notice the job is gone.
Where we're honest about the join
The company join is partial on AppliTrack — around 21% of postings resolve to an employer domain, well below what we manage on platforms that publish one. A district posting names no website and no domain, and "Jefferson County" is a name that belongs to a dozen places. We resolve what we can and leave the rest rather than guess a school district's identity.
And the two numbers that matter: 77% descriptions, and 100% country, 0% city. Both are the platform's ceilings, not ours, and we would rather you knew them before you query than after.
Query it with ?source=applitrack.
Quickstart, then pricing.
Every source above normalises onto the same schema, with the hiring company already joined onto each posting.
Get an API key