BETAone9x Pages is in public beta — deploy today, plans and pricing land soon. Get access →
documentation

one9x Pages docs

Everything you need to deploy a static site, end to end — from your first deploy to custom domains and agent workflows. What's further out is kept in its own section, so you always know what's part of the product.

Getting started

Pages hosts static sites — anything that builds down to plain files (HTML, CSS, JS, images, fonts). If your framework produces a dist/ or build/ folder, you can deploy it. There's no runtime and no server config: you build locally (or in CI), and we serve the files.

The whole flow is three steps:

  1. Install the CLI and sign in with one9x login.
  2. Run one9x pages release ./dist --site mysite --deploy.
  3. Open the HTTPS URL it prints.

Everything on this page is part of the launch product: signed-in deploys to a platform subdomain (mysite.one9x.app) over HTTPS, atomic deploys, staged releases with preview URLs, one-command rollback, custom domains with managed certificates, and deploy from an AI agent over MCP. Paid tiers are on the roadmap.

Install & sign in

The CLI is a single static binary — the same one9x binary used across every one9x product, with Pages as a subcommand group. Install it, then sign in once to link the CLI to your account.

install & sign in
$ curl -fsSL https://get.one9x.com | shinstalled one9x → /usr/local/bin$ one9x loginopening browser to authorize …✓ signed in as you@example.com
install & sign in
$ curl -fsSL https://get.one9x.com | shinstalled one9x → /usr/local/bin$ one9x loginopening browser to authorize …✓ signed in as you@example.com
powershell
$ irm https://get.one9x.com/install.ps1 | iexinstalling one9x (windows/amd64) …✓ one9x.exe → %LOCALAPPDATA%\Programs\one9x$ one9x login✓ signed in as you@example.com

The macOS and Linux installer is a shell script and drops the binary in /usr/local/bin. The Windows one drops one9x.exe in %LOCALAPPDATA%\Programs\one9x and adds it to your PATH — no admin rights needed, and it works in the same terminal.

Prefer to place it yourself? The binaries are plain downloads — get.one9x.com/<version>/one9x-windows-amd64.exe (or -arm64), with the current version at get.one9x.com/VERSION. macOS and Linux builds sit alongside them under the same paths.

one9x login authorizes the CLI against your one9x account and stores a token locally — there's no API key to copy around, and it renews itself so you sign in once per machine. Every Pages command runs as you, against your sites. For CI, cron and agents, mint a machine token with one9x tokens create <name> (or from the dashboard) and set it as ONE9X_TOKEN — it lasts 90 days, extends without changing the secret, and you can revoke it any time:

ci
$ one9x tokens create "ci"one9x_tok_7Kd…  (shown once)$ export ONE9X_TOKEN=one9x_tok_…$ one9x pages release ./dist --site mysite --deploy✓ deployed

Signup is open — you need an account before the CLI can log in.Create one, confirm your email, then run one9x login.

Deploy a directory

Point the CLI at your build output and give the site a name with --site. The site name is a DNS label — lowercase letters, digits and hyphens, 1–63 characters — and it becomes your subdomain.

release
$ one9x pages release ./dist --site mysite --deployhashing ./dist … 248 filesuploading … 41 changed, 207 unchanged✓ v8 live in 1.4s→ https://mysite.one9x.app/

The CLI hashes your files locally and uploads only the ones that actually changed since your last release, so the first push is quick and every push after is quicker — an unchanged re-release uploads nothing. The new version goes live in a single atomic flip: visitors are served either the old version or the new one, never a half-uploaded mix. The full mechanics are in How deploys work.

--deploy is what makes it live. Leave it off and you get a staged release with a preview URL instead.

Build your command

Tick what you need and copy the result. Every flag here is one the binary actually takes — the full list is in the CLI reference.

Routing

--error takes <code>:<path> and repeats — one row per status.

More flags

What gets uploaded

Everything in the directory except dot-paths — .git, .env and anything else beginning with a dot stays on your machine. .well-known is the one exception, because it's a reserved public path: assetlinks.json and apple-app-site-association only work at that exact URL, so Android App Links and iOS universal links verify.

