<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: EkLine.io</title>
    <description>The latest articles on DEV Community by EkLine.io (@ekline).</description>
    <link>https://dev.to/ekline</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3770936%2F5ca4ed3c-868e-4e7a-9a2e-5500fd486d37.png</url>
      <title>DEV Community: EkLine.io</title>
      <link>https://dev.to/ekline</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ekline"/>
    <language>en</language>
    <item>
      <title>What Wrong Docs Cost Test Automation Teams</title>
      <dc:creator>EkLine.io</dc:creator>
      <pubDate>Thu, 21 May 2026 15:00:27 +0000</pubDate>
      <link>https://dev.to/ekline/what-wrong-docs-cost-test-automation-teams-56ek</link>
      <guid>https://dev.to/ekline/what-wrong-docs-cost-test-automation-teams-56ek</guid>
      <description>&lt;p&gt;You know the feeling. You copy a code example from the official docs, wire it into your test suite, and everything passes. Green across the board. Three months later, the framework ships a major version, that example uses a deprecated command, and suddenly 200 tests across 50 teams are failing on Monday morning.&lt;/p&gt;

&lt;p&gt;Nobody changed the tests. Nobody changed the application. The docs were wrong, and the tests were built on them.&lt;/p&gt;

&lt;p&gt;In test automation, a wrong code example does not break one test. It breaks every test that was modeled after it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ecosystem is bigger than you think
&lt;/h2&gt;

&lt;p&gt;Test automation frameworks are infrastructure now, not niche tools.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/microsoft" rel="noopener noreferrer"&gt;
        microsoft
      &lt;/a&gt; / &lt;a href="https://github.com/microsoft/playwright" rel="noopener noreferrer"&gt;
        playwright
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API. 
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🎭 Playwright&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://www.npmjs.com/package/playwright" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/0c0b88b731baae15c1fea7318541bac85741ecce8999effb24c9ef0424c90a9c/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f706c61797772696768742e737667" alt="npm version"&gt;&lt;/a&gt; &lt;a href="https://www.chromium.org/Home" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/5312d1fa20695126863c5976e408f40a7714789d9f35e5e44a442263d3fd45d7/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6368726f6d69756d2d3134392e302e373832372e32322d626c75652e7376673f6c6f676f3d676f6f676c652d6368726f6d65" alt="Chromium version"&gt;&lt;/a&gt; &lt;a href="https://www.mozilla.org/en-US/firefox/new/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/51f758526b88b75c8d169439c608e0b8de953f0fbc795cbd8711a02e0f18ebef/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f66697265666f782d3135302e302e322d626c75652e7376673f6c6f676f3d66697265666f7862726f77736572" alt="Firefox version"&gt;&lt;/a&gt; &lt;a href="https://webkit.org/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/5c900e8803765a59d9436251138d1ffc21f224465c01ba5a22cf5ebca96ea992/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7765626b69742d32362e342d626c75652e7376673f6c6f676f3d736166617269" alt="WebKit version"&gt;&lt;/a&gt; &lt;a href="https://aka.ms/playwright/discord" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a0f278c5bb9bf1c04af523df174c2526d259f8441df73f3378f686863c9ffde0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6a6f696e2d646973636f72642d696e666f726d6174696f6e616c" alt="Join Discord"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;
&lt;a href="https://playwright.dev" rel="nofollow noopener noreferrer"&gt;Documentation&lt;/a&gt; | &lt;a href="https://playwright.dev/docs/api/class-playwright" rel="nofollow noopener noreferrer"&gt;API reference&lt;/a&gt;
&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Playwright is a framework for web automation and testing. It drives Chromium, Firefox, and WebKit with a single API — in your tests, in your scripts, and as a tool for AI agents.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Get Started&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Choose the path that fits your workflow:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;

&lt;th&gt;Best for&lt;/th&gt;
&lt;th&gt;Install&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://github.com/microsoft/playwright#playwright-test" rel="noopener noreferrer"&gt;Playwright Test&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;End-to-end testing&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npm init playwright@latest&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://github.com/microsoft/playwright#playwright-cli" rel="noopener noreferrer"&gt;Playwright CLI&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Coding agents (Claude Code, Copilot)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npm i -g @playwright/cli@latest&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://github.com/microsoft/playwright#playwright-mcp" rel="noopener noreferrer"&gt;Playwright MCP&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AI agents and LLM-driven automation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @playwright/mcp@latest&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://github.com/microsoft/playwright#playwright-library" rel="noopener noreferrer"&gt;Playwright Library&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Browser automation scripts&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npm i playwright&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://github.com/microsoft/playwright#vs-code-extension" rel="noopener noreferrer"&gt;VS Code Extension&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Test authoring and debugging in VS Code&lt;/td&gt;
&lt;td&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright" rel="nofollow noopener noreferrer"&gt;Install from Marketplace&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Playwright Test&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Playwright Test is a full-featured test runner built for end-to-end testing. It runs tests across Chromium, Firefox, and WebKit with full browser isolation, auto-waiting, and web-first assertions.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Install&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm init playwright@latest&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Or add manually:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm i -D @playwright/test
npx playwright install&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Write a test&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-ts notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-s1"&gt;test&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s1"&gt;expect&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/microsoft/playwright" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/cypress-io" rel="noopener noreferrer"&gt;
        cypress-io
      &lt;/a&gt; / &lt;a href="https://github.com/cypress-io/cypress" rel="noopener noreferrer"&gt;
        cypress
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Fast, easy and reliable testing for anything that runs in a browser.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;
  &lt;a href="https://www.cypress.io" rel="nofollow noopener noreferrer"&gt;
    
      
      
      &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcypress-io%2Fcypress%2FHEAD%2F.%2Fassets%2Fcypress-logo-light.png" class="article-body-image-wrapper"&gt;&lt;img alt="Cypress Logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcypress-io%2Fcypress%2FHEAD%2F.%2Fassets%2Fcypress-logo-light.png"&gt;&lt;/a&gt;
    
  &lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://on.cypress.io" rel="nofollow noopener noreferrer"&gt;Documentation&lt;/a&gt; |
  &lt;a href="https://on.cypress.io/changelog" rel="nofollow noopener noreferrer"&gt;Changelog&lt;/a&gt; |
  &lt;a href="https://on.cypress.io/roadmap" rel="nofollow noopener noreferrer"&gt;Roadmap&lt;/a&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;
  The web has evolved. Finally, testing has too.
&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;
  Fast, easy and reliable testing for anything that runs in a browser.
&lt;/p&gt;

&lt;p&gt;
  Join us, we're &lt;a href="https://cypress.io/jobs" rel="nofollow noopener noreferrer"&gt;hiring&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://www.npmjs.com/package/cypress" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/8dd959f9df9c4ee7a213ab4ab890646f38f556c65c304b4a46d353126593862d/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f646d2f637970726573732e737667" alt="npm"&gt;
  &lt;/a&gt;
  &lt;a href="https://on.cypress.io/discord" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/50684c858eed5a4136514771f85ad259dafc9dae903f8d8038e5b2bddaed5d98/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636861742d6f6e253230446973636f72642d627269676874677265656e" alt="Discord chat"&gt;
  &lt;/a&gt;
    &lt;a href="https://stackshare.io/cypress" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/df3808304b0cf5754b0c5b350b3612761329b575a84416fae092c369a0f55d3e/68747470733a2f2f696d672e737461636b73686172652e696f2f6d6973632f666f6c6c6f772d6f6e2d737461636b73686172652d62616467652e737667" alt="StackShare"&gt;
  &lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What is Cypress?&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;
  &lt;a href="https://player.vimeo.com/video/237527670" rel="nofollow noopener noreferrer"&gt;
    &lt;img alt="Why Cypress Video" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1271364%2F31739717-dbdff0ee-b41c-11e7-9b16-bfa1b6ac1814.png" width="75%" height="75%"&gt;
  &lt;/a&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installing&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://badge.fury.io/js/cypress" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/10ce24aa266f0fc04f8727ee209c49621dcee573b88cdb258fe1c10c2e847035/68747470733a2f2f62616467652e667572792e696f2f6a732f637970726573732e737667" alt="npm version"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Install Cypress for Mac, Linux, or Windows, then &lt;a href="https://on.cypress.io/install" rel="nofollow noopener noreferrer"&gt;get started&lt;/a&gt;.&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install cypress --save-dev&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;or&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;yarn add cypress --dev&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;or&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;pnpm add cypress --save-dev&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/cypress-io/cypress/./assets/cypress-installation.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcypress-io%2Fcypress%2FHEAD%2F.%2Fassets%2Fcypress-installation.gif" alt="installing-cli e1693232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Contributing&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://cloud.cypress.io/projects/ypt4pf/runs" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/bb7e01b57076ac4208bc26f719235155736820192c1450ffb412365f4e4eef1b/68747470733a2f2f696d672e736869656c64732e696f2f656e64706f696e743f75726c3d68747470733a2f2f636c6f75642e637970726573732e696f2f62616467652f73696d706c652f7970743470662f646576656c6f70267374796c653d666c6174266c6f676f3d63797072657373" alt="cypress"&gt;&lt;/a&gt;
&lt;a href="https://circleci.com/gh/cypress-io/cypress/tree/develop" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/48bab6e43fa291a9720feaaf7572a8e8d9af53f33a8ec06c814154f00381deb2/68747470733a2f2f636972636c6563692e636f6d2f67682f637970726573732d696f2f637970726573732f747265652f646576656c6f702e7376673f7374796c653d737667" alt="CircleCI"&gt;&lt;/a&gt; -  &lt;code&gt;develop&lt;/code&gt; branch&lt;/p&gt;
&lt;p&gt;Please see our &lt;a href="https://github.com/cypress-io/cypress/./CONTRIBUTING.md" rel="noopener noreferrer"&gt;Contributing Guideline&lt;/a&gt; which explains repo organization, linting, testing, and other steps.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;License&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/cypress-io/cypress/blob/develop/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/8bb50fd2278f18fc326bf71f6e88ca8f884f72f179d3e555e20ed30157190d0d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e2e737667" alt="license"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This project is licensed under the terms of the &lt;a href="https://github.com/LICENSE" rel="noopener noreferrer"&gt;MIT license&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Badges&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Configure a badge for your project's README to show your test status or test count in the &lt;a href="https://www.cypress.io/cloud" rel="nofollow noopener noreferrer"&gt;Cypress Cloud&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://cloud.cypress.io/projects/ypt4pf/runs" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/bb7e01b57076ac4208bc26f719235155736820192c1450ffb412365f4e4eef1b/68747470733a2f2f696d672e736869656c64732e696f2f656e64706f696e743f75726c3d68747470733a2f2f636c6f75642e637970726573732e696f2f62616467652f73696d706c652f7970743470662f646576656c6f70267374796c653d666c6174266c6f676f3d63797072657373" alt="cypress"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://cloud.cypress.io/projects/ypt4pf/runs" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/5a6558983961994a92c49b8ecafc27aa652d956f64d6f2168b7ab8f34a9a806f/68747470733a2f2f696d672e736869656c64732e696f2f656e64706f696e743f75726c3d68747470733a2f2f636c6f75642e637970726573732e696f2f62616467652f636f756e742f7970743470662f646576656c6f70267374796c653d666c6174266c6f676f3d63797072657373" alt="cypress"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Or let the world know your project is using Cypress with the badge below.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.cypress.io/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/02695ab60ccd645829c15705ea5d6b420e66f2c2d74597d3bea52878446ce9c3/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f746573746564253230776974682d437970726573732d3034433338452e737667" alt="Cypress.io"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;[![Cypress.io](https://img.shields.io/badge/tested%20with-Cypress-04C38E.svg)](https://www.cypress.io/)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/cypress-io/cypress" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Framework&lt;/th&gt;
&lt;th&gt;GitHub Stars&lt;/th&gt;
&lt;th&gt;Dependent Projects&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/microsoft/playwright" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;85,100+&lt;/td&gt;
&lt;td&gt;451,000+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/cypress-io/cypress" rel="noopener noreferrer"&gt;Cypress&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;49,600+&lt;/td&gt;
&lt;td&gt;1,500,000+&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Playwright publishes documentation across &lt;a href="https://playwright.dev/docs/intro" rel="noopener noreferrer"&gt;four languages&lt;/a&gt;: Node.js, Python, Java, and .NET. That is four times the surface area for documentation drift. Cypress has shipped &lt;a href="https://github.com/cypress-io/cypress" rel="noopener noreferrer"&gt;389 releases&lt;/a&gt; since its inception, and each release can deprecate commands, change default behaviors, or introduce new APIs.&lt;/p&gt;

