We run about ninety programmatic pages of the form "what assessment does Employer X use". It is a standard SEO play: one data file, one template, ninety pages, real search demand behind every one.
It is also a standard way to publish confident nonsense at scale, and for a while we did. This is what the audit found and what we changed in the code so it could not happen again.
Three sources of truth, all disagreeing
The associations between employers and assessment vendors lived in three separate places, none of which referenced the others. The result was exactly what you would predict:
The HSBC guide implied Cubiks and SHL.
/employers/hsbc was written about Arctic Shores.
PROVIDER_COMPANIES cited a prep site for Cubiks.
Three surfaces, three answers, same employer. A reader could land on our guide, be told to prepare for one vendor, and sit a completely different test.
Worse than the disagreement was where the data came from. Several associations were, by the old file's own admission in a comment, "sector-appropriate selections". Which is a considerate way of saying somebody looked at a bank and wrote down the vendor banks tend to use.
The audit numbers:
- 15 of 38 employers had any sourced association at all. The rest were inference.
- Four US guides claimed their employer appeared in a vendor's published customer logos. That vendor's live customer page does not list them.
That last one is the one that actually stings. It is not a vague claim, it is a specific, checkable, false statement about a named third party, published on ninety pages worth of template, because nobody ever opened the page it cited.
The rule that fixes it: no URL, no entry
The rewrite collapsed everything into one file with one rule at the top:
1. Every association carries a URL someone actually opened. No URL, no entry.
2. No association is inferred from sector norms or from what peers use. An
employer with no evidence gets an empty `associations` array, and the site
says so rather than guessing.
The type system enforces the first one, because the URL is not optional:
export interface ProviderEvidence {
provider: AssessmentProvider
/** A URL that was actually opened and checked, not a plausible-looking one. */
url: string
type: EvidenceType
date: string
confidence: Confidence
note: string
}
You cannot add an association without a citation. That does not stop someone pasting a plausible-looking URL, but it does stop the specific failure mode where a claim exists because it felt right and nobody ever had to write down where it came from.
Evidence has a type, and the type is not a vibe
export type EvidenceType =
/** The provider's own case study, customer page or press release. */
| 'first-party-provider'
/** The employer's own careers page, job posting or transparency record. */
| 'employer-official'
/** Multiple independent, recent candidate reports agreeing. */
| 'candidate-reports'
/** An assessment-preparation site's claim. Weakest; never sufficient alone. */
| 'prep-site'
And a separate axis for how much weight a claim can carry:
/**
* - `high` first-party or employer-official, dated 2024 or later. Safe to
* state plainly (still with a "confirm with your recruiter" note).
* - `medium` first-party but undated, or several agreeing recent candidate
* reports. State as reported, not as confirmed.
* - `low` a single prep-site or candidate claim. Usable as background only;
* never the subject of a page.
*/
export type Confidence = 'high' | 'medium' | 'low'
Then the thing that makes all of it real:
4. `confidence` gates what a guide page claims: only `high` and `medium`
associations are shown as providers an employer uses, because a guide is a
promise about what someone is about to sit.
A low association is not deleted, it is demoted. It stays in the file as background for whoever next works on that employer, and it cannot reach a page. This is the pattern I would steal for any content-driven site: keep the weak evidence, make the rendering layer refuse it. Deleting it means the next person redoes the research and arrives at the same weak conclusion with nothing recording that it was already considered and rejected.
A real entry looks like this, and the note is written to be read by a human who is deciding whether to trust it:
{
provider: 'hirevue',
url: 'https://www.hirevue.com/resources/video/how-philips-streamlined-global-early-careers-hiring',
type: 'first-party-provider',
date: 'undated (opened 2026-09-12)',
confidence: 'high',
note: 'HireVue resource page on Philips global early-careers hiring, in HireVue’s own words: "They selected Hirevue’s Video Interview and Games-Based Assessment solutions to do this." Names the games product specifically rather than HireVue generally, which is what makes it usable on an assessment page.',
}
Note date: 'undated (opened 2026-09-12)'. The source has no publication date, so the field says so and records when we looked instead of inventing a date that would make the citation look stronger than it is.
The hardest rule: say that you do not sell the answer
This is the one with real commercial cost.
3. Only providers we actually simulate appear here. Where the real vendor is
something we do not offer it goes in `unsupportedVendor` so the page can be
honest about it instead of substituting a provider we happen to sell.
We sell practice for a set of assessment vendors. Some employers do not use any of them:
{
slug: 'amazon',
name: 'Amazon',
sector: 'Technology',
hasGuide: true,
unsupportedVendor:
'Amazon’s own instruments: Work Simulation, Work Style Assessment and the SDE Online Assessment, all built around its Leadership Principles.',
associations: [],
}
The Amazon page tells you Amazon uses its own instruments, which we do not simulate. The commercially optimal version of that page recommends the nearest product we do sell. We do not, because a candidate who buys practice for the wrong test and then sits the real one has been actively harmed by us, and they find out at the worst possible moment.
The Allstate entry goes further and records the specific thing we got wrong:
unsupportedVendor:
'JEPS (Job Effectiveness Prediction System), Allstate’s own battery. Allstate is NOT on wonderlic.com/customers, contrary to what this guide used to claim.',
The correction lives in the data, permanently, so nobody re-adds it from the same bad source. Retractions belong next to the thing that was wrong, not in a changelog.
Citations rot, so the file has an expiry date
Employers change vendors, and provider logo walls and case-study pages rotate.
Re-verify before a marketing push, and when a link rots, downgrade or remove
the association rather than leaving a citation that no longer resolves.
Last verified: 2026-08-17.
A dated verification stamp at the top of the file is the cheapest possible freshness mechanism, and it is honest in a way "updated recently" on the page footer is not. The instruction on rot matters too: the default when a link dies is to downgrade, not to keep the claim and quietly drop the citation.
Look at the output
The whole point is that the discipline is visible from outside. Compare a few:
- cogniprep.app/employers/philips and cogniprep.app/employers/siemens: sourced associations, stated plainly, with the vendor named.
- cogniprep.app/employers/amazon: tells you the vendor is Amazon's own and that we do not simulate it. No upsell to the nearest thing we sell.
- cogniprep.app/employers: the full set, where you can spot how many are honest about having no confirmed vendor.
If you are evaluating any "what test does X use" site, including ours, that is the check: find an employer whose assessment the site does not sell practice for, and see what the page does.
If you build programmatic pages
- One data file, and let everything else derive from it. Three surfaces with three answers is not an edge case, it is the default outcome of copying a list.
- Make the citation a required field. Not a comment, not a spreadsheet somewhere. A non-optional property on the type.
- Separate "what kind of evidence" from "how much weight". A first-party source from 2019 and a pile of recent forum posts are different kinds of weak.
- Gate rendering on confidence. Keep low-confidence data, refuse to publish it.
- Give the template an "I do not know" state, and make sure it is genuinely reachable. If every page has an answer, some of the answers are made up.
- Date your verification and write down what you previously got wrong, in the data, next to the fix.
The general point: programmatic SEO multiplies whatever is in your data file by the number of pages in your template. If the file contains inference, you are not publishing ninety pages, you are publishing ninety guesses with a shared stylesheet.
Top comments (0)