# Job posting schema: 5 of 374 careers pages declared it, and 86 linked the jobs to an applicant tracking system

> Lantad asked all 1,419 hostnames in this repository's two committed corpus seed files for robots.txt on 10 October 2026, read every permitted home page with no JavaScript executed, then followed each page's own link to a careers or jobs page and read that too. 380 of the 1,079 readable home pages linked one, and 374 answered HTTP 200 with HTML on two passes the same day. 5 of the 374 declared a JobPosting node, carrying 52 postings between them.

- Canonical page: https://lantad.co/blog/job-posting-schema-5-of-374-careers-pages
- This file: https://lantad.co/blog/job-posting-schema-5-of-374-careers-pages.md
- Last substantive update: 2026-10-10

## Key facts

- **Published:** 2026-10-10
- **Category:** Findings
- **Author:** Lantad
- **Length:** 3314 words
- **Takeaway 1:** Job posting schema is the markup that states an open role as data rather than as styled text, and of the 374 corpus careers pages Lantad read twice on 10 October 2026, 5 declared a JobPosting node while 191 carried JSON-LD of some other kind.
- **Takeaway 2:** 86 of the 374 careers pages linked an applicant tracking system on another host, and not one of those 86 declared a JobPosting of its own, so the roles exist as data somewhere the site does not control.
- **Takeaway 3:** The 5 sites that did declare it carried 52 postings between them, and all 52 named a title, a description and a hiring organisation, which is the pattern in every schema measurement on this site: the absence is the finding, not the misuse.
- **Takeaway 4:** 50 of the 52 postings named no validThrough date, and Google's job posting documentation, last updated 8 September 2026, asks for that property only where a posting has an expiry, so the omission is permitted and still leaves nothing in the markup saying whether a role 647 days old is open.
- **Takeaway 5:** 3 of the 374 careers pages served a plainly identified crawler no readable words at all on either pass, and 4 more served only navigation, among them a page that answered with the words Request Rejected.

## Summary