&lt;p&gt;A code example written for Cypress 12 may use &lt;code&gt;cy.intercept()&lt;/code&gt; patterns that behave differently in Cypress 15. The example still runs. It just does not do what you expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real cost, by the numbers
&lt;/h2&gt;

&lt;p&gt;Here is where stale docs hit your team's budget, velocity, and trust.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test maintenance eats 40-60% of all testing effort
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.capgemini.com/insights/research-library/world-quality-report/" rel="noopener noreferrer"&gt;Capgemini World Quality Report&lt;/a&gt; consistently finds that test maintenance consumes &lt;strong&gt;40-60% of total testing effort&lt;/strong&gt;. That is not writing new tests. That is fixing existing ones that broke because something changed underneath them.&lt;/p&gt;

&lt;p&gt;When a QA engineer fixes a failing test, the first thing they check is the framework docs. If the docs still show the old pattern, the engineer cannot tell whether the test is wrong or the framework changed. They burn time debugging a problem that accurate documentation would have prevented.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://katalon.com/resources/state-of-software-quality-report" rel="noopener noreferrer"&gt;Katalon State of Software Quality Report 2025&lt;/a&gt; found that only &lt;strong&gt;11% of QA teams&lt;/strong&gt; have reached an optimized level of test automation maturity. &lt;strong&gt;56% of QA teams&lt;/strong&gt; report they cannot keep up with testing demands. Documentation that adds friction instead of removing it makes this gap wider.&lt;/p&gt;

&lt;h3&gt;
  
  
  72% of developers abandon tools over bad docs
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.postman.com/state-of-api/" rel="noopener noreferrer"&gt;Postman 2023 State of the API Report&lt;/a&gt; found that &lt;strong&gt;72% of developers&lt;/strong&gt; have abandoned an API due to poor documentation. &lt;strong&gt;54%&lt;/strong&gt; cite documentation as the number one factor when evaluating a tool.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://survey.stackoverflow.co/2023/" rel="noopener noreferrer"&gt;Stack Overflow Developer Survey 2023&lt;/a&gt; found that &lt;strong&gt;90.36% of developers&lt;/strong&gt; use technical documentation as their primary learning resource. For test frameworks competing for adoption (Cypress vs. Playwright vs. Selenium vs. AI-powered tools like Mabl and Testim), your docs are the product evaluation. A developer who hits a broken quickstart example does not file a bug. They try the other framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI pipeline failures: the cost nobody tracks
&lt;/h3&gt;

&lt;p&gt;Each engineering-escalated support ticket costs &lt;strong&gt;$150 to $250&lt;/strong&gt; (&lt;a href="https://developerrelations.com/devrelcon" rel="noopener noreferrer"&gt;DevRelCon&lt;/a&gt; / &lt;a href="https://www.metricnet.com/" rel="noopener noreferrer"&gt;MetricNet&lt;/a&gt;). For a framework with thousands of users, a single stale example that generates 100 support tickets costs $15,000 to $25,000 in direct support alone. The real cost is the engineering hours spent debugging a documentation problem instead of shipping features.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this looks like in your codebase
&lt;/h2&gt;

&lt;p&gt;Your testing framework docs show this page object pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Cypress 12 page object pattern (outdated)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginPage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;fillUsername&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;fillPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But Cypress 15 recommends the App Actions pattern with &lt;code&gt;cy.session()&lt;/code&gt; for authentication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Cypress 15 recommended pattern&lt;/span&gt;
&lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-testid="username"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-testid="password"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-testid="submit"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;include&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both compile. Both run. But the first pattern creates a new browser session for every test, making the suite &lt;strong&gt;3-5x slower&lt;/strong&gt;. A team that follows the old docs builds 500 tests on the slow pattern before discovering the performance problem. Rewriting 500 tests is not a bug fix. It is a quarter of engineering time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things that close the gap
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Automated review on every PR.&lt;/strong&gt; When framework code changes, check if documentation references the changed commands, selectors, or configuration options. Flag stale examples before they merge. For a framework shipping 30+ releases per year, manual review cannot keep pace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Version-aware example validation.&lt;/strong&gt; Your documentation linting should know which framework version each example targets. An example using &lt;code&gt;cy.server()&lt;/code&gt; (removed in Cypress 12) or &lt;code&gt;page.waitForNavigation()&lt;/code&gt; (deprecated in Playwright) should trigger a warning in the PR where the deprecation happens, not after a user reports it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Cross-language freshness tracking.&lt;/strong&gt; For frameworks like Playwright that publish docs in four languages, track which language variants have been updated when the underlying API changes. A Node.js example that gets updated while the Python equivalent stays stale creates an inconsistency that users of the less-maintained language will hit first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;If your testing framework documentation is the first thing a QA engineer reads when evaluating your tool, it needs the same quality bar as the framework itself.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;How does your team keep test framework docs in sync?&lt;/strong&gt; Drop your workflow in the comments. I am genuinely curious whether anyone has solved the version drift problem across multiple language SDKs.&lt;/p&gt;




&lt;p&gt;If your team has this problem, where stale code examples in docs are creating downstream test failures, &lt;a href="https://ekline.io?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=qatest&amp;amp;utm_content=wrong-docs-cost-test-automation" rel="noopener noreferrer"&gt;EkLine&lt;/a&gt; automates documentation review in your GitHub PR workflow. It catches stale code examples, broken links, and style violations before they reach developers. &lt;a href="https://ekline.io/demo?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=qatest&amp;amp;utm_content=wrong-docs-cost-test-automation" rel="noopener noreferrer"&gt;See how it works&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>documentation</category>
      <category>qa</category>
    </item>
    <item>
      <title>An Open Banking API Is a Contract. Your Docs Are the Contract Surface.</title>
      <dc:creator>EkLine.io</dc:creator>
      <pubDate>Thu, 14 May 2026 13:39:29 +0000</pubDate>
      <link>https://dev.to/ekline/an-open-banking-api-is-a-contract-your-docs-are-the-contract-surface-na7</link>
      <guid>https://dev.to/ekline/an-open-banking-api-is-a-contract-your-docs-are-the-contract-surface-na7</guid>
      <description>&lt;p&gt;Strip the acronyms off open banking and what you have is a small, boring promise.&lt;/p&gt;

&lt;p&gt;A bank says to a third-party app: &lt;em&gt;if you bring me a customer who agreed to share their data, I will hand you that data in a predictable shape, through a predictable door, using a predictable key, and I will tell you in a predictable way when something goes wrong.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Four predictable things. That is the whole deal.&lt;/p&gt;

&lt;p&gt;When all four hold, an app can read a customer's transactions and tell them they spend too much on coffee. When even one of them slips, calls still return 200, dashboards stay green, and the data flowing through is quietly wrong. The contract has broken silently, and the only place it can break is in the documentation that was supposed to describe the contract.&lt;/p&gt;

&lt;p&gt;This post is about the four predictabilities, where they leak, and why your docs are the only place to catch the leak before a regulator does.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Contract, in Four Parts
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Part 1: a predictable SHAPE for the data
Part 2: a predictable PERMISSION flow
Part 3: a predictable AUTHORIZATION credential
Part 4: a predictable ERROR vocabulary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the entire open banking contract from a developer's point of view.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://standards.openbanking.org.uk/api-specifications/" rel="noopener noreferrer"&gt;UK Open Banking Standard&lt;/a&gt; and the &lt;a href="https://www.eba.europa.eu/regulation-and-policy/payment-services-and-electronic-money/regulatory-technical-standards-on-strong-customer-authentication-and-secure-communication-under-psd2" rel="noopener noreferrer"&gt;PSD2 RTS in the EU&lt;/a&gt; both encode the same four parts in different words. Your bank's docs are how you, the developer building on top of the bank, learn what shape, what flow, what credential, and what error codes the bank chose inside the room the regulation gave it.&lt;/p&gt;

&lt;p&gt;When the docs and the running API agree, the contract holds. When they disagree, here is what breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drift 1: The Shape Drifts
&lt;/h2&gt;

&lt;p&gt;Documented response has 12 fields. Production now returns 14.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Documented&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transaction_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"booked_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"counterparty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;more&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;documented&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;fields&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Production&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;today&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transaction_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"booked_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"counterparty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"merchant_category_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5814"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;undocumented&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scheme_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FPS-2026-..."&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;undocumented&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;more&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;documented&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;fields&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app reading this ignores the two unknown fields. It still works. But the budgeting category it shows the customer is now derived from a heuristic the app wrote two years ago, when the actual merchant category code is sitting unused in the response. The app cannot use what the docs do not say is there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drift 2: The Permission Drifts
&lt;/h2&gt;

&lt;p&gt;Docs list 5 consent scopes. Production added a 6th, required for the standing-orders endpoint.&lt;/p&gt;

&lt;p&gt;The app requests the documented 5. The bank's authorization server returns a token. The token works for the four endpoints the app uses. The standing-orders endpoint returns a 200 with an empty array.&lt;/p&gt;

&lt;p&gt;The app interprets "empty" as "this customer has no standing orders." The customer has six. They call support to ask why their rent payment is missing.&lt;/p&gt;

&lt;p&gt;Empty data and missing-permission data look identical to a client unless the docs say otherwise. The docs were the only place the new scope could have been announced.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drift 3: The Credential Drifts
&lt;/h2&gt;

&lt;p&gt;Docs describe OAuth 2 with bearer tokens. Production migrated higher-value endpoints to mutual TLS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /accounts/{id}/balance       → bearer token works
POST /payments                   → mTLS required, bearer token = TLS handshake error
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app, holding only a bearer token, can read balances fine. The first time the customer initiates a payment, it fails with a TLS error the app never expected. The on-call engineer spends two hours hunting a network problem that does not exist. The actual problem is that the docs described one credential and production now requires two.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drift 4: The Errors Drift
&lt;/h2&gt;

&lt;p&gt;Docs say &lt;code&gt;429 Too Many Requests&lt;/code&gt; means "you are sending requests too fast."&lt;/p&gt;

&lt;p&gt;Production has started returning 429 for a second case: "the customer revoked consent in the last 60 seconds and our consent cache is still warm."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# What the docs imply
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retry_after&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three retries later, the bank's actual rate limiter trips and bans the app's IP for an hour. One customer revoked consent. Now the app cannot serve any of its customers using that bank, because 429 grew a second meaning the docs never described.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Docs Are the Only Place to Catch This
&lt;/h2&gt;

&lt;p&gt;A test environment catches a broken endpoint. Monitoring catches an outage. A regulator catches a serious incident, eventually.&lt;/p&gt;

&lt;p&gt;None of them catch silent contract drift. Silent drift looks identical to a healthy system from every observation point except one: a developer reading the docs and finding they describe a different system than the one answering when called.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.postman.com/state-of-api/" rel="noopener noreferrer"&gt;Postman 2025 State of the API Report&lt;/a&gt; found 55% of teams cite documentation gaps as their top collaboration challenge. In open banking, "collaboration" includes the regulator. The FCA and EBA both require documented behavior to match production behavior, because a third-party app cannot comply with rules it cannot read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Rules That Follow
&lt;/h2&gt;

&lt;p&gt;If the docs are the contract surface, then changes to the API are changes to the contract, and:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The schema in the docs is the schema in production.&lt;/strong&gt; Generated reference docs are not a nice-to-have in this niche. Hand-edited reference is a contract risk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every consent scope and every error code is documented before it is enforced.&lt;/strong&gt; If a scope becomes required Tuesday, the docs say so Monday.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deprecations ship with a date and an alternative.&lt;/strong&gt; Silent deprecations are silent contract breaks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the whole post in three lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  The One-Sentence Version
&lt;/h2&gt;