To hold anything else back, pass --exclude with a glob. It repeats, and each pattern is matched against the path relative to your directory and against the file name — so '*.map' catches source maps at any depth, while 'admin/*' stays where you wrote it. Excluding a directory drops everything under it. Quote the pattern, or your shell will expand it first.

exclude
$ one9x pages release ./dist --site mysite --deploy \  --exclude '*.map' --exclude admin12 path(s) excluded✓ v9 live

Releases & previews

Building and going live are two verbs, not one. release uploads a directory and creates an immutable version; deploy --to decides which version production serves. Adding --deploy to a release does both in one step, which is the common case.

Without --deploy the release is staged: it exists, it's fully uploaded, and it's reachable at its own preview URL — but production keeps serving whatever it was already on. Check the preview, then promote it.

stage, check, promote
$ one9x pages release ./dist --site mysite✓ v9 staged · production still on v8→ https://v9--mysite.one9x.app/$ one9x pages deploy --site mysite --to v9active version v8 → v9✓ Live · nothing re-uploaded

A preview URL is v<n>--<site>.one9x.app — the same content, the same edge, the same certificate, served from the same immutable files. It costs nothing extra to keep around: previews share every unchanged file with production. Preview URLs are public, so treat them as unlisted rather than private.

Naming a release

Every release gets an automatic per-site number — v1, v2, v3 — and you can add your own label with --tag. Both work as a --to target, as does a version-hash prefix.

tags
$ one9x pages release ./dist --site mysite --tag prod✓ v10 staged · tag prod$ one9x pages deploy --site mysite --to prod✓ Live

How long releases are kept

Retention is count-based, not time-based, so how far you can roll back doesn't depend on how often you ship. The last 5 untagged releases are kept on the free tier — paid tiers keep more — and older ones age out. Tagged releases never age out and don't use up one of those slots, but there's a cap of 5 tags per site — at the cap a tagged release fails with an error rather than quietly dropping one of your tags. one9x pages releases rm frees a slot.

SPA mode & error pages

Two options cover the common routing cases for static sites.

Single-page apps

Add --spa and any path that doesn't match a file is served /index.html with a 200 — so client-side routers (React Router, Vue Router) handle deep links instead of returning a 404.

spa
$ one9x pages release ./dist --site myapp --spa --deploy✓ Live · unknown routes → /index.html

Custom error pages

Map a status code to a page in your build with --error code:path. The flag repeats, so you can set as many as you need.

error pages
$ one9x pages release ./dist --site mysite --deploy \  --error 404:/404.html \  --error 403:/403.html✓ Live · custom 404, 403

Release history

Every release is recorded for the site, newest first. The STATUS column says what each one is: live is the release production is serving, staged is one that exists but hasn't been deployed, and kept is a tagged release held back from retention.

history
$ one9x pages releases list --site mysiteSEQ  VERSION           TAG    WHEN     STATUSv9   sha256:5c1904ab          2m ago   stagedv8   sha256:9f2a1b3c   prod   1h ago   livev7   sha256:41de88a0          3h agov6   sha256:aa03f1cc   rc1    1d ago   kept

A version id is the content hash of that release's manifest. The release number (v8) and any tag are the friendlier way to refer to it; the hash is there when you want to be certain which bytes you mean, and a prefix of it works anywhere a release ref does.

Delete one with one9x pages releases rm v6 --site mysite. The live release can't be deleted — deploy another one first.

Rolling back

Because every version is an immutable manifest of immutable, content-addressed files, no release ever overwrites another. Rolling back is just re-pointing the site at an earlier one — the files are already there, so it takes effect instantly with nothing to rebuild and nothing to re-upload. Keeping a long history is nearly free: shared files are stored once, so you're never charged twice for the same bytes.

There's no separate rollback command, because rolling back is the same operation as going live: deploy --to points the site at a release. It takes a release number, a tag, or a version-hash prefix.

