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}.mdwith YAML frontmatter. - Folders become directories with a
_folder.jsonsidecar 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:
slugis the page's address on the portal and howpushmatches a local file to a remote page. Renaming it moves the page — and breaks any link pointing at the old address.rowVersionis the concurrency token from the moment you pulled. It is what letspushnotice 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.
Related
- CLI Overview — install, configuration, exit codes
- CLI in CI/CD — the CLI's testing and scanning commands
- Doc versioning — mutable versions and why push needs one
- Publishing — turning a Draft into a live portal
- API Keys — creating and scoping the key