&lt;p&gt;An open banking API is a four-part contract, the docs are the contract, and when the two disagree the only people who notice are the customers calling support to ask why their money looks wrong.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://ekline.io/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=fintech&amp;amp;utm_content=open-banking-contract" rel="noopener noreferrer"&gt;EkLine&lt;/a&gt; checks every doc change against the live spec, the consent model, and the published error catalog so the contract surface stays honest.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>What Wrong API Docs Cost Identity Verification Teams</title>
      <dc:creator>EkLine.io</dc:creator>
      <pubDate>Tue, 12 May 2026 19:29:30 +0000</pubDate>
      <link>https://dev.to/ekline/what-wrong-api-docs-cost-identity-verification-teams-bj8</link>
      <guid>https://dev.to/ekline/what-wrong-api-docs-cost-identity-verification-teams-bj8</guid>
      <description>&lt;p&gt;You have been there. You are integrating a KYC endpoint, the docs say to pass three watchlist sources, your request gets a clean 200 back, and everything looks fine. Two months later, compliance flags the integration because a fourth watchlist source became required and nobody updated the docs. The API never rejected the request. It just silently ran an incomplete screening.&lt;/p&gt;

&lt;p&gt;This is not a hypothetical. It is the kind of thing that costs identity verification teams real money.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers Behind the Problem
&lt;/h2&gt;

&lt;p&gt;Global identity fraud costs now &lt;a href="https://snappt.com/blog/identity-fraud-statistics/" rel="noopener noreferrer"&gt;exceed $50 billion annually&lt;/a&gt;. Synthetic identity fraud alone accounts for &lt;a href="https://www.biia.com/synthetic-identity-fraud-statistics-2026-hard-numbers-big-threats/" rel="noopener noreferrer"&gt;$20 to $40 billion in global losses&lt;/a&gt; each year. In the first half of 2025, &lt;a href="https://www.biia.com/synthetic-identity-fraud-statistics-2026-hard-numbers-big-threats/" rel="noopener noreferrer"&gt;8.3% of all digital account creations&lt;/a&gt; were flagged as suspected fraud.&lt;/p&gt;

&lt;p&gt;Identity verification APIs are the front line. Platforms like Socure's ID+ platform, Jumio, and Alloy let teams call a single endpoint for KYC checks, fraud scoring, document verification, and watchlist screening. But the defense only works if the integration is correct. And the integration is only as correct as the documentation guiding it.&lt;/p&gt;

&lt;p&gt;Here is where things break down.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Compliance Fines from Stale Docs
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.archbee.com/blog/build-fully-compliant-fintech-api-documentation-or-pay-the-price" rel="noopener noreferrer"&gt;Sixty percent of fintechs&lt;/a&gt; paid at least $250,000 in compliance fines last year. &lt;a href="https://www.archbee.com/blog/build-fully-compliant-fintech-api-documentation-or-pay-the-price" rel="noopener noreferrer"&gt;Ninety-three percent&lt;/a&gt; struggle to satisfy compliance requirements. In many of those cases, the root cause was not a code bug. It was stale documentation.&lt;/p&gt;

&lt;p&gt;Consider this request body for a multi-module identity check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"modules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"kyc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fraud"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"watchlist"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"consentObtained"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"watchlistSources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ofac"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pep"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"adverse_media"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;watchlistSources&lt;/code&gt; was recently expanded to require &lt;code&gt;sanctions_consolidated&lt;/code&gt; but the docs still list the old three values, every integration built from those docs is running an incomplete screening. The API returns 200. The check "passes." But the compliance record is now inaccurate.&lt;/p&gt;

&lt;p&gt;PCI DSS requires documentation of data flows and security controls. KYC/AML regulations require auditable verification trails. When the docs describing these flows do not match the running code, the audit trail has a hole in it. And auditors are very good at finding holes.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Integration Delays That Multiply
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.postman.com/state-of-api/2025/" rel="noopener noreferrer"&gt;Postman 2025 State of the API Report&lt;/a&gt; found that &lt;strong&gt;55% of API teams cite documentation gaps as their top collaboration challenge&lt;/strong&gt;. In identity verification, the impact is amplified because a single integration typically touches multiple modules at once.&lt;/p&gt;

&lt;p&gt;A typical identity verification flow looks like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Module&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;th&gt;What Breaks with Bad Docs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Identity Resolution&lt;/td&gt;
&lt;td&gt;Matches user data to authoritative sources&lt;/td&gt;
&lt;td&gt;Wrong field mappings, missed required params&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fraud Scoring&lt;/td&gt;
&lt;td&gt;Returns risk scores with reason codes&lt;/td&gt;
&lt;td&gt;Misinterpreted score ranges, stale thresholds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Document Verification&lt;/td&gt;
&lt;td&gt;Validates government IDs&lt;/td&gt;
&lt;td&gt;Unsupported doc types silently fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Watchlist Screening&lt;/td&gt;
&lt;td&gt;Checks OFAC, PEP, adverse media&lt;/td&gt;
&lt;td&gt;Incomplete source lists, missed screening&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decision Engine&lt;/td&gt;
&lt;td&gt;Routes pass/fail/review decisions&lt;/td&gt;
&lt;td&gt;Logic built on outdated score interpretations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When a developer hits an undocumented edge case in one module, the entire integration stalls. And organizations using well-documented, API-driven verification processes &lt;a href="https://dashdevs.com/blog/how-to-choose-a-kyc-provider-onfido-veriff-plaid/" rel="noopener noreferrer"&gt;reduced compliance costs by 47%&lt;/a&gt; and improved verification accuracy by 31%, according to the KYC Benchmark Report. The difference is not the technology. It is whether the team integrating the technology can trust what the docs say.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Fraud That Slips Through Undocumented Changes
&lt;/h2&gt;

&lt;p&gt;Identity verification APIs evolve constantly. New fraud vectors emerge, scoring models get retrained, risk thresholds shift. When those changes ship without corresponding documentation updates, downstream teams do not adjust their decision logic.&lt;/p&gt;

&lt;p&gt;Socure's Sigma Identity Fraud module can &lt;a href="https://developer.socure.com/" rel="noopener noreferrer"&gt;capture up to 90% of fraud in the riskiest 3%&lt;/a&gt;. That precision depends on integrators correctly interpreting score ranges, reason codes, and threshold recommendations. If a model update shifts what a score of 0.7 means, but the docs still describe the old interpretation, fraud teams will make decisions on stale logic.&lt;/p&gt;

&lt;p&gt;Financial services fraud losses reached &lt;a href="https://www.security.org/identity-theft/statistics/" rel="noopener noreferrer"&gt;$12.5 billion in 2024&lt;/a&gt;, a 25% increase over the previous year. Not all of that traces to documentation. But every undocumented API change creates a window where fraud scoring, decision routing, or compliance checks may operate on wrong assumptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Documentation Stack That Actually Matters
&lt;/h2&gt;

&lt;p&gt;Identity verification APIs carry a heavier documentation burden than most fintech APIs. The minimum viable docs must cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authentication and scopes&lt;/strong&gt;: Which API keys access which modules, what permissions each scope grants&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PII handling&lt;/strong&gt;: How personal data is encrypted in transit, masked in logs, retained or purged per regulation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance mappings&lt;/strong&gt;: Which endpoints satisfy which regulatory requirements (KYC, AML, OFAC screening)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versioning and deprecation&lt;/strong&gt;: When endpoints change, what breaks, and how to migrate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error codes with remediation&lt;/strong&gt;: Not just &lt;code&gt;400 Bad Request&lt;/code&gt; but &lt;code&gt;missing required field: consentObtained, see migration guide v4.2&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When any layer in this stack drifts from the running code, the cost shows up as a compliance finding, a failed integration, or a fraud event that should have been caught.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treating Docs Like Code
&lt;/h2&gt;

&lt;p&gt;The teams that avoid these costs treat documentation the way they treat code: it gets tested in CI, it gets reviewed in PRs, and it gets flagged when it drifts from the source of truth.&lt;/p&gt;

&lt;p&gt;Here is what that looks like as a linting rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Example: CI rule for identity verification docs&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kyc-endpoint-versioning&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;KYC endpoints must document version and deprecation date&lt;/span&gt;
    &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/v[0-9]+/(kyc|verify|screening)"&lt;/span&gt;
    &lt;span class="na"&gt;require&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;version_header&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;deprecation_notice&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;migration_guide_link&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running documentation checks in the same pipeline that tests the API means a new endpoint parameter cannot ship without a corresponding docs update. A deprecated field cannot linger in production docs past its removal date. A compliance-critical module cannot change its behavior without a changelog entry.&lt;/p&gt;

&lt;p&gt;This is not a tooling problem. It is a workflow problem. The documentation review needs to sit in the same pipeline as the code review, not in a separate wiki that gets updated "when someone has time."&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Identity verification documentation is not a developer convenience. It is a compliance control, a fraud prevention layer, and an integration reliability guarantee.&lt;/p&gt;

&lt;p&gt;When the docs are wrong, the integration is wrong. When the integration is wrong, the verification is incomplete. And when the verification is incomplete, the cost shows up as a fine, a fraud loss, or a partner who takes their integration timeline from weeks to months.&lt;/p&gt;

&lt;p&gt;If your identity verification docs drift between releases, &lt;a href="https://ekline.io/blog/what-wrong-api-docs-cost-identity-verification-teams?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=fintech&amp;amp;utm_content=wrong-api-docs-cost-identity-verification" rel="noopener noreferrer"&gt;EkLine&lt;/a&gt; catches the gap before your auditor does.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What is the worst documentation gap you have hit during an API integration?&lt;/strong&gt; Stale field names, missing error codes, undocumented breaking changes? Drop your war stories in the comments.&lt;/p&gt;

</description>
      <category>api</category>
      <category>cybersecurity</category>
      <category>security</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Your Support Tickets Are a Documentation Backlog in Disguise</title>
      <dc:creator>EkLine.io</dc:creator>
      <pubDate>Fri, 08 May 2026 14:32:29 +0000</pubDate>
      <link>https://dev.to/ekline/your-support-tickets-are-a-documentation-backlog-in-disguise-ekj</link>
      <guid>https://dev.to/ekline/your-support-tickets-are-a-documentation-backlog-in-disguise-ekj</guid>
      <description>&lt;p&gt;You know the feeling. You are deep in a debugging session, finally making progress on that memory leak, and a Slack notification pulls you out. A customer cannot figure out how to configure SSO with your product. You write a detailed, thoughtful response. You resolve the ticket. You go back to your code. Three days later, a different customer asks the exact same question. A different engineer writes a slightly different answer. The docs never change.&lt;/p&gt;

&lt;p&gt;This is not a support problem. It is a documentation deployment problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers
&lt;/h2&gt;

&lt;p&gt;The cost gap between support-assisted and self-service resolution is wide:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resolution Channel&lt;/th&gt;
&lt;th&gt;Cost Per Ticket&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Agent-assisted (B2B avg)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.lorikeetcx.ai/articles/customer-service-cost-per-ticket" rel="noopener noreferrer"&gt;$18 - $35&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-service / docs&lt;/td&gt;
&lt;td&gt;&lt;a href="https://livechatai.com/blog/customer-support-cost-benchmarks" rel="noopener noreferrer"&gt;$1 - $4&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If 30% of your tickets are questions your docs should already answer, you are spending thousands per quarter on a problem that documentation updates would eliminate. Companies that systematically turn support interactions into documentation updates &lt;a href="https://www.supportbench.com/feedback-loops-customer-success-can-influence-support-documentation/" rel="noopener noreferrer"&gt;reduce ticket volume by 20 to 30%&lt;/a&gt; on the topics they address.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Broken Loop
&lt;/h2&gt;

&lt;p&gt;Support platforms like Pylon, Intercom, Plain, and Thena are good at managing conversations. Tagging, routing, SLA tracking, AI summaries. What they have not solved is the feedback loop: when a ticket reveals a documentation gap, who updates the docs?&lt;/p&gt;