roll back
$ one9x pages deploy --site mysite --to v6active version v7 → v6✓ Live · nothing re-uploaded

An empty --to "" means the release before the active one, which is the one you want when a deploy has just gone wrong and you don't want to go looking up a number first.

Custom domains

Every site is reachable immediately at its platform subdomain (mysite.one9x.app) over HTTPS, with no setup. To serve it on your own domain, link the domain to the site, prove you own it with a DNS record, and point your DNS at us — we issue and renew the TLS certificate automatically.

domains is a top-level command, not part of pages: a domain points at a site, whatever product runs it.

custom domain
$ one9x domains link example.com --site mysite1. TXT    _one9x-challenge.example.com  ->  9f2a…2. CNAME  example.com  ->  sites.one9x.app$ one9x domains verify example.com --site mysite✓ verified · certificate issued→ https://example.com/

link prints both records with your own token filled in — copy them from there rather than from this page. Two records, and both are required:

The two DNS records

Ownership. A TXT record at _one9x-challenge.example.com holding the token link gave you. Only whoever controls the domain's DNS can publish it, which is what makes it proof — pointing DNS at us shows that someone did, not who.

Routing. A CNAME from your domain to sites.one9x.app. DNS does not allow a CNAME at the apex (example.com with no subdomain), so there the record depends on what your DNS provider supports:

Name Type Value
_one9x-challenge TXT the token link printed
www (or any subdomain) CNAMEpreferred sites.one9x.app
@ (apex, ALIAS support) ALIAS / ANAMEpreferred sites.one9x.app
@ (apex, no ALIAS) Afallback 167.104.222.9

Prefer ALIAS or ANAME at the apex where you can. Cloudflare, Route 53 and DNSimple all support it; GoDaddy does not, which is why the A record exists. The difference matters over time: an ALIAS follows us if our edge address ever changes, while an A record is a number in your zone file that only you can update. We will give notice before ever changing it, but a record we can follow is better than a record you have to remember.

Order does not matter, and neither record expires. Verification is checked against public resolvers rather than our own, so give a new record a minute to propagate before running verify — and if it fails, the message names what we actually saw, which is usually enough to spot a typo or a proxy that is intercepting the name.

The certificate is issued the moment ownership is proved, loaded on the first request to your domain, and renewed for you — there's nothing to install or rotate. A domain and its www count as one, so example.com and www.example.com together use a single slot.

Custom domains are a paid-plan feature — Starter includes one, Pro five — and the free tier serves *.one9x.app only. Paid plans are not purchasable yet, so if you need a custom domain right now, get in touch and we will enable it on your account.

Taking a domain off a site

Two commands, and the difference matters. domains unlink example.com stops the domain routing but you keep the name and its certificate — so linking it again later, to this site or another one, is usually instant. It stays in domains list as unlinked. Neither this nor release needs --site; the owning site is looked up from the name.

domains release example.com gives the name up entirely: the record and its certificate are deleted and another account may claim it. That one can't be undone, so it asks for confirmation first.

Deploy from an agent

A deploy is a single authorized action, so it maps cleanly to a single agent tool call. Our MCP server exposes Pages as tools an AI agent can call directly — hand it a built directory and it ships a live URL, with no terminal and no shell glue. It runs through the same signed-in account as the CLI, so an agent only ever touches your sites.

claude · mcp
tool: release  dir:    ./dist  site:   mysite  deploy: true✓ Deployed → https://mysite.one9x.app/

The server exposes release, deploy, releases_list and sites_list — the same operations as the CLI, returning structured JSON rather than prose — and is published to the public MCP directories so agents can discover it. See the developers page for the agent setup.

CLI reference

One binary, one9x. Products are subcommand groups (pages); sites and domains are account-level nouns shared across products. Everywhere a site is named it's the --site flag, and it takes the site's name — its subdomain — rather than an id.

Global flagDescription
--portal <url> optional Portal base URL. Also ONE9X_PORTAL; defaults to portal.one9x.com.
--json optional Emit machine-readable JSON on stdout. Progress goes to stderr.

