Guide · The cheapest, most stable, most auditable surface to script against

ATS automation should drive the same surface a screen reader sees.

Every enterprise ATS publishes a WCAG 2.1 AA accessibility tree because procurement requires it. The tree is dense, semantically labeled, and stable across UI redesigns, because changing it would break the conformance claim and break every screen reader that depends on it. That makes it the single best surface for an AI agent to script against. Cheaper than pixel-level computer use, far more stable than CSS selectors, and the only architecture that produces an audit row a 2026 hiring-law regulator will accept. This guide walks the integration end to end.

M
Matthew Diakonov
13 min read

Direct answer · Verified 2026-05-01

ATS accessibility tree integrationmeans driving the ATS user interface through the browser's accessibility tree, the structured ARIA-labeled snapshot already published for screen readers, using role and accessible-name selectors instead of CSS or pixel coordinates. Every major recruiter ATS (Greenhouse, Lever, Workday Recruiting, Ashby) ships WCAG 2.1 or 2.2 AA conformance because federal procurement requires it, which makes the tree dense enough to drive deterministically.

Verified against Playwright ARIA snapshots, WCAG 2.1, and the published Workday accessibility statement declaring WCAG 2.2 AA, Section 508, and EN 301 549 conformance.

01Why the tree is a procurement contract

Roles and accessible names are a published API in everything but name

The reason ATS UIs ship rich accessibility trees has nothing to do with developer kindness. It is procurement. Every ATS vendor selling into a federal agency, a state government, a public university, or most Fortune 1000 enterprises must publish a Voluntary Product Accessibility Template declaring conformance to WCAG 2.1 AA, Section 508, and (for European buyers) EN 301 549. Workday publishes that statement openly. Greenhouse, Lever, and Ashby publish VPATs on request. The conformance is not a marketing checkbox; it is a precondition of the contract.

The conformance claim is built on roles and accessible names. A button must have role="button" and an accessible name that conveys its purpose. A combobox must announce its options. A form input must be labeled. Once those are shipped and audited, changing them silently breaks every screen reader, breaks the VPAT, and risks the next renewal. The ARIA roles and names become a contract the vendor cannot quietly rotate, the way they can quietly rotate a CSS class name from .stage-button-rev3 to .stage-button-rev4 in any release.

That contract is what an agent gets to script against. locator.getByRole("button", { name: "Move stage" }) is not querying an internal implementation detail; it is querying a documented surface the vendor has agreed not to break. CSS selectors targeting class names or data-test attributes have no such guarantee. They were never published as an API; they survive only as long as no one in engineering decides to refactor.

What the agent actually sees

A real Ashby candidate page collapsed to its accessibility tree. Compare this to the equivalent DOM, which is several thousand nodes and a soup of styled-component class hashes. The tree below is what locator.getByRole walks, and it is what a screen reader announces to a candidate.

candidate-page.aria.yml

02The integration end to end

From snapshot to action to audit row

The shape that makes the integration auditable. Snapshot before, act on a role+name target, snapshot after, hash both, write one row. The recruiter approval queue sits between the act step and the actual fire so nothing reaches the candidate until a human taps approve.

Tree snapshot to approval queue to ATS to audit log

Page DOM
ARIA snapshot
Role + name selector
Action draft
Approval queue
ATS API or UI fire
After-tree snapshot
Audit row
Candidate touchpoint

The wire-level snippet

A move-to-stage on Ashby, written against the accessibility tree. The snippet is Playwright because the API is the cleanest, but the same shape works in any tool that consumes a role+name target (Anthropic computer use accessibility-snapshot mode, the macOS AX APIs, Selenium 4 with the accessibility extension).

ats/move-stage.ts

03Per-vendor accessible names

The selector is the same shape across vendors; the name varies

The role is almost always button, listbox, dialog, or textbox. The accessible name is what changes per vendor. Capture the per-vendor name once when the integration is wired and pin it to the candidate-flow tests; they break loudly when a vendor rebrands a verb.

