CLI

Docs as Code

routebase docs pull downloads a documentation version into a folder of Markdown files, and routebase docs push writes local changes back. Between those two your documentation lives in your repository: reviewed in pull requests, versioned with the code it describes, editable in whatever editor you already use.

The Doc Hub in the app stays fully usable while you do this — the two are views of the same pages, not competing sources.

This guide assumes the CLI is installed and configured — see CLI Overview.

Pulling

routebase docs pull --project <project-id> --dir docs
Option Default Effect
--project required The project id
--doc the only one The documentation id — optional when the project has exactly one
--version-id highest version number Which version to pull
--dir docs Where to write the files

What you get:

  • Pages become {slug}.md with YAML frontmatter.
  • Folders become directories with a _folder.json sidecar holding their metadata.
  • API-reference nodes (spec snapshots) are skipped and listed at the end of the run. They are generated from your specs, so there is no Markdown to edit — manage them in the app.

The frontmatter

---
id: 6f1b2c3d-4e5f-6789-abcd-ef0123456789
title: "Getting Started"
slug: "getting-started"
icon: "rocket"
pageType: "markdown"
position: 0
rowVersion: "AAAAAAAAB9E="
---

# Getting Started

Your content starts here.

Leave these fields alone unless you mean to change them. Two of them do real work:

  • slug is the page's address on the portal and how push matches a local file to a remote page. Renaming it moves the page — and breaks any link pointing at the old address.
  • rowVersion is the concurrency token from the moment you pulled. It is what lets push notice that someone edited the page in the app since then; see below.

position sets the order among siblings and is written back on push. icon is optional.

Pushing

routebase docs push --project <project-id> --dir docs --dry-run
routebase docs push --project <project-id> --dir docs

Run --dry-run first. It computes the whole diff and prints what would change without writing anything — the cheapest way to catch a stray rename before it becomes a moved page.

push targets a mutable version (Draft or Review). By default it picks the newest one. A version that is already published is immutable, and the command says so rather than cloning it behind your back — clone it in the app first, or point --version-id at a mutable one. See Doc versioning.

What push does and does not do

It matches local files to remote pages by slug within each parent folder, then:

  • creates pages and folders that exist locally but not remotely;
  • updates pages whose content differs (compared with line endings normalised, so a Windows checkout does not show every page as changed);
  • skips unchanged pages entirely;
  • restores sibling order from position.

It never deletes. A page that exists remotely with no local counterpart is reported as a warning and left alone. Deleting is done in the app, deliberately — a --dir pointed one level too deep would otherwise wipe a documentation version in one command.

Conflicts

If someone edited a page in the app after your pull, its rowVersion no longer matches and the server rejects that write with a conflict. The page is never overwritten: the conflict is collected, the rest of the push continues, and the command exits with code 6.

To resolve, pull again and reapply your change on top of the newer content. There is no force flag — the failure mode it would create (silently discarding a colleague's edit) is worse than the inconvenience.

After a successful create or update, the CLI writes id, slug and the fresh rowVersion back into the local file. That is why a second push right after the first does not conflict with itself, and why those write-backs belong in your commit.

Transient failures (5xx, 429, network timeouts) are retried up to three times with backoff before the command gives up.

A round trip in a pipeline

The natural shape is: docs live in the repo, and a merge to main pushes them.

name: Publish docs

on:
  push:
    branches: [main]
    paths: ["docs/**"]

jobs:
  push-docs:
    runs-on: ubuntu-latest
    env:
      PROJECT_ID: 00000000-0000-0000-0000-000000000000 # <-- your project id
      # US-hosted organizations only; EU needs nothing.
      # ROUTEBASE_REGION: us
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: "10.0.x"

      - name: Install the Routebase CLI
        run: dotnet tool install --global Routebase.Cli

      - name: Configure
        run: routebase config set-api-key "$ROUTEBASE_API_KEY"
        env:
          ROUTEBASE_API_KEY: ${{ secrets.ROUTEBASE_API_KEY }}

      # Exit 6 means a page changed in the app since the last pull — worth failing on,
      # so nobody's edit gets quietly stranded.
      - name: Push the docs
        run: routebase docs push --project "$PROJECT_ID" --dir docs

The key needs docs:write (and docs:read for pull). Pushing writes into a Draft version — it does not publish. Publishing stays a deliberate step in the app, so a merge never puts unreviewed text in front of your readers; see Publishing.

When to use this

Docs-as-code pays off when the documentation changes with the code — a client library, a set of guides that follows a release, anything a reviewer should see in the same pull request as the change it describes.

It pays off less for pages that non-engineers maintain. Those people have a good editor in the app already, and routing their edits through Git adds a step without adding review value. Mixing both in one documentation version is fine — the two only meet when the same page changes on both sides, and the conflict handling above is exactly for that case.