one9x login / logout / whoami

Authorize the CLI against your one9x account and store a token locally. Run once per machine — the login renews itself, so it doesn't expire out from under you. On a server with no browser, add --no-browser and approve the printed URL from any other device. In CI, skip login entirely and set ONE9X_TOKEN to a machine token.

one9x tokens

Machine tokens authenticate CI, cron and agents with no browser. tokens create <name> mints one and prints it once — store it then, because it is hashed and can never be shown again. Tokens last 90 days by default (--expires-in to choose, up to a year); tokens extend <id> pushes that out without changing the secret, so CI keeps working with the value it already has. tokens list shows what exists, when each was last used and when it expires, and tokens revoke <id> kills one immediately.

one9x pages create <name> / list / rm

pages create <name> makes a new site — the name becomes <name>.one9x.app and is what you pass to --site. pages list shows your sites and their status, and pages rm --site <site> deletes one, after confirming.

one9x pages release <dir>

Upload a directory as a new release. Staged unless you pass --deploy.

FlagDescription
--site <site> required Site name (subdomain), host, custom domain, or numeric id.
--deploy optional Make this release live immediately instead of staging it.
--spa optional Serve /index.html (200) for unmatched routes.
--error <code>:<path> repeatable Map an HTTP status code to a page in the build.
--tag <name> optional Label the release. Tagged releases survive retention.
--exclude <glob> repeatable Leave matching paths out of the release. Matched against the path relative to the directory and against the file name.

one9x pages deploy

Make an existing release live. Uploads nothing — this is both go-live and rollback.

FlagDescription
--site <site> required Which site to re-point.
--to <ref> required Release number (v6), tag (prod), or hash prefix. --to "" is the previous release.

one9x pages releases

releases list --site <site> shows a site's history, newest first, with each release's number, tag and status. releases rm <ref> --site <site> deletes one, freeing a tag slot. The live release can't be deleted.

one9x domains at launch

Custom domains, top-level rather than under a product. domains link <domain> --site <site> routes a name to a site and starts verification, domains verify <domain> --site <site> checks DNS and advances it, and domains list shows every name on the account (or one site's, with --site).

domains unlink <domain> stops a name routing but keeps it and its certificate; domains release <domain> gives it up entirely. Neither takes --site.

one9x completion

Generates a completion script for bash, zsh, fish or powershell. Beyond command and flag names, values complete against your account: --site offers your site names, --to offers that site's releases and tags, and the domain commands offer your domains. If you're logged out or offline it silently offers nothing rather than erroring into your prompt.

How deploys work

Pages uses a “git-push” style deploy: the work of figuring out what changed happens on your machine, so the network only ever carries new bytes.

  1. Hash locally. The CLI walks your directory, computes the SHA-256 of each file, and builds a manifest mapping every path to its content hash.
  2. Ask what's missing. It sends the manifest up; the service replies with just the hashes it hasn't seen before.
  3. Upload only those. The CLI uploads only the missing blobs, addressed by hash. Unchanged files — frameworks, libraries, images shared with earlier deploys — are never re-sent.
  4. Publish atomically. Once every blob is present, the manifest is stored as the new release. Going live is a separate step: the site's active-version pointer flips to that release in one operation. That flip is the whole of both deploying and rolling back, which is why a staged release costs nothing to keep and a rollback is instant.

Every file is stored once by its content and served with long-lived, immutable caching, so repeat visits and files shared across sites load instantly. Nothing is ever mutated in place, which is what makes deploys atomic and rollbacks free.

Coming soon

Designed and on the roadmap, kept separate from the launch product above so the docs only describe what's part of the release.

planned

Paid tiers

Starter, Pro and Max — more sites, storage and custom domains, plus rollbacks, password-protected sites and an uptime SLA — open up as billing and tier limits go live.

planned

Analytics

Per-site request and bandwidth numbers in the dashboard, without adding a script to your pages.

planned

Multi-region serving

Serve a site from more than one region at once — closer to your visitors, no migration. A setting you flip, not a move you make.