Ask an assistant who is hiring and it has to get the roles from somewhere. The careers page is the obvious place, and on most sites it is there, linked from the main navigation under a word a person recognises. Whether the roles on it exist in a form a machine can lift without guessing is a separate question, and job posting schema is the part of [structured data](https://lantad.co/glossary/structured-data) that answers it: a JobPosting node with a title, a description and a date on it, rather than a list of role names set in a styled column.

So this run counted both halves on the same pages. Every hostname in the corpus committed to this repository was asked for its robots.txt, every permitted home page was read with no JavaScript executed, and where that home page linked to its own careers or jobs page, that page was read as well. 380 sites linked one. On the 374 that returned a readable page twice, 5 declared a JobPosting node. The other 369 describe their openings to a reader and describe nothing to [a crawler](https://lantad.co/glossary/ai-crawler), and the largest single group among them has a reason: the jobs are on somebody else's host.

## An engine asked who is hiring has to find the jobs first

A careers page is not a page a scanner can assume exists. The corpus spreads across government, education, healthcare, news and small business, and plenty of those sites have never published a vacancy. So the population here is defined by the sites themselves, using the same rule [a run on pricing pages](https://lantad.co/blog/pricing-page-schema-120-of-195-declared-none) used last week: a page counted only if the home page linked to it on the host the home page itself resolved to, and the link either sat on a path naming careers, jobs, vacancies, openings, employment, recruitment, hiring or joining, or carried anchor text that was one of those phrases and nothing else. The rule is deliberately narrow. It finds the page a visitor would reach by clicking the word Careers, which is the page an engine following the same link would reach too.

380 of the 1,079 readable home pages offered such a link, 341 of them matched on the path and 33 on the anchor text alone. Exactly one of the 380 candidate paths was disallowed to Lantad by the site's own robots.txt, on canonical.com, which is worth stating because it is the opposite of what people assume: nobody hides their vacancies from crawlers on purpose. 6 of the remaining pages did not answer. fullsteamcrossfit.com and destatis.de returned 404 for a link their own home page still prints, petermac.org and themarshallproject.org returned 403 to [a plainly identified crawler](https://lantad.co/bot), time.com returned 406, and americanexpress.com failed to connect at all.

Every figure below is counted over the 374 pages that answered 200 with HTML on a first read and again on a second read the same day. The two passes agreed on the node counts, the block counts and the word counts on every one of the 374. A separate pair of fresh reads taken later the same day agreed with each other on 371 of 374; the two that moved, iisc.ac.in and u.ae, differ by a handful of words of rotating notice text, and one did not answer the third time. The [method notes](https://lantad.co/methodology) describe how the extractor decides what counts as text.

## 5 of 374 careers pages declared a JobPosting node

The test was a single one and it used the shipped code rather than a parser written for the occasion: walk every JSON-LD script body the extractor finds, flatten each document the way the scanner's schema sub-score flattens it, and ask whether any node carries a type of JobPosting. 5 pages did. fieldguide.com declared 46 postings, predoc.ai 3, and promptless.ai, thewireguyelectric.com and usehatchapp.com one each, for 52 nodes in total.

The 369 that did not are not one group. 183 of them carried no JSON-LD anywhere on the careers page, which is the same absence this blog found when [141 of 382 home pages carried none in the raw HTML](https://lantad.co/blog/structured-data-141-of-382-home-pages-carried-none). The other 186 carried JSON-LD and used it for something else, and that is the more interesting half, because the machinery was already on the page. Across the 374 pages the walk reached 1,869 nodes. 116 pages declared an Organization, 90 a WebSite, 85 a BreadcrumbList, 82 a WebPage, 78 an ImageObject and 49 a SearchAction. 12 declared an FAQPage on a careers page. 5 declared the type the page is about.

112 pages declared an Organization and no JobPosting, which is a careers page telling a machine who the employer is and nothing about what the employer is hiring for. That shape recurs: it is the same disconnect as the [158 sites that declared both an Organization and a WebSite node and connected neither](https://lantad.co/blog/website-schema-markup-158-of-338-connected-neither), and it is what makes these counts persistent rather than transitional. Nothing reports them. A page with a valid BreadcrumbList and no JobPosting passes every validator there is, because there is no rule anywhere that a careers page must describe a job. The absence is invisible from inside the site, and total from outside it.

## What does job posting schema have to carry?

The vocabulary is old and settled. [The schema.org JobPosting type](https://schema.org/JobPosting) is defined as "A listing that describes a job opening in a certain organization", and it carries datePosted, described there as the publication date of an online listing, and baseSalary, the base salary of the job. [Google's job posting documentation](https://developers.google.com/search/docs/appearance/structured-data/job-posting), carrying Last updated 2026-09-08 UTC, names five required properties: datePosted, description, hiringOrganization, jobLocation and title. It marks seven more as recommended, among them employmentType, baseSalary, directApply, identifier, jobLocationType, applicantLocationRequirements and validThrough.

Measured against that, the 52 nodes are in better shape than the headline suggests, and it would be dishonest to report the count without saying so. All 52 named a title, a description and a hiringOrganization. 51 named a datePosted; the single omission is usehatchapp.com, whose one node is titled Multiple Positions Available and reads more like a standing notice than a posting. 49 named an employmentType and 50 named directApply. On location the split looks worse than it is: only 6 nodes carried jobLocation, while 46 carried jobLocationType together with applicantLocationRequirements, which is the documented pattern for fully remote roles. The same Google page states that the jobLocation property is not required if applicantLocationRequirements is present, so those 46 are not missing a required property. They are remote jobs described correctly.

Two properties are genuinely rare. identifier appeared on none of the 52. baseSalary appeared on one. A reader can draw their own conclusion about pay transparency from a single salary across 52 postings, but the narrower point is about extraction: an assistant asked what a role pays has nothing in the data to read on 51 of them, whatever the page says in prose. This is the pattern every schema run on this site reports, most recently when [20 of 616 home pages declared a Service node](https://lantad.co/blog/service-schema-markup-20-of-616-declared-it): the sites that reach for a type at all tend to use it properly, and the finding is almost never a misuse.

## 86 careers pages linked the jobs to an applicant tracking system

The largest explanation for 369 pages with no JobPosting is not neglect. It is that the careers page is a landing page and the jobs live in a hosted applicant tracking system on another hostname. Counting links against a named list of 39 such systems, 86 of the 374 pages linked at least one. Workday accounted for 21, Ashby 15, Greenhouse 9, ADP 7, Workable 4, Oracle Cloud 4, Lever 3 and SmartRecruiters 3, with the rest spread one or two apiece. Not one of those 86 pages declared a JobPosting of its own.

A further 187 pages linked only a job aggregator or a social profile, overwhelmingly a LinkedIn company page, and no tracking system at all. Those links are worth separating rather than counting as a handoff, because a LinkedIn link in a footer is a social profile and not a job board, and treating the two as the same thing would inflate the finding. 101 pages linked none of the listed hosts.

14 pages went further and embedded the tracking system in the page itself through a script or an iframe: Ashby on 6, Greenhouse on 3, and BambooHR, Workable, Personio, ADP and Workday on one each. 13 of the 14 declared no JobPosting. That is the worst of the arrangements for a machine, because the page looks complete to a person and arrives at a crawler as chrome with a hole in it: eleos.health served 66 words of main content, hsco.org 52, and chalkbeat.org 3. A role that only exists once a script runs does not exist for a client that does not run scripts, which is the oldest finding on this site and the one [11 of 271 pages that returned no words without JavaScript](https://lantad.co/blog/client-side-rendering-11-of-271-returned-no-words) measured directly. Lantad measures that gap as [prose parity](https://lantad.co/glossary/prose-parity), and the [crawler view of any page](https://lantad.co/tools/what-gptbot-sees) shows which side of it a vacancy falls on.

11 careers URLs did not stay on the site at all: the on-host link answered with a redirect to a different host, among them job-boards.greenhouse.io twice, careers.airbnb.com, careers.db.com, jobs.navyfederal.org and practo.app.param.ai. None of this is a defect in a tracking system, and an employer that buys one has bought a working apply flow. What it means is narrower and worth knowing: the roles are structured data on a host the employer does not own, the page the employer does own says nothing, and an engine that reads the employer's site learns that the employer has a careers page.

## 50 of the 52 postings named no closing date

Of the 52 nodes, 2 carried a validThrough: promptless.ai to 31 December 2026 and thewireguyelectric.com to 18 November 2026. Neither had passed on the measurement date, so nothing here repeats the finding that [171 of 429 declared event start dates had passed](https://lantad.co/blog/event-schema-markup-19-of-1419-sites-declared-it). The other 50 named no closing date, and the honest reading of that is not a defect. Google's documentation is explicit in the other direction: validThrough is required for job postings that have an expiration date, and if a posting never expires, or you do not know when the job will expire, do not include this property. 50 omissions are consistent with 50 roles that have no planned closing date.

What the omission does mean is that the only date in the data is the one the posting opened on. Across the 51 parseable datePosted values the median age was 52 days, which is a healthy number for a live hiring page. The tail is where it gets harder to read. 9 of the 51 were more than 180 days old and one was more than a year: promptless.ai's Founding Engineer, posted 1 January 2025 and therefore 647 days old on the measurement date, with a validThrough of 31 December 2026 still ahead of it. fieldguide.com's Strategic Account Executive dated to 12 December 2025 and predoc.ai's Senior Sales Development Representative to 23 December 2025. At the other end, fieldguide.com's postings included three stamped within the previous 24 hours, so the same site is maintaining the recent end of its list carefully.

None of this says any of those roles is closed, and this run did not test whether they are. It says that the markup cannot answer the question. The same Google page sets out what it expects instead, and the wording is unusually direct for a documentation page: jobs that are no longer open for applications must be expired, by setting validThrough to a past date, by removing the page so it returns 404 or 410, or by removing the JobPosting markup. It adds that failure to take timely action on expired jobs may result in a manual action, and that expired job postings are not allowed. A posting with no closing date and a 647 day old start date is not caught by any of those three mechanisms, because none of them has anything to fire on. It is the same class of quiet defect as [38 of 195 pages that declared a dateModified a reader never saw](https://lantad.co/blog/datemodified-schema-38-of-195-never-showed-the-date) and as [70 of 146 markup values that were not on the page](https://lantad.co/blog/structured-data-for-ai-70-of-146-values-off-page): structurally valid, and describing a state of the world that may have moved.

## What this run did not measure

The population is sites that link a careers page from their home page using a word this run recognised. A site whose vacancies sit behind a link reading Join the mission, or inside a navigation menu built by a script, was not counted, so 380 is a floor on how many corpus sites are hiring rather than a census. The corpus itself is a sampling frame of 1,419 hostnames chosen to spread across industries and site builders, not a random sample of the web, so the proportions here describe this corpus on this date. The seed files are committed to the repository, which is what makes the figures checkable rather than authoritative.

Only the careers index was read, never the individual job pages behind it. That matters more for this subject than it did for pricing, because JobPosting markup conventionally lives on the detail page for one role, not on the list. A site with no JobPosting on its index may well declare one correctly on every vacancy underneath, and this run would not see it. What the run does establish is narrower and still worth stating: on 369 of 374 pages, the page an engine reaches by following the site's own careers link describes no job. The 7 pages that returned no main content at all could not have carried a readable posting either way, and one of those, unesco.org, answered with 17 words reading Request Rejected, which is a bot challenge of the kind [79 of 115 sites that refused a crawler their robots.txt allows](https://lantad.co/blog/is-my-site-blocking-ai-crawlers-79-of-115) turned up rather than a careers page at all.

Nothing here was rendered in a browser, which is the point of reading a page as a crawler reads it and also a limit: a posting injected by a tracking system's script is absent from these counts by design, and a run that compared both views would find more visible roles and exactly the same amount of markup. Nor does any of this measure whether JobPosting markup changes what an assistant says about who is hiring. That needs a controlled test against the engines, a different instrument from the one used here, and this blog has already published that [structured data came third in a citation audit](https://lantad.co/blog/structured-data-came-third-in-a-citation-audit) rather than first. The [glossary entry on generative engine optimization](https://lantad.co/glossary/geo) sets out where this sits among the rest, [what AI says about a site](https://lantad.co/what-ai-says) is where the downstream question gets asked, and [the guide to being cited by ChatGPT](https://lantad.co/how-to-get-cited/chatgpt) covers the retrieval side of it.

## Questions and answers

**What is job posting schema?**

It is structured data on a careers or job page that states an opening as data rather than as styled text, normally a JobPosting node carrying a title, a description, a hiring organisation and a posting date. schema.org defines the JobPosting type as a listing that describes a job opening in a certain organization, and Google's job posting documentation, last updated 8 September 2026, names datePosted, description, hiringOrganization, jobLocation and title as required properties.

**How many sites declare job posting schema on their careers page?**

Of the 374 corpus careers pages Lantad read twice on 10 October 2026, 5 declared a JobPosting node, carrying 52 postings between them. 183 of the 374 carried no JSON-LD at all and 186 carried JSON-LD of some other kind, most commonly an Organization node, which 116 pages declared.

**Does it matter that a job posting names no closing date?**

Not on its own. Google's documentation asks for validThrough only where a posting has an expiration date and says not to include it where a job never expires, so the 50 of 52 nodes that omitted it are within the guidance. It matters in combination with an old start date: the median declared datePosted was 52 days old, 9 of 51 were over 180 days old and one was 647 days old, and with no closing date the markup gives an engine nothing to decide whether the role is still open.

**Is it a problem to run hiring through an applicant tracking system?**

Not for the apply flow, which is what an employer buys one for. It is a problem for extraction from the employer's own site. 86 of the 374 careers pages linked a hosted tracking system on another host and none of those 86 declared a JobPosting of their own, and 14 pages embedded one through a script or an iframe, 13 of which declared none. On those pages a crawler that does not run JavaScript reads the surrounding copy and no roles.

---

Lantad measures whether AI crawlers can actually read a page: it fetches as a non-rendering
crawler, renders as a browser, and reports the gap. Free scan, one URL, no signup.

Method and weights: https://lantad.co/methodology | All pages as markdown: https://lantad.co/md | Crawler policy: https://lantad.co/bot
