Docs-as-tests validation
Documentation drifts out of date the moment the systems it describes change: a link rots, an API endpoint retires, a copied code sample no longer parses, a documented step stops working. Docs-as-tests treats your published documentation like a test suite and reports what no longer holds up.
Two systems carry that job, and they behave differently:
System | What it tests | Setup |
|---|---|---|
Built-in checks | Links, internal cross-references, documented API endpoints, and code samples in your published topics | None. Runs weekly and on demand |
CI test ingestion (Doc Detective) | Your own end-to-end tests: documented steps walked in the live product, screenshots compared against it | Connect a CI runner, add a secret and a workflow |
Both are configured in Settings > Connections, in the Built-in checks and CI test ingestion (Doc Detective) cards, and both turn what they find into fix-it items in your Demand worklist. Neither ever edits your content.
Docs-as-tests is available on every plan, including Free.
Built-in checks
What is scanned
Only topics that are published and not archived. A topic archived after publication is skipped, because it is no longer live for readers.
What each check does
Each run performs four independent checks.
Check | What it does | Finding type |
|---|---|---|
External links | Requests every external |
|
Internal cross-references | Verifies every link to another topic still points at a topic that exists and is published |
|
API endpoints | For reference topics only, probes concrete documented API URLs and flags ones that are gone |
|
Code samples | Parses JSON and YAML code blocks and flags ones that fail to parse |
|
External links
The scan requests each external link with HEAD first. When the server answers 401, 403, 404, 405, or 429, the scan retries with GET before deciding anything: many servers, CDNs, and bot walls refuse HEAD on URLs that GET serves normally. Only a link that still fails after that retry is flagged. Requests are batched with a delay between batches, and requests to private or internal network addresses are blocked.
The finding records the failing URL and the status code, or "unreachable" for a connection error or timeout.
Internal cross-references
Every link to another topic is checked against the database, with no HTTP request involved. Two things count as broken:
Finding title | Meaning |
|---|---|
Broken cross-reference: linked topic was deleted | The target topic no longer exists, so the link is dead for readers |
Cross-reference to an unpublished topic | The target exists but is not published, so the link is dead on the live site |
API endpoints
This check runs only on topics whose type is reference, typically API documentation, including topics imported from an OpenAPI spec. It finds full URLs containing an api or version segment such as /v1/ and probes each one exactly as written.
Two rules keep it quiet:
Templated URLs are skipped. Anything carrying a placeholder such as
{id},<token>,:userId, or*is never probed, because a placeholder path 404s by design.Auth walls are not failures. Only a connection failure (
API host unreachable) or a 404 or 410 (Documented API endpoint gone) raises a finding. A 401, 403, 405, or 429 means the endpoint still exists and merely refused an unauthenticated call, so nothing is reported.
Code samples
Fenced code blocks with a declared language are parsed by a real parser, never by a pattern-matching guess.
Language | Check |
|---|---|
JSON | Must parse with a JSON parser |
YAML | Must parse with a YAML parser, including multi-document files separated by |
The YAML check catches bad indentation, tab characters, duplicate keys, and broken flow syntax, and the finding quotes the parser's own error with its line and column.
Code blocks in any other language, or with no declared language, are not checked. XML and HTML validation was removed: it counted tags to guess whether they balanced, which flagged legitimate fragments and self-closing elements as errors.
When the built-in checks run
Weekly, Mondays at 03:00 UTC.
On demand, when you click Run checks now in the Built-in checks card. The button queues a run and confirms with "Checks queued — new issues appear in your Demand worklist when the run finishes."
Each run reconciles the previous results rather than replacing them wholesale: a problem that is still there keeps its existing finding, and a problem that is fixed disappears on the next run.
Where built-in findings appear
Every built-in finding is a warning severity, and it appears in three places:
The editor's Findings panel for the affected topic
The Content health tab of Analytics
The Demand worklist
Finding type | Meaning |
|---|---|
| An external link failed, or a cross-reference points at a deleted or unpublished topic |
| A documented API endpoint is unreachable or gone |
| A JSON or YAML code sample failed to parse |
Each finding includes the offending value (the URL, the target topic, or a preview of the code) so you can locate and fix it without re-running the scan.
CI test ingestion (Doc Detective)
Built-in checks test what your documentation references. Doc Detective tests what it instructs: it walks the documented steps in the live product and compares screenshots. Connect the CI that runs those tests, and a failure becomes a fix-it item on the topic that carries the broken instruction.
Connect a test runner
Go to Settings > Connections.
In the CI test ingestion (Doc Detective) card, enter a Name for the pipeline, for example, "GitHub Actions".
Click Add test runner. The setup panel opens automatically, because setup is still required.
Copy the Secret. In your code repository, add it as an Actions secret named
TOPICARY_DOCTEST_SECRET, under Settings > Secrets and variables > Actions.Copy the Workflow file and commit it to
.github/workflows/doc-tests.yml.
The generated workflow checks out the repository, runs npx doc-detective@latest runTests, and posts the report back to Topicary with an HMAC-SHA256 signature, the commit, the branch, the run URL, and up to 30 screenshots. It runs on every push to main and on a weekly schedule (Mondays, 06:00 UTC).
The workflow file is generated for GitHub Actions, but the ingestion endpoint is not GitHub-specific. Any runner can POST its Doc Detective output.json to https://topicary.com/api/v1/webhooks/doctest with the x-topicary-connection and x-topicary-signature-256 headers shown in the same panel.
Read a runner's state
Each runner row shows one status and its last result:
Status | Meaning |
|---|---|
Awaiting first run | Connected and enabled, but no report has arrived yet |
Connected | At least one report has been received |
Paused | Disabled with Pause. Reports are not ingested until you click Resume |
Below the name, the row reads either "No test runs yet" or a summary of the last run: passed, failed, and warning counts.
Use Secret, workflow & name to reopen the setup panel, rename the runner, or copy the secret and workflow again. Remove test runner deletes the connection.
Where CI failures appear
CI failures do not go to the Findings panel, and they are not content findings. They appear in the Demand worklist.
Opening one from Demand opens the affected topic with a From Demand banner. Click What failed in that banner to expand the whole failure:
Row | What it shows |
|---|---|
Test | Failed or Warning, the test name, and the file the test is defined in |
Step | Each failed step in plain language, for example, "Could not find 'Save' on the page" or "The page no longer looks like its saved screenshot" |
What to do | For a visual failure: compare the screenshots, and update the topic when the new look is intended. Otherwise: walk the documented steps in the live product and update the topic when the product changed |
Run | The screenshot comparison, a link to the CI run, and the branch and commit that produced it |
Mark fixed sits in the banner itself, so you can close the item without going back to the worklist.
See also
Content health indicators: the structural and freshness checks that run continuously
Automated lifecycle recommendations: the nightly agent that flags stale, unverified, and duplicate topics
Content statuses: only Published topics are scanned by the built-in checks