VendorRoleAccessible nameNote
Greenhousebutton"Move"stage transitions live in the candidate header
Leverbutton"Advance"advance fires the configured next stage
Workday Recruitingbutton"Change Stage"opens a modal listbox
Ashbybutton"Move stage"lowercase 's' in stage, watch the casing
Gem ATSbutton"Move"matches Greenhouse since Gem is built on top
SeekOut Growbutton"Update stage"different verb, same role

Names captured against current production UIs as of 2026-05-01. They drift. The integration health check re-snapshots the tree weekly and pages when a known role+name pair has gone missing.

Three integration shapes, side by side

The accessibility tree sits between the brittle CSS path and the expensive vision path. The trade is easiest to see when the three are written out next to each other on the same task: move a candidate from phone screen to onsite on Ashby.

FeatureCSS selectors / pixel computer useAccessibility tree (role + name)
Selector shapediv.candidate-row .stage-btn--rev4 OR a 1024x768 screenshot fed to a vision model.getByRole('button', { name: 'Move stage' }), the role and name from the published tree.
Stability across UI redesignsCSS class hashes rotate every release. Pixel layouts move with every CSS change.Role and name are a procurement contract. Vendor cannot rotate them without breaking the VPAT.
Cost per actionCSS selector parse is cheap, but rebuild cost on breakage is hours per vendor. Vision is 1500+ image tokens per screenshot before reasoning.ARIA snapshot is text, parses in milliseconds, and fits in a few hundred tokens for the relevant subtree.
Iframe and shadow DOMIframes break naive scrapers. Closed shadow DOM breaks both DOM and pixel readers.Tree composes across iframes; assistive tech has to traverse them so the integration does too.
Audit row contentSelector string and a screenshot. Hard to replay; hard to diff for a regulator.Role, name, before-tree hash, after-tree hash. Replayable. Diff is small enough to render in an approval queue.
Compliance fitCompliance team has to reverse-engineer what the agent saw from a screenshot.Same surface a screen reader sees; same surface a WCAG audit covers; one document trail.
When to fall backFirst choice if the ATS has no published a11y tree (rare on the major vendors).Default for major ATS. Falls back to API when the API surface exists, or to vision for genuinely visual edge cases (e.g. cropping a candidate avatar).

The three shapes coexist in production. The accessibility tree is the default; the others are fallbacks for surfaces it does not cover.

The numbers behind the trade

0major ATS vendors that publish WCAG 2.1 AA or 2.2 AA conformance you can script against (Greenhouse, Lever, Workday, Ashby)
0image tokens for one 1024x768 screenshot before any reasoning, per Anthropic vision pricing
0xsmaller, on average, an ARIA snapshot subtree is than the equivalent DOM for a candidate page
0CSS class hashes the agent has to chase across vendor releases when role+name is the selector

Image-token count from Anthropic vision pricing documentation. ARIA-snapshot vs DOM ratio measured across Ashby, Greenhouse and Lever candidate pages. Vendor-coverage count reflects published VPATs as of 2026-05-01.

04The audit row

What gets written when the recruiter taps approve

A tree-driven action produces an audit row that a regulator can replay. The row pins the action to the role+name target, the actor, the human who approved, and a hash of the before- and after-trees. Below is the actual row written by the move-to-stage in the snippet above.

audit_log.candidate_actions · row written 2026-04-30T14:11:04Z
0 CSS chases

The reason agentic ATS automation breaks at six months is that someone keeps rotating CSS class names. The accessibility tree is the only contract the vendor cannot rotate without breaking screen readers and the VPAT.

Pattern across three 2026 production agentic-ATS deployments

05How to wire it

Five steps to a tree-driven ATS integration

The order matters. Capture the per-vendor names first, then write the actions, then wire the queue, then the audit, then the health check. Skipping the health check is the most common reason an integration goes silently stale.