&lt;p&gt;Here is what the ticket lifecycle looks like in most organizations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Customer hits a documentation gap&lt;/li&gt;
&lt;li&gt;Customer files a support ticket&lt;/li&gt;
&lt;li&gt;Support agent writes a one-off answer&lt;/li&gt;
&lt;li&gt;Ticket closes&lt;/li&gt;
&lt;li&gt;Documentation stays unchanged&lt;/li&gt;
&lt;li&gt;Next customer hits the same gap&lt;/li&gt;
&lt;li&gt;&lt;code&gt;goto 2&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://hbr.org/2017/01/kick-ass-customer-service" rel="noopener noreferrer"&gt;Harvard Business Review research&lt;/a&gt; found that reducing customer effort is the single strongest driver of loyalty. Yet most documentation teams operate reactively, fixing pages only after complaints pile up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Components of the Fix
&lt;/h2&gt;

&lt;p&gt;The support-to-docs loop has three components. Most teams have the first, some have the second, almost none have the third.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Signal detection:&lt;/strong&gt; Identifying which tickets point to documentation gaps. Pylon surfaces conversation patterns across Slack channels. Intercom and Plain offer similar tagging and clustering. The tooling exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Prioritization:&lt;/strong&gt; Not every ticket is a docs gap. Some are bugs, some are feature requests, some are edge cases affecting one customer. Filter for: which questions repeat, which affect onboarding, which block self-serve adoption?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Action:&lt;/strong&gt; This is where it breaks down. Someone needs to take the support answer, restructure it for a public audience, verify it against the current product state, and ship it as a documentation update. That "someone" is usually nobody.&lt;/p&gt;

&lt;p&gt;Here is a triage template to connect signal to action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# support-to-docs triage template&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Documentation gap triage&lt;/span&gt;
&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;support_ticket_tagged_docs_gap&lt;/span&gt;
&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;classify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;missing_page&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;incomplete_steps&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;outdated_content&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;wrong_example&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;affected_page&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;URL&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;doc&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;that&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;needs&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;updating"&lt;/span&gt;
      &lt;span class="na"&gt;frequency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;How&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;many&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;times&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;this&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;question&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;appeared&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;last&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;30&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;days"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;prioritize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;score&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frequency * customer_tier_weight&lt;/span&gt;
      &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;  &lt;span class="c1"&gt;# Act on anything asked 3+ times&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;if_missing_page&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Create&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;new&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;doc&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;support&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;template"&lt;/span&gt;
      &lt;span class="na"&gt;if_incomplete&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Add&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;missing&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;steps&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ticket&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;resolution"&lt;/span&gt;
      &lt;span class="na"&gt;if_outdated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Flag&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;engineering&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;review&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;update"&lt;/span&gt;
      &lt;span class="na"&gt;if_wrong_example&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Replace&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;verified&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;working&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;example"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A Real Example: P0 Security
&lt;/h2&gt;

&lt;p&gt;P0 Security had 13+ cloud integrations, each requiring step-by-step guides for configuring just-in-time access controls. Their documentation was sparse: &lt;a href="https://ekline.io/case-study/p0-security" rel="noopener noreferrer"&gt;76 commits across 28 months&lt;/a&gt;, averaging 2.7 commits per month. When docs were incomplete, customers filed tickets, and engineers lost 3 to 4 hours per guide writing documentation from scratch.&lt;/p&gt;

&lt;p&gt;They did not solve this by hiring a technical writer. They inserted a documentation layer into the existing PR workflow. Documentation PRs were created alongside feature PRs. Engineers went from writing docs (3 to 4 hours each) to reviewing docs (15 minutes each).&lt;/p&gt;

&lt;p&gt;The results:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Docs commits per month&lt;/td&gt;
&lt;td&gt;2.7&lt;/td&gt;
&lt;td&gt;42 merged PRs in the engagement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engineer time per guide&lt;/td&gt;
&lt;td&gt;3-4 hours writing&lt;/td&gt;
&lt;td&gt;15 min reviewing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authoring hours&lt;/td&gt;
&lt;td&gt;110 hours&lt;/td&gt;
&lt;td&gt;10 hours of review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to merge (docs PRs)&lt;/td&gt;
&lt;td&gt;Weeks&lt;/td&gt;
&lt;td&gt;Under 1 day (median)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Six engineers rotated through reviews, contributing an average of 5 comments per reviewed PR, with some reaching 14 to 16 comments of substantive technical feedback. P0 Security's CTO, Greg Vishnepolsky, put it plainly: "On customer calls now, we can just say, 'look at our docs.' That's new for us."&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters for Answer Engines
&lt;/h2&gt;

&lt;p&gt;There is a third reason to care beyond cost and onboarding speed. &lt;a href="https://www.frase.io/blog/what-is-answer-engine-optimization-the-complete-guide-to-getting-cited-by-ai" rel="noopener noreferrer"&gt;AI referral traffic to websites grew 527% year-over-year through mid-2025&lt;/a&gt;. The content that gets cited by AI answer engines is specific, structured, and fresh.&lt;/p&gt;

&lt;p&gt;Documentation that answers the exact question a customer asked in a support ticket is precisely the content these engines prefer to surface. Your support tickets are literally telling you what to publish for maximum AI visibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start This Week
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Export your top 20 support tickets from last month&lt;/li&gt;
&lt;li&gt;Group them by documentation page&lt;/li&gt;
&lt;li&gt;If 3+ tickets point to the same gap, that is your first update&lt;/li&gt;
&lt;li&gt;Take the support team's best answer for each cluster&lt;/li&gt;
&lt;li&gt;Restructure it: direct answer first, steps below, context at the bottom&lt;/li&gt;
&lt;li&gt;Publish it as a documentation update, not a blog post&lt;/li&gt;
&lt;li&gt;Tag future tickets that could have been self-serve, measure monthly&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The data is already in your ticketing system. The answers are already written. The only missing piece is the workflow that turns one into the other.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What does your support-to-docs feedback loop look like?&lt;/strong&gt; Does your team have a process for turning repeated support tickets into documentation updates, or does the knowledge stay locked in your ticketing system? I would love to hear what has worked (or not worked) for your team.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://ekline.io/blog/your-support-tickets-are-a-documentation-backlog-in-disguise?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=support-to-docs&amp;amp;utm_content=support-tickets-documentation-backlog" rel="noopener noreferrer"&gt;ekline.io&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>management</category>
      <category>productivity</category>
      <category>saas</category>
    </item>
    <item>
      <title>Why Your Fintech API Code Examples Are a Liability</title>
      <dc:creator>EkLine.io</dc:creator>
      <pubDate>Thu, 12 Mar 2026 14:34:28 +0000</pubDate>
      <link>https://dev.to/ekline/why-your-fintech-api-code-examples-are-a-liability-4afb</link>
      <guid>https://dev.to/ekline/why-your-fintech-api-code-examples-are-a-liability-4afb</guid>
      <description>&lt;p&gt;A developer copies a code example from your API docs. Changes the account ID. Updates the amount. Hits send.&lt;/p&gt;

&lt;p&gt;The money moves. To the wrong account. Using a deprecated endpoint your docs still show as valid.&lt;/p&gt;

&lt;p&gt;In a social media API, a wrong code example posts the wrong image. Embarrassing. In a financial services API, a wrong code example moves money. And &lt;code&gt;Ctrl+Z&lt;/code&gt; is not a feature that banking infrastructure supports.&lt;/p&gt;

&lt;p&gt;Wrong API examples in fintech aren't documentation bugs. They're operational incidents waiting to happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1sxrwf5g43uydii0ayfi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1sxrwf5g43uydii0ayfi.png" alt=" " width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stat&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;75% of production APIs have variances from their OpenAPI specs&lt;/td&gt;
&lt;td&gt;Nordic APIs / APIContext&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;70% of devs rate code examples as the #1 most important doc component&lt;/td&gt;
&lt;td&gt;SmartBear 2020 State of API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;55% of teams struggle with inconsistent/outdated documentation&lt;/td&gt;
&lt;td&gt;Postman 2025 State of the API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;83% of developers consider doc quality when evaluating an API&lt;/td&gt;
&lt;td&gt;Monetizely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;71% have chosen one API over another because of better docs&lt;/td&gt;
&lt;td&gt;Monetizely&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three-quarters of APIs don't match their own docs. The thing developers trust most is code examples. And doc quality is the deciding factor for most adoption decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  3 Ways Code Examples Silently Break
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqrh5gi3gfpclakmryp3j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqrh5gi3gfpclakmryp3j.png" alt=" " width="799" height="286"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The endpoint changes. The example doesn't.
&lt;/h3&gt;

&lt;p&gt;Payments team ships V3 of the transfers endpoint. API reference gets updated. The four code examples in tutorials still point to V2. V2 still works — for now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;# Your docs still show this:
POST /v2/transfers

# Your API is actually on:
POST /v3/transfers
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Developer builds an entire integration on a deprecated path.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. A required field gets added. The example lies.
&lt;/h3&gt;

&lt;p&gt;Compliance mandates a new &lt;code&gt;purpose_code&lt;/code&gt; field on international transfers. The API rejects requests without it. But the quickstart guide doesn't include it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;What&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;docs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;show:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"destination"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acct_xxx"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;What&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;API&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;actually&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;requires:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"destination"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acct_xxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"purpose_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"P0101"&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;without&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Default values change. Silently.
&lt;/h3&gt;

&lt;p&gt;Your API defaults &lt;code&gt;settlement_speed&lt;/code&gt; from &lt;code&gt;"standard"&lt;/code&gt; (T+1) to &lt;code&gt;"instant"&lt;/code&gt;. The code example doesn't specify the field because "the default is fine."&lt;/p&gt;

&lt;p&gt;Every developer copying that example now gets instant settlement — with different fees — and doesn't know it.&lt;/p&gt;

&lt;p&gt;This is the one that should terrify you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real-World Cost
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffxlx5gufywa1yvqh8xpw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffxlx5gufywa1yvqh8xpw.png" alt=" " width="799" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This isn't theoretical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PayPal&lt;/strong&gt; had endpoints listed in docs that didn't exist, undocumented webhook delays, and session timeouts merchants discovered through trial and error — in production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Healthcare:&lt;/strong&gt; A deprecated SOAP endpoint remained accessible for 6 months while vulnerabilities were only patched in newer REST services. 450,000 patient records exposed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Financial services:&lt;/strong&gt; Average $300,000+ per hour in API-related downtime costs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failed payments&lt;/strong&gt; cost the global economy $118.5 billion annually&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Write Once, Pray Forever
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgjj8xi8pa2zmjixaomgd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgjj8xi8pa2zmjixaomgd.png" alt=" " width="800" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's how fintech API documentation actually works in practice: An engineer writes a feature, someone writes the docs and code examples, and on the day they're written, everything is accurate. Then the feature evolves over 6-12 months. The API reference gets updated — maybe. But the code examples scattered across guides and tutorials? Almost certainly not.&lt;/p&gt;

