Developer docs

CLI & GitHub integration

Engineers build from files in the repo, not from a browser tab. The versodocs CLI pulls the approved (Current) version of a spec into the repo as Markdown, records the exact version in versodocs.lock, and tells CI when that version has been superseded.

Install

Node.js 20 or later, no dependencies. Run it with npx or add it to the project:

bash
npx -y @versodocs/cli@0 --help
# or
npm install --save-dev versodocs

The CLI uses the REST API and works on every plan. Reading past versions (pull --version, pull --frozen) and diff need Team; on Free, check still runs and says which plan adds change summaries.

Log in

bash
versodocs login --org <org-slug> [--api-url https://versodocs.com]

Create an API key in VersoDocs under Settings → Developer (scope docs:read). login asks for it with hidden input, or reads it from --token or stdin (echo "$TOKEN" | versodocs login --org acme). It is stored in ~/.config/versodocs/config.json ($XDG_CONFIG_HOME respected; %APPDATA%\versodocs\config.json on Windows) with file mode 600. versodocs logout deletes it.

SettingFlagEnvDefault
Tokenlogin --tokenVERSODOCS_TOKEN—
Org--orgVERSODOCS_ORGfrom versodocs.lock, then login
API URL--api-urlVERSODOCS_API_URLhttps://versodocs.com

VERSODOCS_TOKEN always wins over the stored token, so use it in CI. A stored token is only ever sent to the API URL it was saved for.

Commands

pull

bash
# One spec (path = <workspace>/<folder…>/<slug>)
versodocs pull eng/specs/checkout-api [--out docs/checkout-api.md] [--version 12]

# Every spec in a folder; subfolders are mirrored under --out-dir
versodocs pull --folder eng/specs --out-dir docs/specs

# Everything in versodocs.lock, updated to the latest Current version
versodocs pull

# Everything in versodocs.lock at exactly the locked versions (restore)
versodocs pull --frozen

Each file is the published Markdown with YAML front-matter:

docs/specs/checkout-api.mdmarkdown
---
title: "Checkout API"
versodocs_id: "0b6c2d8e-1f2a-4b3c-9d4e-5f6a7b8c9d0e"
version: 13
published_at: "2026-10-01T09:12:00.000Z"
url: "https://versodocs.com/org/acme/eng/specs/checkout-api"
reference: "versodocs:acme/0b6c2d8e-1f2a-4b3c-9d4e-5f6a7b8c9d0e@v13"
---

# Checkout API
…

Without --out, a single spec is written to <slug>.md in the current directory; --out-dir defaults to .. --summary-file <file> writes a Markdown table of what changed (doc, old → new version, link); the auto-sync workflow uses it as the pull request body.

status

text
FILE                        LOCKED  CURRENT  STATUS
--------------------------  ------  -------  ----------
docs/specs/checkout-api.md  v12     v13      outdated
docs/specs/payments.md      v4      v4       up to date

Statuses: up to date, outdated (a newer version is Current), missing (file deleted), modified (file edited locally since the pull), not found (document deleted or unpublished). --json prints machine-readable rows.

check

Same table; exits 1 when any spec is outdated, missing or not found, and prints a summary of what changed for each outdated spec (Team+).

diff

bash
versodocs diff docs/specs/checkout-api.md   # or the doc path: eng/specs/checkout-api

Summary and Markdown diff between the locked version and Current (or --version N). Team+

API endpoints used

CommandEndpointPlan
pull <path>GET docs/by-path?format=mdAll plans
pullGET docs?folder=…, GET docs/{id}?format=mdAll plans
pull --version / --frozenGET docs/{id}?version=NTeam+
status, checkGET docs/{id}/current (falls back to GET docs/{id} on Free)All plans
diff, check summaryGET docs/{id}/diffTeam+

versodocs.lock

Commit it. It records the org, API URL, folders pulled with --folder, and for every spec its id, path, version, file and the sha256 of the file as written. Entries are sorted and the JSON is formatted deterministically, so it diffs cleanly in pull requests.

versodocs.lockjson
{
  "lockfile_version": 1,
  "api_url": "https://versodocs.com",
  "org": "acme",
  "folders": [{ "folder": "eng/specs", "out_dir": "docs/specs" }],
  "docs": [
    {
      "id": "0b6c2d8e-1f2a-4b3c-9d4e-5f6a7b8c9d0e",
      "path": "eng/specs/checkout-api",
      "version": 13,
      "file": "docs/specs/checkout-api.md",
      "sha256": "…"
    }
  ]
}

CI check

Fail a build when the repo's specs fall behind:

.github/workflows/ci.yml (step)yaml
- run: npx -y @versodocs/cli@0 check
  env:
    VERSODOCS_TOKEN: ${{ secrets.VERSODOCS_TOKEN }}

GitHub auto-sync

A ready-to-copy workflow keeps the specs in your repo on their Current version. Every hour, or on demand, it runs versodocs pull against versodocs.lock and, when a spec has a new published version, opens (or updates) one pull request titled Update specs from VersoDocs. Nothing changes in your repo until someone merges it. Works on every plan.

  1. Pull your specs once, locally, so the repo has a lockfile:
    bash
    npx -y @versodocs/cli@0 login --org <your-org>
    npx -y @versodocs/cli@0 pull --folder <workspace>/<folder> --out-dir docs/specs
    git add versodocs.lock docs/specs && git commit -m "docs: add specs from VersoDocs"
  2. Add an API key (docs:read) as the repository secret VERSODOCS_TOKEN.
  3. Copy integrations/github-sync/versodocs-sync.yml to .github/workflows/.
  4. Settings → Actions → General → Workflow permissions → enable Allow GitHub Actions to create and approve pull requests.
.github/workflows/versodocs-sync.yml (core)yaml
on:
  schedule:
    - cron: '17 * * * *'   # hourly
  workflow_dispatch:
  repository_dispatch:
    types: [versodocs.published]

permissions:
  contents: write
  pull-requests: write

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: npx -y @versodocs/cli@0 pull --summary-file "$RUNNER_TEMP/versodocs-changes.md"
        env:
          VERSODOCS_TOKEN: ${{ secrets.VERSODOCS_TOKEN }}
      - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
        with:
          branch: versodocs/sync
          commit-message: 'docs: update specs from VersoDocs'
          title: Update specs from VersoDocs
          body-path: ${{ runner.temp }}/versodocs-changes.md
  • The workflow reuses one branch (versodocs/sync), so you get one rolling PR.
  • Folders pulled with --folder are re-listed on every sync, so documents added later arrive in the next PR.
  • PRs opened with the default GITHUB_TOKEN don't trigger other workflows (GitHub's rule); pass a PAT or GitHub App token via the action's token: input if CI must run on them.