1

Open the candidate flow with a screen reader running

Start with the surface the integration is going to consume. VoiceOver on macOS, NVDA on Windows. Walk the candidate flow once and write down the announced role and name for every interactive control you intend to drive. That list is the per-vendor selector dictionary; it lives in the integration repo and is the source of truth.

2

Replace every CSS selector in the prototype with getByRole

Take whatever ATS automation prototype already exists and rewrite every selector to a role+name pair from the dictionary. Run the test suite against a sandbox tenant. The ones that fail are the controls that do not have an accessible name (a missing label, an icon-only button); fix those by patching the integration with an aria-label override or by filing the bug with the vendor.

3

Wire every action through the approval queue

No action fires directly against the live ATS. Every action is computed, the before-tree is captured, the action is written to the queue as a draft (Gmail label, Slack thread, web view), and the recruiter taps approve. The fire happens after the tap and the after-tree is captured against the same role+name target.

4

Write the audit row keyed to role + name + tree hash

One row per fired action. Tool, vendor, target_role, target_name, from-state, to-state, before_tree_sha256, after_tree_sha256, actor_agent, approved_by, approval_latency. That is the row a regulator under NYC LL144, IL HB 3773, CO CAIA, or EU AI Act high-risk hiring will ask to see.

5

Run a weekly tree-diff health check against the dictionary

Snapshot the candidate flow once a week against a known fixture and diff the role+name pairs against the dictionary. If a name has rotated (the verb changed, the casing changed, the button moved into a dialog), page the on-call. Vendors do not announce these changes; the diff is the only signal.

The diligence script for a vendor who claims to drive your ATS

Bring this list into any conversation with a vendor selling agentic ATS automation. The answers separate the tree-driven products from the CSS-scraper-with-marketing-copy ones quickly.

Ten minutes with the integration engineer

  • Show me the per-vendor accessible-name dictionary for Greenhouse, Lever, Workday, and Ashby. If the answer is a CSS file, the integration breaks the next time the vendor rotates a class name.
  • Open the candidate flow in your test harness and screen-share a Playwright trace. Every action target should be a role+name pair, not a class or a data-test attribute.
  • Show me the audit row for one fired action. It should pin the action to a role, a name, a before-tree hash, an after-tree hash, the actor agent, and the human who approved.
  • Tell me what happens when a vendor changes the accessible name on a button. The right answer is a tree-diff alert on the weekly health check, not a customer ticket two weeks later.
  • Show me the approval queue. Every UI action should sit in the queue as a draft until a recruiter taps approve. If the agent fires actions directly, the human-in-the-loop story for NYC LL144 is fictional.
  • Tell me the per-action token cost. Tree-driven actions should be in the low hundreds of tokens; pixel-driven actions are 1500+ before reasoning. The bill is the proof of architecture.
  • Tell me how the integration handles iframes and shadow DOM. The right answer involves frameLocator and accessibility-tree composition, not a workaround for each vendor.

06Where 10xats sits

The named agents drive the tree, not the DOM

10xats is one of several agentic ATS stacks shipping in 2026. The architectural choice that makes the workflow defensible to a recruiter and to a regulator at the same time is that every named agent that touches an ATS UI drives the accessibility tree, not the rendered DOM. The Sourcing Agent reads candidate dossiers from a role+name walk of the candidate listing page. The Scheduling Agent drafts panel emails from the role+name structure of the candidate timeline. The Match Rating audit log keys to the same role+name target the recruiter saw in the tree.

Every action sits in the recruiter approval queue as a draft with the tree snapshot attached. Approve fires the action through the published tree surface. The audit row writes itself, keyed to the role and name and to the before- and after-hash of the affected subtree. That is the shape NYC Local Law 144, Illinois HB 3773, Colorado CAIA, and the EU AI Act high-risk hiring rules all expect: a human in the loop, a logged action, and a replayable surface.