&lt;p&gt;76% of failed API integrations result from inadequate documentation or support.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Stripe, Twilio, and Plaid Do
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh8vm9ze6xyeyuvmvqevn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh8vm9ze6xyeyuvmvqevn.png" alt=" " width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stripe:&lt;/strong&gt; SDK generation pipeline built on Ruby DSL → OpenAPI specs → auto-generated library code in multiple languages. They also built and open-sourced &lt;a href="https://markdoc.dev" rel="noopener noreferrer"&gt;Markdoc&lt;/a&gt;. Interactive examples see 62% higher engagement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Twilio:&lt;/strong&gt; Migrated 5,000+ pages with Yoyodyne. Samples update automatically when API or codegen tool changes. Zero manual sync.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plaid:&lt;/strong&gt; Discovered developers bypassed navigation for search. Expanded their search index by hundreds of entries. Behavior data drives docs improvement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common thread:&lt;/strong&gt; They treat code examples as testable code, not documentation text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your 4-Week Safety Net
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0vx07p5l9uarecdwummc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0vx07p5l9uarecdwummc.png" alt=" " width="800" height="561"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Week 1: Audit your quickstart. Fresh environment. Run every example.
Week 2: Fix the broken quickstart examples. Highest traffic first.
Week 3: Add example testing to CI. One file, one test. Fail the build.
Week 4: Expand to top 5 integration guides. Set up freshness tracking.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tools That Help
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://pact.io" rel="noopener noreferrer"&gt;Pact&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Contract testing — catches breaking API changes before production&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vale.sh" rel="noopener noreferrer"&gt;Vale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Open source prose linter for style guide enforcement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://ekline.io" rel="noopener noreferrer"&gt;EkLine&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Managed docs automation — style, links, terminology on every PR in CI/CD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://stoplight.io/spectral" rel="noopener noreferrer"&gt;Spectral&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;OpenAPI linting — catches spec drift&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;In most software, documentation that's 95% accurate is fine. In financial services, the 5% that's wrong is where someone loses money.&lt;/p&gt;

&lt;p&gt;Outdated docs isn't a typo. It's a bug. In fintech, that bug moves money.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;We'll be at Fintech Meetup at the end of March. If any of this resonated, let's connect. &lt;a href="https://ekline.io" rel="noopener noreferrer"&gt;ekline.io&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>documentation</category>
      <category>softwareengineering</category>
      <category>fintech</category>
    </item>
    <item>
      <title>Docs-as-Code: The Complete CI/CD Workflow (From Git to Production)</title>
      <dc:creator>EkLine.io</dc:creator>
      <pubDate>Tue, 03 Mar 2026 18:07:30 +0000</pubDate>
      <link>https://dev.to/ekline/docs-as-code-the-complete-cicd-workflow-from-git-to-production-89b</link>
      <guid>https://dev.to/ekline/docs-as-code-the-complete-cicd-workflow-from-git-to-production-89b</guid>
      <description>&lt;p&gt;You know what docs-as-code is. I'm not going to explain it for the 900th time. Store docs in Git, write in Markdown, review through PRs, deploy through CI/CD. Got it. Moving on.&lt;/p&gt;

&lt;p&gt;What nobody seems to write about is what a working pipeline actually looks like. The real config files, the real tradeoffs, the stuff that took us three iterations to get right. Every "docs-as-code guide" I've read either stops at the philosophy or gives you a toy example that falls apart the moment you have more than five pages.&lt;/p&gt;

&lt;p&gt;This is the actual pipeline. Config files you can copy. Decisions explained so you understand &lt;em&gt;why&lt;/em&gt;, not just &lt;em&gt;what&lt;/em&gt;. Opinions included at no extra charge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repository Structure
&lt;/h2&gt;

&lt;p&gt;Start with a layout that scales. Here's what works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;your-product/
├── docs/
│   ├── getting-started/
│   │   ├── quickstart.md
│   │   ├── installation.md
│   │   └── first-project.md
│   ├── guides/
│   │   ├── authentication.md
│   │   ├── configuration.md
│   │   └── deployment.md
│   ├── api-reference/
│   │   ├── endpoints.md
│   │   └── error-codes.md
│   ├── assets/
│   │   └── images/
│   └── index.md
├── .github/
│   └── workflows/
│       └── docs.yml
├── .vale.ini              # Style linting config
├── .lychee.toml           # Link checker config
├── cspell.json            # Spell checker config
└── .ekline/
    └── config.yaml        # EkLine config (if using)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, before you blindly copy this, let me explain the decisions, because they matter more than the structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docs live in the same repo as code. Not a separate repo.&lt;/strong&gt; This is the hill I will die on. When an engineer changes the authentication flow, the docs for authentication should be right there, in the same PR. Separate repos create a gap between "code changed" and "docs should change". That gap is where documentation debt lives. Every guide that tells you to use a separate docs repo is optimizing for the docs team's convenience at the expense of accuracy.&lt;/p&gt;

&lt;p&gt;If your docs are already in a separate repo, don't panic. Everything in this guide still applies. But if you're starting fresh, co-locate them. You'll thank me when someone changes an API endpoint and the docs PR is part of the same review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Group by user task, not by content type.&lt;/strong&gt; &lt;code&gt;getting-started/&lt;/code&gt; is a user journey. &lt;code&gt;api-reference/&lt;/code&gt; is a content type. I'd love to tell you I'm perfectly consistent about this, but the truth is most real doc structures are a hybrid. The important thing: a new user should be able to look at the folder names and know where to go. If they can't, your IA needs work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Config files live at the root.&lt;/strong&gt; Not in a &lt;code&gt;.config/&lt;/code&gt; folder, not inside &lt;code&gt;docs/&lt;/code&gt;. Root level. Why? Because every tool expects them there by default, and fighting tool defaults is a waste of your finite time on earth.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Writing Workflow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Branching
&lt;/h3&gt;

&lt;p&gt;Treat docs PRs like code PRs. Same workflow, same rigor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; docs/update-auth-guide
&lt;span class="c"&gt;# write your changes&lt;/span&gt;
git add docs/guides/authentication.md
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Update auth guide for OAuth2 PKCE flow"&lt;/span&gt;
git push origin docs/update-auth-guide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a &lt;code&gt;docs/&lt;/code&gt; prefix for documentation branches. Two reasons: it makes filtering your PR list trivial, and you can apply branch-specific CI rules (like running docs checks only on &lt;code&gt;docs/*&lt;/code&gt; branches). Small thing, surprisingly useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Template
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;.github/PULL_REQUEST_TEMPLATE/docs.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## What changed&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- Brief description of documentation changes --&amp;gt;&lt;/span&gt;

&lt;span class="gu"&gt;## Why&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- What triggered this update? Feature change, user feedback, bug fix? --&amp;gt;&lt;/span&gt;

&lt;span class="gu"&gt;## Pages affected&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- List the doc pages modified --&amp;gt;&lt;/span&gt;

&lt;span class="gu"&gt;## Checklist&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Tested all code examples
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Verified all links work
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Screenshots are current (if applicable)
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Reviewed on mobile/responsive view
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The "Why" field is the most important part of this template, and the one people skip most often. "Updated the auth docs" tells a reviewer nothing. "Updated auth docs because we switched from API keys to OAuth2 in v3.2" tells them everything. It connects the docs change to the trigger, and six months from now when someone asks "why did we rewrite this page," the answer is right there in the PR.&lt;/p&gt;




&lt;h2&gt;
  
  
  Automated Quality Checks
&lt;/h2&gt;

&lt;p&gt;This is where docs-as-code stops being a philosophy and starts being a system. Every PR that touches documentation triggers a pipeline of automated checks. Here's each stage, with the configs we actually use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 1: Spell Checking
&lt;/h3&gt;

&lt;p&gt;The least glamorous check and the one that catches the most embarrassing mistakes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/docs.yml (spell check job)&lt;/span&gt;
&lt;span class="na"&gt;spell-check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;contains(github.event.pull_request.labels.*.name, 'documentation') ||&lt;/span&gt;
      &lt;span class="s"&gt;contains(join(github.event.pull_request.changed_files), 'docs/')&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;streetsidesoftware/cspell-action@v6&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;docs/**/*.md'&lt;/span&gt;
        &lt;span class="na"&gt;incremental_files_only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your &lt;code&gt;cspell.json&lt;/code&gt; needs a product-specific dictionary. Without one, CSpell will flag every product name, every technical term, and every acronym. After this, your engineers will disable the check within a week:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"words"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"your-product-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"OAuth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PKCE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webhook"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webhooks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"authn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"authz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kubectl"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ignorePaths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"docs/assets/**"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Start with &lt;code&gt;incremental_files_only: true&lt;/code&gt;. I cannot stress this enough. Running spell check on all files the first time will generate hundreds of findings. Nobody will fix them. The check will become noise. Check only changed files, build the dictionary organically, and clean up old files as you touch them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 2: Link Validation
&lt;/h3&gt;

&lt;p&gt;Broken links are the cockroaches of documentation. For every one you see, there are ten you don't:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Link check job&lt;/span&gt;
&lt;span class="na"&gt;link-check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;lycheeverse/lychee-action@v2&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
          &lt;span class="s"&gt;--no-progress&lt;/span&gt;
          &lt;span class="s"&gt;--exclude-mail&lt;/span&gt;
          &lt;span class="s"&gt;--exclude 'localhost'&lt;/span&gt;
          &lt;span class="s"&gt;--exclude '127.0.0.1'&lt;/span&gt;
          &lt;span class="s"&gt;--exclude 'example.com'&lt;/span&gt;
          &lt;span class="s"&gt;'docs/**/*.md'&lt;/span&gt;
        &lt;span class="na"&gt;fail&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lychee is fast (Rust-based, concurrent) and handles most edge cases. Your &lt;code&gt;.lychee.toml&lt;/code&gt; config handles the rest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;exclude&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"127.0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"example.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;accept&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="py"&gt;max_retries&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Accept 429 (rate limited) as OK.&lt;/strong&gt; Some sites aggressively rate-limit automated checkers. Without this, you'll get false failures from GitHub, npm, and basically any popular site. You'll chase ghosts for an hour before you figure out it's not your links that are broken but it's the target servers telling you to slow down. Ask me how I know.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 3: Style Guide Enforcement
&lt;/h3&gt;

&lt;p&gt;This is where consistency happens at scale. Two options, both legitimate:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A: Vale (DIY, full control)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Style lint job&lt;/span&gt;
&lt;span class="na"&gt;style-lint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;errata-ai/vale-action@reviewdog&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docs/&lt;/span&gt;
        &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github-pr-review&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option B: EkLine (managed — includes link checking and style in one action)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Docs review job&lt;/span&gt;
&lt;span class="na"&gt;docs-review&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ekline-io/ekline-github-action@v6&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;content_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./docs&lt;/span&gt;
        &lt;span class="na"&gt;ek_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.EKLINE_TOKEN }}&lt;/span&gt;
        &lt;span class="na"&gt;github_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
        &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github-pr-review&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full disclosure: EkLine is ours. The honest difference: Vale gives you granular control over every rule and costs nothing. EkLine bundles style, links, and terminology checking into one action and manages the rules for you. If you use EkLine, skip the separate link check job. That is already included. If you use Vale, keep Lychee for links.&lt;/p&gt;

&lt;p&gt;Pick based on your situation, not my sales pitch.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Full Pipeline
&lt;/h2&gt;

&lt;p&gt;Here's the complete workflow file. Copy this, adjust the paths, and you have a working docs-as-code pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Documentation Quality&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;docs/**'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*.md'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;!README.md'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;spell-check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Spell Check&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;streetsidesoftware/cspell-action@v6&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;docs/**/*.md'&lt;/span&gt;
          &lt;span class="na"&gt;incremental_files_only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="na"&gt;link-check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Link Validation&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;lycheeverse/lychee-action@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;--no-progress --exclude-mail 'docs/**/*.md'&lt;/span&gt;
          &lt;span class="na"&gt;fail&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="na"&gt;style-lint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Style Guide&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;errata-ai/vale-action@reviewdog&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docs/&lt;/span&gt;
          &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github-pr-review&lt;/span&gt;

  &lt;span class="c1"&gt;# Optional: build and preview&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build Preview&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;spell-check&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;link-check&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;style-lint&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;20'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three quality jobs run in parallel which is the point. The build step waits for all checks to pass before generating a preview. On most repos, the whole thing finishes in under a minute.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Human Review Process
&lt;/h2&gt;

