A recruiter copies a client's job description into the ATS, hits publish, and it lands on your careers feed, Indeed and LinkedIn inside an hour. If that req is performed in Illinois, Minnesota, New Jersey, Vermont or Massachusetts, and there is no pay range in the ad, you have a problem that is yours and not the client's. Most of these statutes reach third parties who post on an employer's behalf, which is the entire staffing business model.
This is a good candidate for a small agent for the same reasons VMS intake is: the volume is daily, the rule is written down, and the failure is silent until someone complains. It is also not a legal opinion generator. The job is to check a posting against a table your counsel signed off on, block the ones that fail, and keep a record of what went out.
The current shape of the rules
As of early 2026 the state-level list a US staffing firm hits most often looks roughly like this. Thresholds are employee counts, and they vary; confirm each line with counsel before you encode it, and re-check quarterly, because cities keep adding ordinances.
| Jurisdiction | In force | Employer threshold | Usual ask |
|---|---|---|---|
| Colorado | 2021 | any | Rate or range, benefits summary, application deadline |
| New York State | Sept 2023 | 4+ | Range, job description |
| California | Jan 2023 | 15+ | Pay scale in the posting |
| Washington | Jan 2023 | 15+ | Range plus general benefits |
| Illinois | Jan 2025 | 15+ | Range plus benefits, and third-party posters are covered |
| Minnesota | Jan 2025 | 30+ | Range or fixed rate, benefits description |
| New Jersey | June 2025 | 10+ | Range plus benefits |
| Vermont | July 2025 | 5+ | Range |
| Massachusetts | Oct 2025 | 25+ | Range |
Plus city ordinances (Cincinnati, Toledo, Jersey City, Cleveland from late 2025) and salary-history bans that often travel with them. The point of the table is not that it is complete. The point is that it lives in a database rather than in a recruiter's memory.
create table posting_rules (
id bigserial primary key,
jurisdiction text not null, -- 'US-IL', 'US-NY', 'US-OH-CLEVELAND'
effective_from date not null,
effective_to date,
employee_threshold int, -- null = applies to all
requires_range boolean not null default true,
requires_benefits boolean not null default false,
requires_deadline boolean not null default false,
bans_salary_history boolean not null default false,
citation text not null, -- statute or ordinance, as cited by counsel
reviewed_on date not null,
reviewed_by text not null
);
reviewed_on and reviewed_by are not decoration. When a client asks why you posted a bare title in Newark last March, the answer is the row as it stood on that date, with a name against it.
Step one: resolve the jurisdictions for a req
This is the part everyone underestimates. A req is not in one place. It has a work location, sometimes several; it may be hybrid against an office; it may be remote-anywhere, in which case several states take the view that their rules apply if the work can be performed there.
Resolve to a set, not a value:
create table req_jurisdictions (
req_id text not null,
jurisdiction text not null,
basis text not null, -- 'worksite', 'remote-eligible', 'supervisor-location'
resolved_at timestamptz not null default now(),
primary key (req_id, jurisdiction)
);
Three resolver rules cover most of it. A named worksite adds its state and city. A remote-eligible req adds every jurisdiction in your rules table unless the client has excluded states in writing, in which case store the exclusion list on the req and add only the rest. An onsite req whose hiring manager sits elsewhere may add the supervisor's state; Illinois is the one that made people notice this.
Then the requirement for the posting is the union of the requirements of every resolved jurisdiction. Strictest wins. In practice that means most remote reqs need a range, a benefits line and, if Colorado is in the set, an application deadline.
Step two: gate the publish, do not chase after
The agent runs at the moment a recruiter tries to publish, not nightly. Nightly means the ad was live and wrong for nine hours.
The check itself is dull, which is the aim:
- Is there a
pay_minandpay_maxon the req, or a fixed rate, in structured fields rather than prose? Prose ranges parse badly and paste worse. - Is the range plausible? Flag anything wider than a factor your firm agrees on, say 1.8x, or a floor under the applicable minimum wage. "$1 to $500,000 per hour" is technically a range and has already been litigated about elsewhere.
- Does it carry the benefits sentence, if any resolved jurisdiction wants one?
- Does the description body contain salary-history questions, in a jurisdiction that bans them? A short pattern list catches most: "current salary", "salary history", "present compensation".
- Does the bill rate imply a pay range you cannot actually honour? This one is internal, but it is the one that saves margin arguments later.
Failures split two ways. Missing range in a covered jurisdiction is blocking: the publish button does not work, and the agent names the jurisdiction and the field to fill. Everything else is advisory: it posts, with a flag on the req and a line in the daily exceptions list.
Keep the model out of the blocking path. An LLM is useful for reading a client's pasted description and proposing which fields it implies, and for drafting the benefits sentence in the client's voice. It should not be the thing that decides whether a posting is lawful. Deterministic rule, human override, recorded.
Step three: record what actually went out
Boards mangle postings. Your ATS feed sends a range field that Indeed renders and LinkedIn sometimes does not, and syndication partners re-post stale copies for weeks. So store the artifact, per destination:
create table posting_events (
id bigserial primary key,
req_id text not null,
destination text not null, -- 'careers-site', 'indeed', 'linkedin', 'client-portal'
posted_at timestamptz not null,
removed_at timestamptz,
pay_min numeric(10,2),
pay_max numeric(10,2),
pay_unit text,
rules_applied jsonb not null, -- jurisdictions + rule ids as at posted_at
body_snapshot text not null,
actor text not null, -- the person who published
override_reason text
);
rules_applied freezes the rule versions used, so a later rule change does not rewrite history. body_snapshot is what a regulator or a plaintiff's lawyer will ask for, and it is cheap to keep. Illinois expects records for a number of years; pick your longest applicable retention, put it in the purge job, and do not keep candidate PII in this table at all — it is about ads, not people.
Overrides deserve their own field. A recruiter will hit a blocking failure at 4:55pm with a client on the phone, and someone senior will wave it through. Better that the wave-through is a row with a name and a reason than a disabled check.
What to measure after a month
- Share of new postings with a structured range, before and after.
- Blocking failures caught at publish, by jurisdiction. This is the number that tells you which client intake is sloppy.
- Overrides, with reasons, and who signed them.
- Time from req creation to live posting. If the gate added more than a couple of minutes, your intake form is missing fields, not your recruiters.
The second number usually surprises owners. Firms that believe they are clean typically find a fifth to a third of reqs arriving from clients with no rate at all, which is a client intake problem the agent has just made visible.
When not to build this
If you post exclusively in states with no transparency rule and have no remote reqs, a checklist in the intake form is enough. Not yet.
If you post across five or more states, take remote reqs, or syndicate to boards you do not control, the gate pays for itself the first time a client sends a bare title for a New Jersey role. It is also a two-week build in most ATSs, assuming pay fields exist and are not free text — on Loxo, Crelate, JobAdder, Vincere and Recruiterflow they usually do, though what the feed actually transmits to each board is worth testing before you trust it.
One warning. Nothing here makes a posting lawful, and no tool can. It enforces a table your counsel wrote and produces a record of what you published. That is the part engineering can own.
If you want it built, contact us with your ATS, the states you post in and a month of postings, and we will tell you what share would have failed the gate. A Workflow Assessment ranks it against the rest of the queue.