Spec drift check (pr-check)

Catch pull requests built from an outdated spec. versodocs pr-check scans the PR title and description for VersoDocs references, asks which version is Current, and fails (or warns) when the PR points at an older one. Team+

text
"Payments API": PR references Spec v12 but Current is v13: https://versodocs.com/org/acme/eng/proposals/…

Reference a spec by pasting Copy reference for PRs from any document page (versodocs:<org>/<documentId>@v<N>), or a document URL with ?v=<N>. On GitHub, the GitHub App runs the same check with no workflow file.

.github/workflows/spec-drift.ymlyaml
name: Spec drift check
on: { pull_request: { types: [opened, edited, synchronize, reopened] } }
jobs:
  spec-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-node@v4
        with: { node-version: 22 }
      - run: npx -y @versodocs/cli@0 pr-check
        env:
          VERSODOCS_TOKEN: ${{ secrets.VERSODOCS_TOKEN }}
OptionDefaultDescription
VERSODOCS_TOKEN (env)requiredAPI key (docs:read). Only ever sent to --api-url.
--api-urlhttps://versodocs.comVersoDocs origin. URL references on other hosts are ignored.
--modefailfail: drift or an unknown reference fails the job. warn: report only, always exit 0.
--require-referenceoffTreat a PR without any spec reference as a problem.
--text / --filePR title + bodyScan this text / file instead.

On GitHub Actions it adds ::error / ::warning annotations, a job-summary table and the references / outdated step outputs. Network errors and VersoDocs 5xx responses only warn, so an outage never blocks merges; a rejected token (401), a plan without the CI check (403) or an unknown document (404) is reported as a problem. Include edited in the triggers so fixing the reference re-runs the check. Pull requests from forks get no secrets, so skip them with if: github.event.pull_request.head.repo.full_name == github.repository.

.gitlab-ci.ymlyaml
spec-check:
  image: node:22
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  script:
    - npx -y @versodocs/cli@0 pr-check
  # VERSODOCS_TOKEN: masked CI/CD variable

Errors & exit codes

HTTPWhat the CLI tells you
401Run versodocs login --org <slug> or set VERSODOCS_TOKEN.
403 plan_featureWhich plan unlocks the feature, and the upgrade URL.
404Check the org slug / path and that the document has a published version.
429Retried automatically (honours Retry-After up to 30 s), then reports the wait.

Exit codes: 0 ok, 1 failure (check found outdated specs, pr-check found drift in fail mode), 2 usage error (bad flags, no token, nothing to scan).