&lt;p&gt;Here's the part that trips people up: automated checks don't replace human review. They change what humans review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the machine handles:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spelling errors&lt;/li&gt;
&lt;li&gt;Broken links&lt;/li&gt;
&lt;li&gt;Style guide violations (headings, terminology, voice, tone)&lt;/li&gt;
&lt;li&gt;Formatting consistency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What humans handle:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the content actually accurate?&lt;/li&gt;
&lt;li&gt;Is the explanation clear to someone who doesn't already understand it?&lt;/li&gt;
&lt;li&gt;Are the code examples correct and tested? (Machines can check syntax. They can't check if your example actually solves the problem it claims to.)&lt;/li&gt;
&lt;li&gt;Is anything missing?&lt;/li&gt;
&lt;li&gt;Does the information architecture make sense?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation is the real value of docs-as-code. Not "docs in Git". That's just storage. The value is this: when a reviewer opens a PR and the automated checks have already passed, they know the formatting is clean, the links work, and the style guide is followed. They can focus entirely on whether the content is &lt;em&gt;right&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That's a fundamentally different (and much more useful) review conversation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;Once a PR merges, deploy automatically. I'll keep this section short because deployment is well-documented elsewhere and the choices are straightforward:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Pages&lt;/strong&gt; (simplest, free):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/main'&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;peaceiris/actions-gh-pages@v3&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;github_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
        &lt;span class="na"&gt;publish_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Vercel/Netlify:&lt;/strong&gt; Connect your repo, get preview URLs on every PR, auto-deploy on merge. Both auto-detect Docusaurus, Next.js, Astro, and most doc frameworks. Setup takes about five minutes. The docs for both platforms cover this better than I can.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docs platforms (Mintlify, GitBook, ReadMe):&lt;/strong&gt; They have their own deployment pipelines: push to a branch, they rebuild. Your CI checks still run; they just validate before the platform builds.&lt;/p&gt;

&lt;p&gt;Pick one and move on. The deployment step is the least interesting part of this pipeline.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Mistakes (The Part You Actually Came Here For)
&lt;/h2&gt;

&lt;p&gt;If you've set up a docs-as-code pipeline before, you probably scrolled straight to this section. I respect that. Here's what goes wrong:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running all checks on all files from day one.&lt;/strong&gt; You will generate 500 findings on existing docs. Nobody will fix them. The checks will become noise that everyone ignores. This is the single most common mistake, and I've watched teams make it repeatedly. Start incremental by only checking changed files. Clean up existing docs gradually, file by file, as you touch them for other reasons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blocking PRs on warnings.&lt;/strong&gt; Set style violations to &lt;code&gt;warning&lt;/code&gt; for the first month. Let people see them, get used to the feedback loop, understand why the rules exist. Once the team accepts the system, upgrade critical rules to &lt;code&gt;error&lt;/code&gt;. If you start with &lt;code&gt;error&lt;/code&gt;, you'll get a revolt. I promise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Separate docs repo without a trigger.&lt;/strong&gt; If docs live in a separate repo from code, you need a way to signal "the product changed, docs might need updating." A webhook, a GitHub Action that runs on product repo changes, a Slack notification. Something. Anything. Without this signal, the docs repo slowly diverges from reality and nobody notices until a customer complains. This is how documentation debt starts: not with bad writing, but with missing signals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No code example testing.&lt;/strong&gt; The most common docs bug isn't a typo or a broken link. It's a code example that doesn't work. The developer copies your snippet, gets an error, and blames your product. Not the docs. If your docs include code snippets, consider extracting and testing them. Tools like &lt;a href="https://mdxjs.com/" rel="noopener noreferrer"&gt;mdx-js&lt;/a&gt; or custom scripts can help. At minimum, have someone actually run the examples before publishing. Yes, every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ignoring the contributor experience.&lt;/strong&gt; If your CI takes 10 minutes to run, contributors won't wait. They'll push, context-switch, forget, and your PR will go stale. Keep the docs pipeline under 2 minutes. Run checks in parallel. Use incremental checking. The fastest way to kill a docs-as-code culture is to make the feedback loop slow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Start Here
&lt;/h2&gt;

&lt;p&gt;If you're setting up docs-as-code from scratch, don't try to build the whole pipeline in a day. Here's the order that works:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 1:&lt;/strong&gt; Set up the repo structure. Add Lychee for link checking. This catches real problems immediately with zero configuration. You'll find broken links you didn't know existed. It's both satisfying and slightly alarming.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 2:&lt;/strong&gt; Add CSpell. Build your product dictionary as you go. Add words when they're flagged incorrectly. The first few days will be annoying. By day five, it's useful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 3:&lt;/strong&gt; Add style enforcement (Vale or EkLine). Start with a preset style guide. Warnings only. Do not start with errors. I will say this as many times as it takes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 4:&lt;/strong&gt; Review the findings from week 3. Tune the rules. Disable noisy ones, tighten important ones. Upgrade key rules from warning to error. Now you have a system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ongoing:&lt;/strong&gt; Clean up existing docs gradually. Don't create a "fix all style issues" ticket. That ticket will sit in your backlog for eternity and make everyone sad. Fix issues as you touch files for other reasons. This is how real documentation quality improves: incrementally, consistently, without heroics.&lt;/p&gt;

&lt;p&gt;The pipeline is never "done." It evolves with your docs, your team, and your style guide. The important thing is that it exists. And that it runs on every PR, quietly catching the stuff that used to require a human to notice.&lt;/p&gt;

&lt;p&gt;That's what docs-as-code actually means. Not "docs in Git." Docs with the same quality guarantees we give to code.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What does your docs pipeline look like? I'd love to compare setups — especially the weird edge cases. Drop a comment or find me at &lt;a href="https://ekline.io?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=docs-engineering-series" rel="noopener noreferrer"&gt;ekline.io&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>devops</category>
      <category>documentation</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>5 GitHub Actions That Save Our Docs Team Hours Every Week</title>
      <dc:creator>EkLine.io</dc:creator>
      <pubDate>Wed, 25 Feb 2026 16:22:29 +0000</pubDate>
      <link>https://dev.to/ekline/5-github-actions-that-save-our-docs-team-hours-every-week-jkc</link>
      <guid>https://dev.to/ekline/5-github-actions-that-save-our-docs-team-hours-every-week-jkc</guid>
      <description>&lt;p&gt;We're a small startup. And documentation is literally our product — we build tools to help other teams keep their docs accurate. So if our own docs have broken links, inconsistent terminology, or sloppy formatting, that's not just embarrassing. It's the kind of credibility problem that makes potential customers close the tab.&lt;/p&gt;

&lt;p&gt;Here's the thing nobody tells you about running a docs-focused company: you become absurdly paranoid about your own documentation. Every broken link feels personal. Every style inconsistency keeps you up at night. But we're a small team — we can't spend our days playing copy editor on every PR.&lt;/p&gt;

&lt;p&gt;So we automated the stuff that shouldn't require a human brain. Here are the five GitHub Actions that run on every docs PR in our repo. They're all free for open source, and together they catch the vast majority of issues that used to eat up our review cycles.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Catch Broken Links Before They Go Live
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem nobody wants to admit:&lt;/strong&gt; You write docs, link to other pages, API references, external resources. You feel good about it. Three months later, half those links are dead. Someone moved a page, a third-party site decided to restructure their entire URL scheme (thanks, I love re-doing work), or you linked to a branch that got merged and deleted.&lt;/p&gt;

&lt;p&gt;Broken links are the fastest way to make users distrust your documentation. One dead link and they start wondering what else is wrong. It's like finding a cockroach in a restaurant — you know there are more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tool:&lt;/strong&gt; &lt;a href="https://github.com/lycheeverse/lychee-action" rel="noopener noreferrer"&gt;Lychee&lt;/a&gt; — a Rust-based link checker that's fast enough to run on every PR without making you want to throw your laptop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check Links&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;docs/**'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*.md'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;link-check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check links&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;lycheeverse/lychee-action@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
            &lt;span class="s"&gt;--no-progress&lt;/span&gt;
            &lt;span class="s"&gt;--exclude-mail&lt;/span&gt;
            &lt;span class="s"&gt;'docs/**/*.md'&lt;/span&gt;
          &lt;span class="na"&gt;fail&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why Lychee and not the Node-based alternatives:&lt;/strong&gt; Speed. Lychee is written in Rust and checks links concurrently. On our docs repo (~200 pages), it finishes in under 30 seconds. We tried &lt;code&gt;markdown-link-check&lt;/code&gt; first — 3+ minutes. When you're running this on every PR, that difference matters. Nobody wants to wait for CI when they just fixed a typo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We learned this the hard way:&lt;/strong&gt; Add a &lt;code&gt;.lychee.toml&lt;/code&gt; config to exclude URLs that are expected to fail in CI. We spent a very confusing afternoon debugging "broken" links that were just localhost examples in code snippets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;exclude&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"127.0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"example.com"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Time saved:&lt;/strong&gt; About 2 hours per week of "why does this link 404" tickets. Which, honestly, were the most soul-crushing tickets to deal with.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Enforce Your Style Guide Automatically
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Your style guide says "use second person" and "prefer active voice." Your docs say "the configuration can be completed by the user." Everyone knows the rules. Nobody catches everything. Including you — especially at 4pm on a Friday.&lt;/p&gt;

&lt;p&gt;Style guide violations are like dishes in the sink. Nobody notices one. But after a month, your kitchen is a health hazard and nobody wants to cook. After a year, your docs read like they were written by 20 different people — because they were.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tool:&lt;/strong&gt; &lt;a href="https://github.com/errata-ai/vale-action" rel="noopener noreferrer"&gt;Vale&lt;/a&gt; — a prose linter with presets for Google, Microsoft, and other style guides. Think ESLint, but for words.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prose Lint&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;docs/**'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vale&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Vale lint&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;errata-ai/vale-action@reviewdog&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docs/&lt;/span&gt;
          &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github-pr-review&lt;/span&gt;
          &lt;span class="na"&gt;vale_flags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--minAlertLevel=warning"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key configuration is the &lt;code&gt;.vale.ini&lt;/code&gt; file in your repo root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;StylesPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;.vale/styles&lt;/span&gt;
&lt;span class="py"&gt;MinAlertLevel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;warning&lt;/span&gt;

&lt;span class="nn"&gt;[docs/*.md]&lt;/span&gt;
&lt;span class="py"&gt;BasedOnStyles&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;Google&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This uses Google's style guide out of the box. Want Microsoft's instead? Change &lt;code&gt;Google&lt;/code&gt; to &lt;code&gt;Microsoft&lt;/code&gt;. Want your own custom rules? Create YAML files in &lt;code&gt;.vale/styles/YourCompany/&lt;/code&gt;. Fair warning: you will go down a rabbit hole. It's kind of fun, though.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Honest caveat:&lt;/strong&gt; Vale is powerful but it has a learning curve. Writing custom rules is its own skill, and it took us a few iterations to get our config dialed in without drowning in false positives. If you want something that works out of the box with less yak-shaving, that's where tools like EkLine come in (see #5). But if you're the type who enjoys fine-grained control and building your own system, Vale is the gold standard. No question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time saved:&lt;/strong&gt; About 1-2 hours per week of leaving "please change this to active voice" comments. My least favorite type of review comment to leave, and probably everyone's least favorite to receive.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Kill Typos Without a Human Proofreader
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Traditional spell checkers and technical writing are mortal enemies. They flag &lt;code&gt;kubectl&lt;/code&gt;, &lt;code&gt;OAuth&lt;/code&gt;, &lt;code&gt;GraphQL&lt;/code&gt;, and every product name as errors. So people turn them off. And then actual typos ship. And then you're the company whose security doc says "authetication."&lt;/p&gt;

&lt;p&gt;(Don't laugh. We caught that one in a PR. Twice.)&lt;/p&gt;

&lt;p&gt;Documentation with typos reads as careless. And in code examples, a typo isn't just embarrassing — it means the example literally doesn't work. A developer copies your snippet, gets an error, and blames your product. Not the typo. Your product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tool:&lt;/strong&gt; &lt;a href="https://github.com/streetsidesoftware/cspell-action" rel="noopener noreferrer"&gt;CSpell&lt;/a&gt; — a spell checker that actually understands technical terminology, so it won't flag every third word in your API docs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Spell Check&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;docs/**'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cspell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Spell check&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;streetsidesoftware/cspell-action@v6&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;docs/**/*.md'&lt;/span&gt;
          &lt;span class="na"&gt;incremental_files_only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The magic is in &lt;code&gt;cspell.json&lt;/code&gt;, where you define your product dictionary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dictionaries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"tech-terms"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dictionaryDefinitions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tech-terms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".cspell/tech-terms.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"addWords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"words"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"EkLine"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ekline"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Docusaurus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mintlify"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GitBook"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"devops"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cicd"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ignorePaths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"node_modules/**"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"*.min.js"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Do this or you'll hate yourself:&lt;/strong&gt; Use &lt;code&gt;incremental_files_only: true&lt;/code&gt; to only check changed files. We made the mistake of running it on everything the first time. Hundreds of findings. Nobody wanted to touch it. It sat in a ticket for three weeks before we wised up and switched to incremental-only. Build the dictionary over time — don't try to boil the ocean.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time saved:&lt;/strong&gt; About 30 minutes per week, plus the priceless benefit of never shipping "authetication" in a security doc. Again.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Stop Docs PRs From Going Stale
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Someone opens a docs PR. Reviewer is busy. A week passes. The contributor moves on to something else. The PR sits for two months, accumulates merge conflicts, and eventually gets closed with a sad little "closing due to inactivity" comment.&lt;/p&gt;

&lt;p&gt;We've all been on both sides of this. It's demoralizing for contributors, and for small teams it creates this invisible drag — every time you open the PR list, you have to mentally re-process "is this still relevant?" for PRs that should have been closed weeks ago.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tool:&lt;/strong&gt; &lt;a href="https://github.com/actions/stale" rel="noopener noreferrer"&gt;Stale&lt;/a&gt; — GitHub's official action for managing inactive issues and PRs. Simple, effective, slightly passive-aggressive in the best way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Stale Docs PRs&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;9&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;MON'&lt;/span&gt;  &lt;span class="c1"&gt;# Every Monday at 9am&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stale&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/stale@v9&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;repo-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;stale-pr-message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="s"&gt;This PR has been inactive for 14 days.&lt;/span&gt;
            &lt;span class="s"&gt;If the changes are still needed, please rebase&lt;/span&gt;
            &lt;span class="s"&gt;and request a re-review. Otherwise, it will be&lt;/span&gt;
            &lt;span class="s"&gt;closed in 7 days.&lt;/span&gt;
          &lt;span class="na"&gt;stale-pr-label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;stale'&lt;/span&gt;
          &lt;span class="na"&gt;days-before-pr-stale&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14&lt;/span&gt;
          &lt;span class="na"&gt;days-before-pr-close&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;21&lt;/span&gt;
          &lt;span class="na"&gt;only-labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;documentation'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters more than it sounds:&lt;/strong&gt; Before we added this, our PR list was a graveyard. Eight open PRs, four of them from two months ago. Every Monday's standup included someone asking "should we close these?" and nobody committing to it. Now the bot handles it. The mental overhead just... disappeared.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One thing we got wrong at first:&lt;/strong&gt; Set &lt;code&gt;only-labels: 'documentation'&lt;/code&gt; so this only affects docs PRs. Code PRs have different lifecycle expectations. We didn't scope it initially and accidentally auto-closed a feature PR that was waiting on a design review. That was a fun conversation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time saved:&lt;/strong&gt; About 1 hour per week of PR grooming and "is this still needed?" purgatory.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Automated Docs Review With Inline PR Comments
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Actions #1-3 each solve one piece of the puzzle. But running three separate actions means three separate configurations, three sets of output to parse, and no single view of "how's this PR's documentation quality, overall?"&lt;/p&gt;

&lt;p&gt;What if one action could check your style guide, validate links, flag terminology issues, and give you a quality score — all as inline PR comments, right on the lines where the issues are?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tool:&lt;/strong&gt; &lt;a href="https://github.com/ekline-io/ekline-github-action" rel="noopener noreferrer"&gt;EkLine&lt;/a&gt; — full disclosure, this is ours. I'll keep this brief and honest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Documentation Review&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;docs/**'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*.md'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;docs-review&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EkLine Docs Review&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ekline-io/ekline-github-action@v6&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;content_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./docs&lt;/span&gt;
          &lt;span class="na"&gt;ek_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.EKLINE_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;github_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github-pr-review&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It analyzes changed files and leaves inline review comments directly on the PR — style guide compliance, link validity, terminology consistency, quality score per file. Not a wall of CI output. Comments on the exact lines where the issues are.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use EkLine vs. the DIY stack:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;DIY Stack&lt;/th&gt;
&lt;th&gt;EkLine&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Setup time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hours (configure each tool)&lt;/td&gt;
&lt;td&gt;Minutes (one action, managed rules)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Customization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full control over every rule&lt;/td&gt;
&lt;td&gt;Presets + custom config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free (all open source)&lt;/td&gt;
&lt;td&gt;Free for public repos, paid for private&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Separate CI logs per tool&lt;/td&gt;
&lt;td&gt;Unified inline PR comments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maintenance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;You maintain configs + updates&lt;/td&gt;
&lt;td&gt;Managed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Teams who want granular control&lt;/td&gt;
&lt;td&gt;Teams who want it working now&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We obviously eat our own dogfood — EkLine runs on every PR we open. But the honest truth is: if you enjoy building your own toolchain and you want total control, the DIY stack (Vale + Lychee + CSpell) is excellent. If you're a small team and you'd rather spend your time writing docs than configuring linters, that's what we built EkLine for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time saved:&lt;/strong&gt; About 3-4 hours per week of mechanical review work.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Combined Workflow
&lt;/h2&gt;

&lt;p&gt;You don't have to pick just one. Here's a minimal combined workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Docs Quality&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;docs/**'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*.md'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;spell-check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;streetsidesoftware/cspell-action@v6&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;docs/**/*.md'&lt;/span&gt;
          &lt;span class="na"&gt;incremental_files_only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="na"&gt;link-check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;lycheeverse/lychee-action@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;--no-progress --exclude-mail 'docs/**/*.md'&lt;/span&gt;
          &lt;span class="na"&gt;fail&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="na"&gt;docs-review&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ekline-io/ekline-github-action@v6&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;content_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./docs&lt;/span&gt;
          &lt;span class="na"&gt;ek_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.EKLINE_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;github_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github-pr-review&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The three jobs run in parallel. Most PR builds finish in under a minute. Contributors get feedback before a human reviewer even opens the PR.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Actually Looks Like in Practice
&lt;/h2&gt;

&lt;p&gt;Before we set this up, our docs PR review went like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Contributor opens PR&lt;/li&gt;
&lt;li&gt;Waits 1-3 days for reviewer (we're a small team, we're busy)&lt;/li&gt;
&lt;li&gt;Reviewer leaves 8 comments — 3 about style, 2 about links, 1 typo, 2 about actual content&lt;/li&gt;
&lt;li&gt;Contributor fixes, pushes again&lt;/li&gt;
&lt;li&gt;Reviewer re-reviews (another day)&lt;/li&gt;
&lt;li&gt;Merged on day 5&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Contributor opens PR&lt;/li&gt;
&lt;li&gt;Automated checks run in 60 seconds&lt;/li&gt;
&lt;li&gt;Contributor sees inline feedback, fixes mechanical issues themselves&lt;/li&gt;
&lt;li&gt;Reviewer sees clean PR, focuses on one question: "Is this content accurate and helpful?"&lt;/li&gt;
&lt;li&gt;Merged same day&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The biggest shift isn't time saved — it's &lt;strong&gt;who fixes what&lt;/strong&gt;. Contributors fix their own mechanical issues because the feedback is instant and specific. It's not a person telling them "you got this wrong." It's a bot, and somehow that stings less. Reviewers stop being the grammar police and start being the content experts they were hired to be.&lt;/p&gt;

&lt;p&gt;That's the real win. Not the hours. The sanity.&lt;/p&gt;




&lt;h2&gt;
  
  
  Start With One, Add More Later
&lt;/h2&gt;

&lt;p&gt;If you're starting from zero, don't set up all five on day one. You'll spend more time configuring tools than writing docs, and that defeats the entire point.&lt;/p&gt;

&lt;p&gt;Here's the order I'd recommend:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start with Lychee&lt;/strong&gt; (link checking). Broken links are the most common and most damaging docs issue. Basically zero configuration. Immediate value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add CSpell&lt;/strong&gt; once you've built a product dictionary. This takes about a week of "add to dictionary" clicks before it stops being annoying and starts being useful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add Vale or EkLine&lt;/strong&gt; when you have a style guide you actually want to enforce. If you don't have a style guide yet, pick Google's and customize from there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add Stale&lt;/strong&gt; when your PR backlog grows beyond what you can track in your head. For us, that was about PR number six.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each one is useful on its own. Together, they're a system. But a system you build up over time — not one you configure in a weekend and then never touch again (because that system will rot just like your docs).&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What's in your docs CI pipeline? We're always looking for new tools to try. Drop a comment or reach out — we genuinely want to know what's working for other teams.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;About the author:&lt;/strong&gt; Bipin works on documentation tooling at &lt;a href="https://ekline.io" rel="noopener noreferrer"&gt;EkLine&lt;/a&gt; — a small team building documentation quality tools. He's interested in how teams scale documentation quality without scaling review burden. Check out our public repos at &lt;a href="https://github.com/ekline-io/ekline-github-action" rel="noopener noreferrer"&gt;github.com/ekline-io/ekline-github-action&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>documentation</category>
    </item>
    <item>
      <title>Automating Documentation Review in Your CI/CD Pipeline</title>
      <dc:creator>EkLine.io</dc:creator>
      <pubDate>Fri, 13 Feb 2026 12:34:59 +0000</pubDate>
      <link>https://dev.to/ekline/automating-documentation-review-in-your-cicd-pipeline-goj</link>
      <guid>https://dev.to/ekline/automating-documentation-review-in-your-cicd-pipeline-goj</guid>
      <description>&lt;p&gt;You lint your code. You run tests. You check for security vulnerabilities.&lt;/p&gt;

&lt;p&gt;But what about your documentation?&lt;/p&gt;

&lt;p&gt;Most teams treat docs as second-class citizens in CI/CD. Code gets automated quality gates; docs get... a human reading them whenever they have time.&lt;/p&gt;

&lt;p&gt;Here's how to change that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Documentation Review is a Bottleneck
&lt;/h2&gt;

&lt;p&gt;Every docs PR in our repo sat waiting for review. Someone had to manually check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are headings capitalized correctly?&lt;/li&gt;
&lt;li&gt;Did the author use our terminology ("click" not "select")?&lt;/li&gt;
&lt;li&gt;Are all the links still valid?&lt;/li&gt;
&lt;li&gt;Is the structure consistent with other pages?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was tedious for reviewers and slow for contributors. Engineers would submit docs, wait days for feedback, then get a list of nitpicky style fixes.&lt;/p&gt;

&lt;p&gt;We decided to automate the mechanical stuff.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Automated
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Style guide compliance&lt;/td&gt;
&lt;td&gt;Manual review&lt;/td&gt;
&lt;td&gt;Automated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Broken links&lt;/td&gt;
&lt;td&gt;Occasional spot checks&lt;/td&gt;
&lt;td&gt;Every PR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terminology consistency&lt;/td&gt;
&lt;td&gt;"I think we use X?"&lt;/td&gt;
&lt;td&gt;Enforced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heading format&lt;/td&gt;
&lt;td&gt;Manual review&lt;/td&gt;
&lt;td&gt;Automated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quality metrics&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Per-file scores&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Setup: GitHub Actions + EkLine
&lt;/h2&gt;

&lt;p&gt;We use &lt;a href="https://github.com/ekline-io/ekline-github-action" rel="noopener noreferrer"&gt;EkLine&lt;/a&gt; for automated docs review. Here's our workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Documentation Review&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;docs/**'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*.md'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;docs-review&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EkLine Docs Review&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ekline-io/ekline-github-action@v6&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;content_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./docs&lt;/span&gt;
          &lt;span class="na"&gt;ek_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.EKLINE_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;github_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github-pr-review&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. When someone opens a PR that touches documentation, EkLine:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Analyzes the changed files against our style guide&lt;/li&gt;
&lt;li&gt;Checks all links&lt;/li&gt;
&lt;li&gt;Validates terminology&lt;/li&gt;
&lt;li&gt;Leaves inline comments directly on the PR&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What the Feedback Looks Like
&lt;/h2&gt;

&lt;p&gt;Instead of a wall of linter output in CI logs, contributors get comments exactly where issues are:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm9l86qv3q5zzj8zxpw3w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm9l86qv3q5zzj8zxpw3w.png" alt=" " width="800" height="619"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The feedback is actionable: "Define 'MCP', if it's unfamiliar to the audience." with a suggestion to fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Your Style Rules
&lt;/h2&gt;

&lt;p&gt;EkLine comes with presets (Google, Microsoft), but you can customize:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .ekline/config.yaml&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;headings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;capitalization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sentence-case&lt;/span&gt;
  &lt;span class="na"&gt;voice&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;prefer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;active&lt;/span&gt;
  &lt;span class="na"&gt;terminology&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;banned&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;utilize&lt;/span&gt;  &lt;span class="c1"&gt;# use "use"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;leverage&lt;/span&gt; &lt;span class="c1"&gt;# use "use"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;click&lt;/span&gt;    &lt;span class="c1"&gt;# use "select"&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;API&lt;/span&gt;      &lt;span class="c1"&gt;# not "api" or "Api"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Results After 3 Months
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Avg. review cycles per docs PR&lt;/td&gt;
&lt;td&gt;2.4&lt;/td&gt;
&lt;td&gt;1.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to first feedback&lt;/td&gt;
&lt;td&gt;1-3 days&lt;/td&gt;
&lt;td&gt;Instant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Style-related review comments&lt;/td&gt;
&lt;td&gt;~60%&lt;/td&gt;
&lt;td&gt;~10%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Broken links shipped&lt;/td&gt;
&lt;td&gt;Monthly&lt;/td&gt;
&lt;td&gt;Rare&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The biggest win wasn't efficiency—it was &lt;strong&gt;contributor confidence&lt;/strong&gt;. Engineers now get instant feedback, fix issues themselves, and submit cleaner PRs. They're not waiting days just to learn they used the wrong heading style.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: Automating the "What Needs Updating?" Problem
&lt;/h2&gt;

&lt;p&gt;Automated review catches issues in what you submit. But there's a harder problem: knowing what &lt;em&gt;should&lt;/em&gt; be submitted after a product change.&lt;/p&gt;

&lt;p&gt;EkLine also has a &lt;strong&gt;Docs Agent&lt;/strong&gt; that addresses this. You can prompt it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The user invitation flow changed from 2 steps to 3 steps"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And it will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scan your docs repo for all files mentioning invitations&lt;/li&gt;
&lt;li&gt;Draft updated content&lt;/li&gt;
&lt;li&gt;Create a PR with the changes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This shifts documentation maintenance from "hope someone remembers" to "tell the agent what changed." We're still rolling this out, but early results show it dramatically reduces docs drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternatives to Consider
&lt;/h2&gt;

&lt;p&gt;If EkLine doesn't fit your needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://vale.sh/" rel="noopener noreferrer"&gt;Vale&lt;/a&gt;&lt;/strong&gt; — The underlying linter many tools use. More DIY, but very flexible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://alexjs.com/" rel="noopener noreferrer"&gt;alex&lt;/a&gt;&lt;/strong&gt; — Focused on inclusive language checking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;markdownlint&lt;/strong&gt; — Basic Markdown formatting rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference with EkLine is the GitHub integration (inline PR comments) and managed style guide configs. If you want to build your own setup, Vale is excellent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should You Automate Docs Review?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Yes, if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple people contribute to documentation&lt;/li&gt;
&lt;li&gt;You have (or want) a style guide&lt;/li&gt;
&lt;li&gt;Review cycles are slowing down releases&lt;/li&gt;
&lt;li&gt;You're already using docs-as-code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Maybe not, if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Solo writer on a small project&lt;/li&gt;
&lt;li&gt;Docs are in Confluence/Notion (no Git integration)&lt;/li&gt;
&lt;li&gt;Your review process works fine&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Sign up at &lt;a href="https://ekline.io" rel="noopener noreferrer"&gt;ekline.io&lt;/a&gt; — free for public repos, 30-day trial on Standard ($50/user/month)&lt;/li&gt;
&lt;li&gt;Add the GitHub Action to your repo&lt;/li&gt;
&lt;li&gt;Open a PR with docs changes&lt;/li&gt;
&lt;li&gt;See the automated review&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Full setup guide: &lt;a href="https://docs.ekline.io" rel="noopener noreferrer"&gt;docs.ekline.io&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What's in Your Docs CI?
&lt;/h2&gt;

&lt;p&gt;I'm curious what other teams are doing for documentation quality. Are you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Running any automated checks?&lt;/li&gt;
&lt;li&gt;Using different tools?&lt;/li&gt;
&lt;li&gt;Still doing everything manually?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drop a comment—I'd love to learn from your setup.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Bipin works on documentation tooling at &lt;a href="https://ekline.io" rel="noopener noreferrer"&gt;EkLine&lt;/a&gt;. He's interested in how teams scale documentation quality without scaling review burden.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>devops</category>
      <category>github</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Your Documentation is Lying to Your Users</title>
      <dc:creator>EkLine.io</dc:creator>
      <pubDate>Fri, 13 Feb 2026 12:32:51 +0000</pubDate>
      <link>https://dev.to/ekline/your-documentation-is-lying-to-your-users-41o6</link>
      <guid>https://dev.to/ekline/your-documentation-is-lying-to-your-users-41o6</guid>
      <description>&lt;p&gt;Let me be direct: your documentation is probably lying to your users right now.&lt;/p&gt;

&lt;p&gt;Not maliciously. Not intentionally. But lying nonetheless.&lt;/p&gt;

&lt;p&gt;That endpoint you deprecated three months ago? Still documented as active. That config flag you renamed in v2.3? Still showing the old name. That "quick start" guide that takes 47 steps because your product evolved?&lt;/p&gt;

&lt;p&gt;Every day, developers hit your docs, follow instructions that no longer work, and quietly conclude your product is broken. They don't file a bug. They don't complain. They just leave.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Math Doesn't Work
&lt;/h2&gt;

&lt;p&gt;Here's the uncomfortable reality most teams avoid:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Documentation decays faster than you can maintain it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think about it. Every feature ship, every API change, every UI update, every renamed variable creates potential doc drift. In a healthy engineering org shipping weekly? That's 50+ potential documentation landmines per year. Per product.&lt;/p&gt;

&lt;p&gt;Now look at your docs team. One person? Two? Part-time contributors who'd rather be coding?&lt;/p&gt;

&lt;p&gt;The math doesn't work. It never did.&lt;/p&gt;

&lt;h2&gt;
  
  
  "We'll Update Docs Before Release"
&lt;/h2&gt;

&lt;p&gt;Every team says this. Almost none do it consistently.&lt;/p&gt;

&lt;p&gt;Why? Because documentation updates compete with shipping features. And shipping features wins. Every time.&lt;/p&gt;

&lt;p&gt;This isn't a discipline problem. It's a systems problem.&lt;/p&gt;

&lt;p&gt;Your CI catches code bugs before merge. Your tests catch regressions. Your linters catch style violations.&lt;/p&gt;

&lt;p&gt;What catches documentation drift? A customer complaint three months later? A support ticket from someone who wasted an hour on outdated instructions?&lt;/p&gt;

&lt;p&gt;That's not a system. That's hope.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bug Reframe
&lt;/h2&gt;

&lt;p&gt;Here's what changed my thinking: &lt;strong&gt;treat outdated documentation as a bug.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not a "nice to have." Not a "we'll get to it." A bug. With the same urgency you'd give a production incident.&lt;/p&gt;

&lt;p&gt;Because that's what it is. When your docs say one thing and your product does another, that's a defect in your customer experience. It's a reliability issue.&lt;/p&gt;

&lt;p&gt;Bugs get tracked. Bugs get prioritized. Bugs get fixed.&lt;/p&gt;

&lt;p&gt;Documentation drift? It just... accumulates. Until someone notices. Which is usually a customer. Who's now frustrated.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Works
&lt;/h2&gt;

&lt;p&gt;After watching this pattern repeat across teams, here's what I've seen work:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Automate the detection, not just the writing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The hard part isn't writing docs. It's &lt;em&gt;knowing&lt;/em&gt; when docs need updating.&lt;/p&gt;

&lt;p&gt;When your product changes, something should flag which documentation is now potentially wrong. Not "maybe we should check the docs." A specific alert: "This PR modified the authentication flow. These 4 doc pages reference authentication."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Put docs in the same workflow as code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Docs-as-code isn't just a storage strategy. It's a workflow strategy.&lt;/p&gt;

&lt;p&gt;If docs live in the same repo, reviewed in the same PRs, tested in the same CI—they get the same attention as code. Not because people suddenly care more about docs. Because the system treats them equally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Make "done" include documentation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is cultural, but systems can enforce it.&lt;/p&gt;

&lt;p&gt;A feature isn't shipped until the docs are updated. A breaking change isn't merged until the migration guide exists. Not as a suggestion. As a gate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tooling Gap
&lt;/h2&gt;

&lt;p&gt;For code quality, we have: linters, formatters, type checkers, test frameworks, security scanners, dependency auditors.&lt;/p&gt;

&lt;p&gt;For documentation quality, most teams have: a human reading it when they have time.&lt;/p&gt;

&lt;p&gt;That gap is why docs rot faster than code.&lt;/p&gt;

&lt;p&gt;The fix isn't more humans. It's applying the same thinking we already apply to code: automated checks, continuous validation, systematic enforcement.&lt;/p&gt;

&lt;p&gt;Style guide violations? Flag them automatically. Broken links? Catch them in CI. Terminology inconsistency? Enforce it the same way you enforce code style.&lt;/p&gt;

&lt;p&gt;This isn't complicated. We just haven't bothered to apply existing patterns to documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Cost
&lt;/h2&gt;

&lt;p&gt;"Our docs are a little out of date" sounds minor.&lt;/p&gt;

&lt;p&gt;Here's what it actually costs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Support tickets&lt;/strong&gt; for problems the docs created&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lost deals&lt;/strong&gt; when prospects can't complete your quick start&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Churn&lt;/strong&gt; from users who concluded your product doesn't work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engineering time&lt;/strong&gt; answering questions that docs should handle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reputation damage&lt;/strong&gt; in every "your docs are wrong" GitHub issue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most teams never connect these costs to documentation. They're too diffuse. Too delayed.&lt;/p&gt;

&lt;p&gt;But they're real. And they compound.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Challenge
&lt;/h2&gt;

&lt;p&gt;Here's something you can do in 10 minutes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick your most important user journey (signup, first API call, basic integration)&lt;/li&gt;
&lt;li&gt;Follow your own docs as if you've never seen your product&lt;/li&gt;
&lt;li&gt;Note every place where instructions don't match reality&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most teams find 3-5 issues. In their most important flow. That users hit every day.&lt;/p&gt;

&lt;p&gt;That's not a documentation problem. That's a customer experience bug hiding in plain sight.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Use
&lt;/h2&gt;

&lt;p&gt;Full disclosure: I use &lt;a href="https://ekline.io" rel="noopener noreferrer"&gt;EkLine&lt;/a&gt; for automated docs review. It runs in CI, flags style issues, catches broken links, and tells me when product changes affect documentation.&lt;/p&gt;

&lt;p&gt;It's not the only option. &lt;a href="https://vale.sh/" rel="noopener noreferrer"&gt;Vale&lt;/a&gt; is excellent if you want to build your own system. The point isn't the tool—it's treating documentation with the same rigor we apply to code.&lt;/p&gt;

&lt;p&gt;Whatever you use, the first step is the same: stop treating documentation as a writing problem. Start treating it as a systems problem.&lt;/p&gt;

&lt;p&gt;The writing was never the hard part.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What's the oldest lie in your documentation? I'm genuinely curious—drop a comment. No judgment. We've all been there.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>devops</category>
      <category>devrel</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