There are good reasons to use a different ATS automation stack. There is no good reason to drive the DOM or pixels when the published accessibility tree is sitting right there.

It is the cheapest surface

Hundreds of tokens, not thousands

An ARIA snapshot of the relevant subtree fits in a few hundred tokens. A 1024x768 screenshot is 1568 image tokens before any reasoning runs. At ten thousand actions a month the bill difference compounds.

It is the most stable surface

A procurement contract, not a refactor target

Roles and accessible names are part of the VPAT. Vendors cannot rotate them without breaking the conformance claim and breaking every screen reader. CSS class hashes have no such guarantee.

It is the most auditable surface

Same view a screen reader gets

The audit row pins to a role, a name, and a tree hash. A regulator can replay the action against the same surface a screen-reader user would have walked. CSS-selector logs and pixel logs cannot do that.

The one-paragraph version

Drive the surface that already has a contract

Every enterprise ATS already publishes an accessibility tree because procurement requires it. The tree is dense, semantically labeled, and stable across releases because changing it would break the VPAT. Driving an ATS through role+name selectors is the cheapest, most stable, most auditable architecture for an AI agent to work with. Combine it with a recruiter approval queue and a tree-hash audit log and you have the only shape that satisfies NYC LL144, Illinois HB 3773, Colorado CAIA, and the EU AI Act high-risk hiring rules without inventing a new compliance story per vendor. The surface a screen reader sees is the surface your agent should script against.

Want to see a tree-driven ATS action on your own pipeline?

Join the waitlist. We will walk through a move-stage on your live ATS through the accessibility tree, with the audit row attached.

Questions integration engineers actually ask about driving the accessibility tree

What does 'ATS accessibility tree integration' actually mean?

It means driving the ATS user interface through the browser's accessibility tree, the structured ARIA-labeled snapshot the browser already exposes for screen readers, instead of querying CSS selectors or interpreting pixel screenshots. An agent reads the tree, finds an interactive control by its role and accessible name (button 'Move to onsite', combobox 'Select stage', textbox 'Reject reason'), and acts on it. Greenhouse, Lever, Workday Recruiting and Ashby all publish WCAG 2.1 AA or 2.2 AA conformance, which means the tree is dense and stable enough to script. The selectors look like locator.getByRole('button', { name: 'Move to onsite' }) in Playwright, or the equivalent in any tool that reads the same accessibility surface.

Why is the accessibility tree more stable than CSS selectors for ATS automation?

Because the accessibility tree is a procurement contract. ATS vendors selling into federal agencies, public universities, and most Fortune 1000 employers must publish a VPAT declaring conformance to WCAG 2.1 AA, Section 508, and (for EU buyers) EN 301 549. Once a control is shipped with a published role and accessible name, changing those breaks the conformance claim and breaks every assistive technology that relies on the tree. Class names like .ats__StageDropdown_xX91 are an internal implementation detail and rotate with every redesign. Roles and names are a published API in everything but name.

Does this work for Greenhouse, Lever, Workday and Ashby specifically?

Yes. Workday publishes a public accessibility statement declaring WCAG 2.2 Levels A and AA plus Section 508 and EN 301 549 conformance. Greenhouse, Lever, and Ashby publish VPATs on request and ship ARIA roles on every interactive surface in the recruiter UI. The exact accessible names vary across vendors (the move-to-stage button reads 'Move' on Greenhouse, 'Advance' on Lever, 'Change Stage' on Workday and 'Move stage' on Ashby), so the selector is per-vendor, but the integration shape is identical: snapshot the tree, find by role and name, fire the action, capture the resulting tree as the diff.

How does this differ from RPA and from computer-use agents?

