The routingcat command
Run the command as npx routingcat <command> in your app’s folder. npx routingcat --help lists the commands, npx routingcat <command> --help shows one, and npx routingcat --version prints the version.
npx routingcat runs the version your routingcat.config.json pins, as "routingcat": "1.0.3", for push, plan, doctor, docs and, once the pin is written, init, since each version writes its own head partial: started as another version, it says so in one line, then runs the pinned one through npx with the same arguments, and stops where npx can’t fetch or run it.
Conventions
Options every command takes
| Option | What it does |
|---|---|
--json |
Prints one JSON object instead of text, described in JSON output. A line a person needs, such as a sign-in link, goes to standard error. |
--site <key> |
The site to act on, by its key. Without it, the site routingcat.config.ts or routingcat.config.json names, found from the current folder upward; else, on a machine paired with a Shopify store from its admin, that store’s site; else your only site. |
-h, --help |
Prints the command’s usage. |
Options every write takes
The writes change something: keys, link, unlink, members, domains, connect, disconnect, pause, resume, promote, retire, revert, set, gap and push. Each also takes:
| Option | What it does |
|---|---|
--dry-run |
Shows what the write would do, with the operation it would create, and changes nothing. |
--idempotency-key <key> |
Makes a retry safe: a key used again returns the first result instead of acting twice. One is generated when absent: 1 to 128 letters, digits and _ . : -. |
--expect <registry version> |
Acts only if the site has not changed since you read it, at the version status --json gives as registry_version; otherwise it fails with a version conflict. |
Exit codes
| Code | Meaning |
|---|---|
| 0 | Done. |
| 1 | Rejected, denied or failed, with a message id. |
| 2 | The command or its arguments are wrong. |
| 3 | Waiting, on a person, such as approving a sign-in link, or on time, such as doctor’s checks of a deploy that has just gone out. Run the same command again once the person has acted, or a little later. |
| 4 | RoutingCat cannot be reached. Allow network access for the command, or ask the person to run it. |
Addresses and ids
An address names what your code declares, by its keys: home is a page, home/hero a slot, home/hero:oneclick a variant, and home/hero:oneclick@v2 one of its versions. The commands take addresses wherever they act on what you declared.
Everything else goes by the id the output prints. show takes an address or an id. The ids you meet start with:
| Prefix | Names |
|---|---|
sf_ |
a page |
sl_ |
a slot |
vr_ |
a variant |
rv_ |
a version |
ch_ |
a change your build or push registered |
pb_ |
a publication: what a page serves, for revert --to |
dp_ |
a deployment |
op_ |
an operation: an act, or a build or push registering |
cn_ |
a connection to a payment provider |
ev_ |
a scorecard |
run_ |
one of a scorecard’s results |
Values
Times are UTC.
| Value | Written as |
|---|---|
| A share | 0.1 or 10%, from 0 to 1 |
| A duration | 7d, 24h, 30m or 2w, or seconds, as 604800 |
| A time | UTC, as 2026-10-20T14:00:00Z, or a day, as 2026-10-20, which is its start; or now |
| A first day | A day, as 2026-10-10, or a span back from today, as 7d: the last seven days, today included |
Setup
init
npx routingcat init [--dry-run] [--agents-md | --no-agents-md] [--host <host>] [--origin <url>] [--helpers]Sets RoutingCat up in the project of the current folder.
Where your host runs nothing of RoutingCat’s, it sets up the script integration, which decides in the page. It finds your templates, signs you in, and creates your site, bound to no host. Then it writes routingcat.config.json and the head partial, includes the partial first in your layout’s <head>, and installs the skill for your coding agent; with --helpers, it also writes your server’s helpers. It ends with what your CI runs: push --check before your deploy, and push after it. Set up RoutingCat gives each edit.
With your agreement, it adds a two-line note to AGENTS.md that points coding agents to the skill. At a terminal it asks whether to add the note, then prints the plan and asks before applying it; run by an agent, it asks nothing, and says to ask you about the note. Nothing reaches visitors until you deploy. Run again, it does only what is missing, and keeps the note up to date.
| Option | What it does |
|---|---|
--dry-run |
Prints the plan and changes nothing. |
--agents-md |
Adds the note to AGENTS.md without asking: your agent runs it once you agree. |
--no-agents-md |
Leaves AGENTS.md and CLAUDE.md as they are, without asking. |
--host <host> |
Where the site deploys, where the folder does not say: vercel, cloudflare-workers, cloudflare-pages, or none for a host that runs nothing of RoutingCat’s, which sets up the script integration. On a site routingcat.config.json sets up already, another host moves it there, its pages, variants and results kept. |
--origin <url> |
The production address, as https://www.example.com, where neither the host nor the site’s configuration gives it: a script site’s, a site’s on Cloudflare, or a Hydrogen storefront’s on Oxygen. routingcat.config.json lists its host, and routingcat.config.ts’s config() names it as origin. |
--helpers |
On a script site its own server renders, Laravel, Rails, Django, WordPress or PHP: writes the server helpers in its language, which set the visitor cookie and the fetch site on each page it renders, registers them where one file shows where, and declares them in routingcat.config.json. |
$ npx routingcat init --host noneroutingcat 1.0.0 · <project>
Checks ✓ Laravel: its templates resources/views/**/*.blade.php · Laravel renders this site's pages with nothing of RoutingCat's running before the HTML, so RoutingCat decides in the page: the script integration. On a host whose middleware runs RoutingCat, such as Vercel or Cloudflare, the same templates would take server decisions: a visitor id the server sets for 400 days, secret seeds, no wait on a first view, and geography from the first view. (D87) ✓ production address https://www.lumen.com, from APP_URL in .env ✓ signed in to RoutingCat
Plan 1 create the site lumen, for the script integration, with no host binding 2 bootstrap/app.php leave rc_vid out of cookie encryption: $middleware->encryptCookies(except: ['rc_vid']); 3 routingcat.config.json new: the site and its public key 4 resources/views/routingcat-head.blade.php new: the head partial, as routingcat push --head-only writes it 5 resources/views/layouts/app.blade.php include the head partial first in <head>: @include('routingcat-head') 6 the skill .agents/skills/routingcat/, copied to .claude/skills/routingcat/Nothing reaches visitors until you deploy.
✓ site lumen created (op_01m3yesth0fjfsctr17tbq1z82), for the script integration, with no host binding✓ 2 files edited, 2 files and 2 folders created (see git diff)· In CI, before your deploy: npx routingcat@1.0.0 push --check, which fails on a stale stamp or partial.· Right after your deploy, on your production branch: npx routingcat@1.0.0 push, which registers it.· The push proves itself with the site's deploy key: ask the site's owner to run npx routingcat keys deploy --branch <your production branch> in their own terminal, and to set what it prints as the secret ROUTINGCAT_DEPLOY_KEY in your CI.· Where your host deploys on its own, as Forge or a hook on your server does, run that push at the end of its deploy script, with ROUTINGCAT_DEPLOY_KEY and ROUTINGCAT_BRANCH, your production branch's name, in its environment.· Without a CI, register production from this machine after each deploy: npx routingcat@1.0.0 push --env production, which shows the plan and pushes it once you confirm.· Your Laravel server can set RoutingCat's visitor cookie, which Safari then keeps 400 days instead of 7, and the fetch-site cookie, which tells a visit typed or bookmarked from one that followed a link. If you want it to, run npx routingcat init --helpers, which adds the helpers that set them. (D102)Ask the person: "Add a two-line note to AGENTS.md so coding agents always know RoutingCat is here? (recommended)" If yes, run npx routingcat init --agents-md.Dashboard: https://app.routingcat.com/lumenNext: read .agents/skills/routingcat/setup.md and follow itlogin
npx routingcat loginSigns you in on this machine. It prints a link, opens it in your browser, and waits 50 seconds for you to approve it. Approved later, run it again: it picks up the same link, and ends with exit code 3 while it waits on you.
logout
npx routingcat logoutRevokes the sign-in on this machine and deletes it.
doctor
npx routingcat doctor [--wait]Checks the install and prints one line per check, with a message id when one fails: the latest production push registered, the GitHub link where you made one, your pages carry the head partial first in <head>, naming templates a push registered, RoutingCat’s CDN has routing data for each routed page, the partial’s post part loads as pinned, your pages’ Content Security Policy lets RoutingCat run, RoutingCat takes the records of your production address, each page’s slots as served match your templates, the server helpers your config declares set their cookies, an arrival was recorded, and the objective’s first event, which waits for one real conversion. Your pages decide only in a browser, so doctor‘s own requests record no visit: until a first arrival comes, it names a page to open in a browser. Locally, it checks routingcat.config.json and the key in it, your templates’ stamps and head partial against what push writes, the partial’s include, the server helpers your config declares, and the skill’s version.
Once a page is routed, it also checks that the page is live and that its first routed visit and render beacon arrived. It says whether AGENTS.md has the note for coding agents, which never fails a check, and it changes nothing. Troubleshooting reads each check.
| Option | What it does |
|---|---|
--wait |
After a deploy, waits up to 5 minutes, printing each check as it passes. An agent running it gives the call a longer timeout than that. |
$ npx routingcat doctorlumen · production ✓ build registered ch_01m3yfrpt0ewpateq9yrq61xkb · objective signup · outcomes waitlist 14:19:52 ✓ head partial 1 page: first in <head>, core 1.0.0, manifest 47a096ddb5c9… registered ✓ post part rt/1/post/1.0.4.js loads, its integrity checked ✓ policy lets RoutingCat run, apply and record ✓ records ingest.routingcat.com accepts them from https://www.lumen.com ✓ markup no slot on these pages yet ✓ first arrival arrivals in the pulse, last 14:39:18 14:39:18 ✓ signup 2 events 14:33:59 · server · identify 14:32:05 14:33:59 ✓ card on file Visa •••• 4242 · AGENTS.md has no RoutingCat note, so coding agents may skip the skill unless a request matches it; if the person wants the note, run npx routingcat init --agents-md9 of 9 passed (exit 0)keys
npx routingcat keys [server|deploy] [--env production|preview] [--branch <name>] [--rotate|--revoke]Lists the site’s keys, or shows one. keys server shows the server events key, which your backends send events with, through the HTTP events API, and mints it on first use.
keys deploy mints the deploy key, ROUTINGCAT_DEPLOY_KEY, for the push your CI runs where link github has not linked the site to its repository, and shows it only then.
The production deploy key is bound to your production branch, so a preview build never publishes with it, even with the key in its variables. The preview deploy key is bound to no branch: on Cloudflare, one preview key in the preview environment’s variables covers every preview branch. Only the site’s owner mints, rotates and revokes keys.
| Option | What it does |
|---|---|
--env production|preview |
The key’s environment: production, by default, or preview, whose events stay out of production results. |
--branch <name> |
The production branch, which the production deploy key needs: the key proves production only for builds of that branch, as their CI reports it. With --rotate, the new key is bound to the branch given. |
--rotate |
Replaces the key. The one it replaces keeps working for 24 hours. |
--revoke |
With keys deploy, stops the environment’s deploy key at once, and the one its last rotation replaced, as for a key that leaked. |
A write: it also takes the options every write takes.
$ npx routingcat keys deploy --branch maindeploy key · production · branch main · minted now (op_01m4zbfqr0fwqttp9y5atppz0f) dk_qEi3Pi0oOEMbi6gb9l2uUsWQPF2A7H_OEIrNnn1cO3iCUcOn3YG4MgeQpxfKf4c-v6BzUec_KQP9ml31_8pHZOJqlNX_OP9EtLs1tZs_OrPB1YozQ4JQhqFMeKhYKYge17RdO_0ANyBicK6Set it as the secret ROUTINGCAT_DEPLOY_KEY where routingcat push runs, in your CI or in your host's deploy script. It is shown only when minted or rotated.It proves production only for pushes on main, as your CI reports the branch, or ROUTINGCAT_BRANCH in a deploy script.link
npx routingcat link github [--branch <name>]Links a script site to its GitHub repository, the one your origin remote names, so that the repository’s GitHub Actions workflows register the site with GitHub’s own token, and no deploy key needs keeping. The link stays pending until a workflow run of that repository proves it, within a day: a run that calls push in a job with permissions: id-token: write. From then on, runs on your production branch register production, and other runs register previews. While the link is pending, the command ends with exit code 3. Only the site’s owner links a repository. A site whose host’s middleware decides needs no link: it registers in its build.
| Option | What it does |
|---|---|
--branch <name> |
Your production branch, as main, whose runs register production. Without it, the one routingcat.config.json names as environments.production.branch. |
A write: it also takes the options every write takes.
$ npx routingcat link github --branch mainlink github · done · op_01m58vb0sff5f8vmx9bg22924h · registry version 3 st_01m58vasyqevdvx3zhrfa86nsa repository quire-app/site · branch main · status pending · expires_at 2026-10-20T01:10:12Zlumen · GitHub repository quire-app/site · production branch main · pending until Oct 20, 01:10 UTC✗ lumen is linked to GitHub repository quire-app/site once a workflow run of it proves it: run routingcat push in a workflow of quire-app/site whose job has permissions: id-token: write, before Oct 20, 01:10 UTC. (D116, exit 3)unlink
npx routingcat unlink githubRemoves the site’s link to its GitHub repository. The repository’s workflows then register nothing unless they hold the site’s deploy key. Only the site’s owner removes the link.
A write: it also takes the options every write takes.
$ npx routingcat unlink githubunlink github · done · op_01m58vb6mze6xtmwxg5rp0exqm · registry version 5 st_01m58vasyqevdvx3zhrfa86nsa repository quire-app/site removed · branch main removed · status pending removed · requested_at 2026-10-19T01:10:12Z removedlumen · no GitHub repository linked: CI's push registers with the site's deploy keymembers
npx routingcat members [add|remove <email or principal>]Lists the site’s members. The owner adds or removes one by email; an added member’s access starts at their first sign-in with that address.
A write: it also takes the options every write takes.
$ npx routingcat memberspantry · members theo@pantry.app owner active since 2026-07-08 ana@pantry.app member active since 2026-07-08 sam@pantry.app member pending added 2026-10-10, until their first sign-indomains
npx routingcat domains [add|remove <domain>]Lists the domains RoutingCat accepts browser events and links from: your production domain, and the domains added here.
The owner adds or removes one, for routingcat.link and the browser script on another domain.
A write: it also takes the options every write takes.
connect
npx routingcat connect whop [--sandbox] [--prove] [--reconcile] [--account-key <key>]Counts a payment provider’s payments as outcomes: purchases, renewals, trials, refunds, disputes and cancellations, each joined to the visit that led to it. connect whop creates the webhook in your Whop company with your own API key, from WHOP_API_KEY or typed at the terminal, which is never written to disk, then has Whop send its test event. Only the site’s owner connects a provider.
| Option | What it does |
|---|---|
--sandbox |
Whop’s: connects your Whop sandbox instead of the live company. |
--prove |
Whop’s: hands RoutingCat the key once, to prove the company is yours, and RoutingCat does not keep it. A company another site holds through its webhook’s deliveries alone then moves to this site. |
--reconcile |
Whop’s: also hands RoutingCat the key, kept encrypted, to read back payments a webhook missed. The key then needs payment:basic:read, member:basic:read and plan:basic:read too. |
--account-key <key> |
Where your own account ids live on the provider’s objects: a metadata key, as user_id, or customer for the provider’s customer id. On a connection the site holds, it changes only that. |
A write: it also takes the options every write takes.
$ npx routingcat connect whop --sandbox --site lumen✓ whop connected biz_8Jk4mWzR2pLs (sandbox) · signature verifiedconnections
npx routingcat connectionsLists the site’s connections to its payment providers: each one’s account, state, last delivery and outcomes today, and the tracking gaps a disconnection opened.
disconnect
npx routingcat disconnect <whop|shopify|cn_…>Removes a connection, named by its provider or its id: its payments stop counting from now, and the time after is recorded as a tracking gap. It deletes Whop’s webhook where RoutingCat keeps your Whop key, and otherwise says to delete it in Whop. Only the site’s owner disconnects one.
A write: it also takes the options every write takes.
Read
status
npx routingcat status [page] [--since <date or cursor>]The orientation, in under 40 lines. For each routed page: the measured gain, and each variant with its label, predicted rate, difference from the default, signal and share served. Then how conversions joined visits, the month’s visitors, where conversions came from, each place where what’s live differs from your code, warnings, and what changed since you last ran it. Give a page to see that page alone.
| Option | What it does |
|---|---|
--since <date or cursor> |
Shows the timeline instead: deploys, changes, acts and warnings since a day or a time, or since the cursor a previous --json returned, to read only what followed. |
$ npx routingcat statusinkwell · production · 2026-10-11 09:20Z · counts to 09:00Z · estimates matured to 10-11 · contract 1.0since 2026-10-04 09:20Z: 1 deploy (dp_01m4k54960e4ma9r026sfe2he2), no acts → routingcat status --since 2026-10-04
home · objective signup, 24 h · control: the defaults, 10% · live since 10-02 · publication pb_01m4k57yc0fa9vhrhd9qpf2dm2 gain +9 conversions (signup) vs defaults, no range: too few visits · measured · 341 routed / 47 control visits · maturity 0.88 hero default write-daily · 4 live write-daily default 2.2% [0.5, 3.9] · signal 3 Early · served 28% · 96 visits streaks favored 2.6% [0.7, 4.5] · +19% [−100, +145] vs default · signal 3 Early · served 40% · 135 visits prompts learning 3.3% [1.1, 5.6] · +51% [−100, +207] vs default · signal 3 Early · served 31% · 104 visits private prior 2.7% [0.4, 5.3] · +24% [−100, +177] vs default · signal 0 New · served 2% · 6 visits variant rates and contrasts are predicted; the measured gain compares routed visits with the control
tracking signup 19 in 7 days · joined 37% (visitor 32%, account 5%) · no routed visit 53% · no key 0% · unlinked 0% · outside window 11%visitors 333 on routed pages in October so far, people only · 5 automated left outvariants 3 live of the 10 the Hobby account's sites share, the defaults not countedsources 7 days, accounting: search 8 · direct 4 · social 3 · email 2 · paid 1 · unknown 1 → routingcat sourceswarnings none · deployments: dp_01m4k54960e4ma9r026sfe2he2 current (commit 2d79ae7), none unregisteredmore routingcat show home/hero:streaks · --json for every fieldshow
npx routingcat show <address or id> [--by <dimension>]Any object, one hop each way: a page, slot, variant or version by its address, or anything by the id the output prints. A variant shows its version, its predicted rate and difference from the default with their ranges, its signal, counted rates, share served, scorecards, and a breakdown, by device unless --by names another. A version shows its content and how it differs from the version before. A page, a slot or a variant also lists where what’s live differs from your code, a line per place.
| Option | What it does |
|---|---|
--by <dimension> |
The breakdown: device, traffic_source, visit, time_of_day or day_type. |
$ npx routingcat show home/hero:save-timehome/hero:save-time · vr_01kx68ntc0e1j9x6dma6hjgtym · live · learning · served by dp_01m4xeq560edfb3k7r2f65mexw version @v1 (rv_01kx68ntc0e12r1dvm2cr5xczt, 3c7ce6) since 07-10, ch_01kx68ntc0f6ws65jtkmgmd04q "Try four headlines and a call to action on the home page" headline "Dinner, decided" subhead "Your week of meals and your shopping list, ready on Sunday."
scores signup@1 · release rl_01m4yfavm0ey5rfmqd2d8a3anf · publication pb_01m4xettc0e9karqctc3tvjkvf · counts to 00:50Z · matured to 10-15 rate 3.8% [3.0, 4.5] · predicted vs default −14% [−32, +4] relative · −0.6 pts [−1.4, +0.2] · predicted signal 59 · Building · 2,171 matured visits counted 3.7% of 2,534 matured visits · 3.8% of all 2,565, incomplete · 78.8% rendered served 11% of hero; routing currently intends 9%
scorecards ev_01kx68ntc0fgnbx7xqtn0wa7xx vs default current, since 07-10 · as served · +7% [−20, +34] predicted · run_01m4ygw9n0ep1t35tc0jfgwwz4
by device visits served relative counted predicted mobile 1,222 10% 0.86 3.1% 3.1% [2.1, 4.0] desktop 1,199 13% 1.17 4.5% 4.5% [3.3, 5.6] other 144 12% 1.11 3.5% 3.5% [0.9, 6.2] also --by traffic_source, visit, time_of_day, day_type
other outcomes, counted trial_started 1.0%, value 0.02 per visit · purchase 0.5%, $0.13 per visitengagement seen 77% · 2.5 s visible · 3.0% click inside the slotrelated sl_01kx68ntc0enatwx14ctrzbj6n · ch_01kx68ntc0f6ws65jtkmgmd04q · pb_01m4xettc0e9karqctc3tvjkvf · ev_01kx68ntc0fgnbx7xqtn0wa7xx → routingcat show <id>sources
npx routingcat sources [page] [--since <date>] [--by source|campaign|landing]Conversions and revenue by where visits came from: by channel, or by source, campaign or landing page. It counts where conversions came from, not what caused them. Give a page for its visits alone.
| Option | What it does |
|---|---|
--since <date> |
The first day: a day, as 2026-10-10, or a span back from today, as 7d. |
--by source|campaign|landing |
Groups by source, campaign or landing page instead of by channel. |
$ npx routingcat sources --since 7dpantry · production · 7 days to 2026-10-15 08:00Z · accounting, not lift · contract 1.0
channel arrivals signup rate trial_started purchase revenuesearch 2,883 63 2.2% 31 11 $99direct 1,816 39 2.1% 15 6 $129referral 594 14 2.4% 6 3 $102social 818 12 1.5% 7 3 $102email 356 10 2.8% 8 0 —ai 449 9 2.0% 10 0 —paid 467 8 1.7% 6 1 $9other 164 3 1.8% 2 1 $9unknown 230 7 3.0% 0 0 —no_arrival — 7 — 5 1 $9total 7,777 172 2.2% 90 26 $459
top sources google 66 · newsletter 10 · producthunt.com 8 · chatgpt.com 6top campaigns launch-thread (social) 8 · weekly-digest (email) 6 · retarget-q3 (paid) 5 · brand-search (paid) 2top landing / 97 · /pricing 15 · /changelog 9 · /blog/meal-prep-sunday 8channels as classified by program launch-1 · also --by source, campaign, landingdownload
npx routingcat download <report> [--filter k=v…] [--by <dims>] [--format csv|json]Writes a report to a file in the current folder, and prints its path, its rows and how recent it is. The reports:
| Report | What it holds |
|---|---|
traffic |
Visits, visitors, routed page loads and arrivals, by day. |
outcomes |
Conversions and their value, by outcome and day, and where they came from. |
engagement |
How each slot was seen and clicked, and each page’s scrolling, time and speed. |
delivery |
What each slot rendered, and render beacons against routed page loads. |
progress |
The measured gain of each page, over time. |
scores |
Each variant’s predicted rate and difference from the default. |
evaluations |
Scorecards and their results. |
diagnostics |
The checks behind the warnings, such as how conversions joined visits. |
cells |
The finest grain: one row per day, page, slot, variant and breakdown. |
| Option | What it does |
|---|---|
--filter k=v… |
Selects rows, never visitors: page=, slot=, objective=, since= and until=, as --filter page=home. Repeat it, or separate pairs with commas. |
--by <dims> |
One or two breakdowns, separated by a comma: device, traffic_source, visit, time_of_day and day_type. Or one of country or channel, which give counts only. |
--format csv|json |
csv or json. |
docs
npx routingcat docs [<page>] [--family <family>]Prints a page of the documentation for the installed version, from the copy for your site’s stack: the one routingcat.config.ts or routingcat.config.json names, found from the current folder upward. Without a page, it prints the copy’s map of every page; with # and a heading’s anchor, as guides/tracking#record-a-tracking-gap, that section alone. It reads the docs the package carries, so it works offline, and on a site that runs RoutingCat through npx, with no node_modules.
| Option | What it does |
|---|---|
--family <family> |
Another copy, by its key, among those the installed version carries: nextjs, nuxt, astro, react-router, html or script. |
$ npx routingcat docs 'guides/tracking#record-a-tracking-gap'## Record a tracking gap
Run `npx routingcat gap <outcome> --from <time>`.
```sh# a heading inside code is not onenpx routingcat gap signup --from 2026-10-10T08:00:00Z```
### Close it
Run the same command with `--to`.Act now
pause
npx routingcat pause <variant…>Stops serving variants, named by address. A pause holds until resume, whatever your code declares, and their scorecards close. A slot’s default cannot be paused: promote another variant first.
A write: it also takes the options every write takes.
$ npx routingcat pause home/hero:plan-week✗ home/hero:plan-week is the default of home/hero, and every slot needs its default, so it cannot be paused or retired. Promote another variant first. (A8, exit 1)resume
npx routingcat resume <variant…>Serves paused or retired variants again, each with a new scorecard against its slot’s default.
A write: it also takes the options every write takes.
promote
npx routingcat promote <variant…>Makes each variant its slot’s default, so the control sees it too. The measured gain so far is kept, and measurement continues against the new defaults. It takes the variants of one page, at most one per slot.
It holds until a production push changes that default in your templates: make the variant the one your slot markup shows by default, as Ship changes shows.
A write: it also takes the options every write takes.
retire
npx routingcat retire <variant…>Stops serving variants for good, keeping their history. A slot’s default cannot be retired. A retired variant returns with resume, with revert, or when it is declared again and that change goes live in production.
A write: it also takes the options every write takes.
$ npx routingcat retire home/hero:currentretire · done · op_01m4zbfqr0fa49v710xk3sc2w2 · registry version 20 home/hero:current eligibility: live → retired welcome/checklist:tour held: true → false change ch_01m4zbfqr0fwqttp9y5atppz0f publications pb_01m4zbfqr0fzbb7a7j23jr4e9q · pb_01m4zbfqr0fgq9d4fhwyhfgbd7 scorecard ev_01m4zbfqr0eddsyqqmq0kgke7y opened: welcome/checklist:tour vs welcome/checklist:steps scorecard ev_01kx68ntc0fgnbx7xqtn0wa7xx: window closed scorecard ev_01kx68ntc0fz2tfkmdtfzpescs: window closed scorecard ev_01ky9xn1r0ebbtkka94kcpe432: window closedrevert
npx routingcat revert <page> [--to <pb_…>]Returns a page to what it served before. No version is created or lost.
| Option | What it does |
|---|---|
--to <pb_…> |
Returns to an earlier publication instead, by its id, as status --since lists them. |
A write: it also takes the options every write takes.
$ npx routingcat revert homerevert · done · op_01m4zbfqr0fa49v710xk3sc2w2 · registry version 20 home publication: pb_01m4xettc0e9karqctc3tvjkvf → pb_01m4zbfqr0fwqttp9y5atppz0f · as: none → pb_01m4dh0420fwwsmq55hmqw650q publications pb_01m4zbfqr0fwqttp9y5atppz0fset
npx routingcat set [<page or slot>] <setting>=<value>… [--confirm]Changes routing settings. With a slot’s address, or a page’s for each of its slots: fixed_share, personalization, floor and cap; floor=auto and cap=auto return to the automatic bounds. Without an address, the site’s: control_share, random_control_share, episode_window and routing. Each is name=value, several at once; Routing settings gives each one’s range and default.
| Option | What it does |
|---|---|
--confirm |
Says you agreed to lower control_share below 5%, which set refuses without it, with exit code 3: below 5% the measured gain is slow to come, and at 0 it can’t be computed. Your agent gives it only once you have agreed. |
A write: it also takes the options every write takes.
$ npx routingcat set home/hero fixed_share=50% floor=autoset · done · op_01m4zbfqr0fa49v710xk3sc2w2 · registry version 20 home/hero fixed_share: 0 → 0.5 publications pb_01m4zbfqr0fwqttp9y5atppz0f scorecard ev_01kx68ntc0fgnbx7xqtn0wa7xx: support changed scorecard ev_01kx68ntc0fz2tfkmdtfzpescs: support changed scorecard ev_01ky9xn1r0ebbtkka94kcpe432: support changed scorecard ev_01m1rgqs60eg4ab70xdf6s4fb7: support changedgap
npx routingcat gap <outcome> --from <time> [--to <time>] [--note <text>]Records a tracking gap: an outcome’s events were not tracked from --from to --to, so the visits in between count as unknown rather than as no conversion. Without --to the gap stays open; the same command with a --to closes it.
| Option | What it does |
|---|---|
--from <time> |
When the gap started: a time or a day. |
--to <time> |
When it ended. Without it, the gap stays open. |
--note <text> |
Why, in a few words. |
A write: it also takes the options every write takes.
$ npx routingcat gap signup --from 2026-10-10T08:00:00Z --note 'a deploy dropped the call'gap · done · op_01m4zbfqr0fa49v710xk3sc2w2 · registry version 19 signup@1 from 2026-10-10T08:00:00Z · to none gap signup@1 from 2026-10-10T08:00:00Z, open until you give --to annotation signup (gap:signup:2026-10-10T08:00:00Z)Deploy
plan
npx routingcat plan [page]Compares the declarations in your templates with production, and shows what a production push would make live, with each page’s limits. It validates as push does, and exits with 1 where push would refuse them. Give a page to plan it alone.
$ npx routingcat planlumen · this code against production (dp_01m3yfqdsgfkr9jahvnyvk6gaa, commit 4be1c07) · contract 1.0
home new page · route / · objective signup, 24 h (from routingcat.config.json) hero new slot · default current + current "Edit photos like a pro" the default, which the control always sees + oneclick "Pro-level photo edits in one click" scorecard against current + studio "Your photos, studio-ready" scorecard against current + best "Make every photo your best one" scorecard against current
In production this goes LIVE, with routing within about 2 minutes: the control keeps seeing the defaults · the random slice of visits is split evenly the rest are routed toward signup, as the model learns which variant works for whomA production push applies only what its templates changed since the previous production push.A push in CI on another branch registers these versions as a preview and changes nothing live.
limits 4 of 100 variants · 1 of 20 slots · 4 of 1,000 combinations the site: 3 live variants, the defaults not counted, of the 10 the Hobby account's sites sharevalid push will accept this (exit 0)preview
npx routingcat preview <variant…> [--path <path>]A signed link, valid for one hour, that opens the production page with the variants you name, and counts nothing in results. It takes the variants of one page, at most one per slot; the page’s other slots show their defaults.
| Option | What it does |
|---|---|
--path <path> |
The path to open; by default, the page’s own. |
$ npx routingcat preview home/hero:oneclickhttps://www.lumen.com/#rc_preview=1.eyJieSI6InByXzAxazZnIn0.c2ln forces home/hero:oneclick@v2 path / (its first route) · on dp_01m4xns9ggfyhra1beeb6f424r · until 10:12ZOpened in a browser, it shows a preview badge; the visit stays out of results and leaves your own assignments as they are.push
npx routingcat push [--check] [--built <dir>] [--head-only] [--dry-run] [--env production [--confirm <digest>]]Run push in your repository, then commit what it changes. It writes each variant’s data-rc-hash into your templates, adds style="display:contents" where a slot or its default lacks it and data-nosnippet where a hidden variant lacks it, and writes the head partial. It never adds or removes hidden.
Run in CI, or in your host’s deploy script, after your deploy, it also registers your templates: with GitHub’s token once link github has linked the site, or else with the site’s deploy key in ROUTINGCAT_DEPLOY_KEY, from keys deploy. A production key needs the branch: RoutingCat reads it from GitHub Actions, GitLab, Bitbucket, CircleCI, Jenkins’ multibranch pipelines, Azure Pipelines, Buildkite, Cloudflare’s builds, Netlify and Vercel, and elsewhere from ROUTINGCAT_BRANCH. Without either, --env production registers production from your machine.
| Option | What it does |
|---|---|
--check |
Changes nothing, and fails while push would still edit a template, or while the head partial is out of date. Run it in CI before you deploy. |
--built <dir> |
Also checks the pages your generator built into that folder, such as public, against your templates: each slot as they declare it, with the hashes push wrote. |
--head-only |
Writes the head partial alone. |
--env production |
Registers a script site’s production from your machine, with your sign-in. It prints the edits it would make and the plan, as plan shows it, then pushes that plan once you confirm it. At a terminal, it asks. Run by an agent, it ends with exit code 3 and the plan’s digest, so that the agent shows you the plan, then runs it again with --confirm. |
--confirm <digest> |
With --env production: the digest printed with the plan, which confirms that plan. If your templates or production changed since, the plan has another digest, and nothing is pushed. |
A write: it also takes the options every write takes.
$ npx routingcat pushlumen · 1 template · 1 slot, 4 variantsstamps 9 edits layouts/index.html:2 home/hero style="display:contents" layouts/index.html:3 home/hero:current data-rc-hash 58c2042d15ca… layouts/index.html:3 home/hero:current style="display:contents" layouts/index.html:6 home/hero:oneclick data-rc-hash c4a2d99ee3bd… layouts/index.html:6 home/hero:oneclick data-nosnippet layouts/index.html:9 home/hero:studio data-rc-hash 975b5d620955… layouts/index.html:9 home/hero:studio data-nosnippet layouts/index.html:12 home/hero:best data-rc-hash c37fb91bad3a… layouts/index.html:12 home/hero:best data-nosnippetpartial layouts/partials/routingcat-head.html: writtenroutingcat LIVE home: 4 variants in hero, routing within about 2 minutes (ch_01m40j70srer495wr3vhnyvjca, pb_01m40j70srfb59gbvnmr3nv2t6)build
npx routingcat buildThe build step of a site declared in routingcat.config.json whose host’s middleware decides, on Vercel or in a Cloudflare Worker: add it after your generator in the build command, as hugo && npx routingcat build. It fails while push would still edit a template or write the head partial again, registers the deployment with the build’s credential, and writes the values your middleware imports to .routingcat/. Without a credential, or with RoutingCat unreachable, the build still ships, and visitors get your defaults. On your own machine, without a credential, it writes development values instead, under which wrangler dev and vercel dev show the variants ?rc= names and record nothing. There, for a site on Cloudflare, ROUTINGCAT_ENV=production asks it for production, under your sign-in alone, a ROUTINGCAT_DEPLOY_KEY ignored: it prints the plan, as plan shows it, with a warning where your folder holds changes not committed or is on another branch than the site’s production branch, and registers that plan once you confirm it. A site another host builds is refused before the plan. At a terminal, it asks. Run by an agent, or with --json, it registers nothing and ends with exit code 3 and the plan’s digest, so that the agent shows you the plan, then runs it again with ROUTINGCAT_CONFIRM set to that digest, beside ROUTINGCAT_ENV=production.
$ npx routingcat buildroutingcat LIVE home: 4 variants in hero, routing within about 2 minutes (ch_01m40j70srer495wr3vhnyvjca, pb_01m40j70srfb59gbvnmr3nv2t6)Stability
The routingcat package follows semantic versioning for what this section lists: within 1.x, a minor or patch version only adds to it, and never removes or changes what it names. A change to any of it waits for 2.0.
- The stable entries, each with the names it exports, values and types:
routingcat/next:config,ConfigDeclaration,Decided,DeclarationError,fromMinorUnits,IdentifyOptions,page,PageDeclaration,RoutingCat,routingcat,routingcatProperties,slot,SlotDeclaration,SlotResult,TrackOptions,variant,VariantDeclarationandwithRoutingCat.routingcat/next/proxy:proxy,ProxyHandlerandProxyOptions.routingcat/browser:IdentifyOptions,InitOptions,LineItem,routingcat,routingcatPropertiesandTrackOptions.routingcat/tracker:navigate,prepareNavigation,routingcat,routingcatProperties,startandStartOptions.routingcat/outcomes:AccountOrigin,AccountSource,Ask,CancelEffectiveProperties,CancelScheduledProperties,currencyExponent,CustomerType,DEFAULT_LINK,DisputeProperties,DisputeState,Dropped,DropReason,Env,EveryOutcome,fromMinorUnits,GetRead,Item,JsonValue,LinkConfig,LinkOut,ListRead,MapContext,Mapped,Mapping,Mode,MoneyOutcome,Need,needKey,Outcome,OutcomeName,OutcomeOfName,PaymentProperties,PriorOutcome,Projected,Projection,Provider,PurchaseProperties,Read,ReadRecord,ReadResult,Reads,readsOf,RefundProperties,RenewalProperties,run,RunResult,shopify,SHOPIFY_API_VERSION,SHOPIFY_API_VERSIONS,SHOPIFY_TYPES,ShopifyDelivery,shopifyKey,ShopifyKeyInput,ShopifyOrderProperties,shopifyTrigger,stripe,STRIPE_API_VERSION,STRIPE_API_VERSIONS,STRIPE_TYPES,stripeKey,StripeKeyInput,stripeReadPath,stripeTrigger,TrialStartedProperties,Trigger,ValueBasis,versioned,VisitorLink,whop,WHOP_API_VERSION,WHOP_API_VERSIONS,WHOP_TYPES,WhopDisputeState,whopKey,WhopKeyInputandwhopTrigger.routingcat/vercel:ASTRO_DESCRIPTOR,FrameworkDescriptor,HTML_DESCRIPTOR,middleware,MiddlewareContext,MiddlewareHandler,MiddlewareOptions,NUXT_DESCRIPTORandRoutingCatBuild.routingcat/cloudflare:ASTRO_DESCRIPTOR,CloudflareOptions,CloudflareWorker,FrameworkDescriptor,HTML_DESCRIPTOR,NUXT_DESCRIPTOR,pages,PagesContext,PagesOptions,RoutingCatBuild,worker,WorkerContextandWorkerOptions.routingcat/vue:config,ConfigDeclaration,DeclarationError,IdentifyOptions,page,PageDeclaration,routingcat,routingcatProperties,RoutingCatSlot,RoutingCatSlotProps,RoutingCatSlotScope,RoutingCatState,ServerEvent,Shown,slot,SlotDeclaration,TrackOptions,useRoutingCat,variantandVariantDeclaration.routingcat/nuxt:defaultandRoutingCatNuxtOptions.routingcat/astro:config,ConfigDeclaration,DeclarationError,default,IdentifyOptions,page,PageDeclaration,RoutingCatAstroOptions,RoutingCatLocals,Shown,slot,SlotDeclaration,TrackOptions,variantandVariantDeclaration.routingcat/astro/components:RoutingCat,RoutingCatProps,RoutingCatSlotandRoutingCatSlotProps.routingcat/runtime:CORE_VERSION,CoreAttributes,CoreConfig,RcState,RoutingCatPage,runCore,runPostandViewState.
- The files the package exports beside them, at their paths and in their formats:
routingcat/runtime/core.js: the browser runtime’s core, which a head partial carries inline.routingcat/runtime/post.js: its post part, which a head partial loads from cdn.routingcat.com.routingcat/runtime/manifest.json: their versions, hashes and sizes, formatrc.runtime/1.routingcat/schemas/*: the JSON Schema of each command’s output, a failure and the config.
- The experimental entries,
routingcat/react-router,routingcat/fetch,routingcat/reactandroutingcat/vite, are outside the promise, each as a whole: any version may change them, a patch included, until RoutingCat supports Hydrogen storefronts. Their declarations say@experimental, and a name they share with a stable entry, as the declarations, is stable through that entry alone. - The environment variables you may set, every other one starting with
ROUTINGCAT_being RoutingCat’s own, for its tests, which may change:ROUTINGCAT_DEPLOY_KEY: a build’s credential where no Vercel or GitHub Actions OIDC token proves it, fromnpx routingcat keys deploy.ROUTINGCAT_BRANCH: the branch a CI RoutingCat does not recognize builds.ROUTINGCAT_ENV: set toproduction, a production build on your own machine, for a site on Cloudflare.ROUTINGCAT_CONFIRM: the digest a production build’s plan printed, which confirms that plan on the next run.ROUTINGCAT_CONFIG_DIR: the folder where the command keeps its sign-in when it does not use the keychain.ROUTINGCAT_KEYCHAIN: set tooff, the command keeps its sign-in in that folder, never the keychain.
- Messages keep their id, as
D2, and their code, asnext_insecure: neither is ever reused for another message, and a code’s parameters keep their names and meaning. Their words may change, so read the id and the code, never the text (Messages). - The JSON contract: every
--jsonoutput statescontract,1.0. Within version 1 it only grows: new fields, and new values where a field’s values are listed, never one removed, renamed or retyped. The JSON Schema of each command’s output and of a failure ships innode_modules/routingcat/schemas/, whichschemas/index.jsonlists with the package’s and the contract’s versions (JSON output). - Exit codes, and no other (the command):
0: done.1: rejected, denied or failed, with a message id.2: the command or its arguments are wrong.3: waiting, on a person or on time: run the same command again later.4: RoutingCat can’t be reached.