RPA tools (UiPath, Automation Anywhere, Blue Prism) historically targeted Windows desktop ATSes with image recognition and brittle XPath fallbacks; the cost was a fragile recorded macro that broke on every UI tweak. Computer-use agents (Anthropic computer use, OpenAI Operator) read pixels and infer affordances from screenshots; the cost is tokens (a 1024x768 screenshot is 1568 image tokens before any reasoning) and a slower loop. Accessibility-tree integration sits in between: cheaper than vision because the tree is text, more stable than RPA because role+name is a published contract, and faster than either because the snapshot is already structured.

What does the audit log look like when the integration uses role+name actions?

Every action writes a row with actor (the agent identifier and the recruiter who approved), tool (ats.move_stage, ats.add_note, ats.send_message), target (a role+name pair plus the rendered text snapshot of the affected node), prior_state (the tree slice before the action), new_state (the tree slice after), and a content hash of both. Regulators auditing under NYC Local Law 144, Illinois HB 3773, Colorado CAIA, or the EU AI Act high-risk hiring rules can replay any decision because the audit row contains both the surface the agent saw and the surface it produced. CSS-selector logs and pixel logs do not give regulators that.

Where does the recruiter approval queue fit in this picture?

Every UI action the agent takes is a draft, not a fired action. The agent computes the role+name target, captures the before-tree, and writes a draft to the approval queue (Gmail label, Slack channel, web view, same shape across all three). The recruiter taps approve and the action fires against the live ATS through the accessibility tree; the after-tree is captured and written to the audit log next to the approval. The queue is the human-in-the-loop gate every 2026 AI hiring law expects, and the accessibility tree is what makes the diff cheap to compute and small enough to render in the queue.

What about ATSes that ship a real public API? Why use the accessibility tree at all?

Use the public API when it exists and covers the surface area you need. Greenhouse Harvest, Lever Data API, Workday Recruiting REST and Ashby Public API cover candidate read, candidate write, application stage transitions, and event webhooks. The accessibility tree is for the long tail the API does not cover (custom scorecard fields a customer added in the admin panel, vendor-specific dropdowns, multi-step approval modals, anything behind a feature flag the API has not caught up to). It is also the bridge while a customer is migrating between ATSes and the API surface is in flux. The two integrations coexist; an agent uses the API when the surface exists and falls through to the tree when it does not.

How do you handle iframes, shadow DOM, and dynamic content in an ATS UI?

Iframes break naive DOM scrapers and most screen-recording RPA, but the accessibility tree composes across iframe boundaries because assistive technology has to traverse them. Playwright's frameLocator drops into the iframe and getByRole works as normal; computer-use accessibility-snapshot mode handles the same case at the OS level. Shadow DOM is similar: closed shadow roots are a problem for both DOM and a11y trees, but most ATS UIs use open shadow roots or no shadow DOM at all because closed roots also break screen readers and would fail the WCAG audit. Dynamic content (toasts, async loaders, optimistic state) is handled by waiting on a role transition, the same way a screen-reader user waits for the live region to announce.

How does 10xats use accessibility-tree integration?

Every named agent that drives an ATS UI uses role+name selectors first and falls back to the public API when the surface is exposed there. The Sourcing Agent reads candidate dossiers from a role+name walk of the candidate listing page; the Scheduling Agent drafts the panel emails by reading the role+name structure of the candidate timeline; the Match Rating agent writes the override audit row keyed to the same role+name target the recruiter saw. Every action surfaces in the approval queue as a draft with the tree snapshot attached. We do this because it is the only architecture that survives ATS UI redesigns and produces an audit log a regulator will accept.

What about ATSes that fail their WCAG audit?

Then the accessibility tree is sparse and the integration falls back to CSS selectors or vision for the missing controls, with a flag in the audit log noting that the action did not target a role+name pair. That happens, but rarely on the major ATSes; the procurement contracts force conformance because federal agencies and public universities cannot legally buy a non-conforming ATS. When it does happen, it is on a custom internal admin tool or on a startup ATS that has not shipped its first VPAT yet. The integration tracks per-vendor conformance and warns when a target falls back to a less stable selector.