Static API
Static hosts the front end — whatever your build step produces. Point it at that directory and it is live on a subdomain of its own, served from cache at the edge, with bandwidth that is not metered.
Deploying
A deploy is one command against your build output directory:
npm run build
altengine static deploy ./dist142 files, 3.1 MiB
uploading 7 files (135 already stored)
7/7
deployed 8f21c4a90b6e — 142 files, 3.1 MiB
https://marketing-web.altengine.app
live here now; everywhere within about a minuteFiles are hashed locally first and only the ones this site does not already have are uploaded, so a redeploy that changed one page uploads one page.
Deployments and rollback
A deploy creates a deployment and activates it. Activation is the only thing that changes what visitors see, and every earlier build is kept, so switching to one is instant and uploads nothing.
altengine static list # deploy history, * marks the live one
altengine static rollback <deployment-id> # switch to any of them
altengine static info # where it lives, which build is live--no-activate uploads a build without publishing it, so a release can be staged and switched on later:
altengine static deploy --no-activate ./dist # upload now
altengine static rollback <deployment-id> # publish when you are readyA visitor gets one build or the other, never a mix. A location already serving the old build can take about a minute to pick up the new one, so give a deploy that long before testing it from a machine that was just on the site.
How a path is resolved
In order, and the first match wins:
| Request | Serves |
|---|---|
/assets/app.js | That exact file. |
/, /docs/ | index.html in that directory. |
/docs | A 301 to /docs/, when /docs/index.html exists. |
/about | /about.html — clean URLs, on by default. |
| anything unmatched | /index.html with a 200 if the site is a single-page app, otherwise /404.html with a 404. |
/docs redirects rather than serving the index directly, or every relative link on that page would resolve one level too high.
Everything in the directory is deployed, dotfiles included — .well-known/ is served like any other path.
Single-page apps
Turn SPA mode on for a client-routed app: an unmatched path gets /index.html with a 200, so a deep link works on a cold load. Leave it off for a generated site, where an unmatched path should 404.
Caching
Two headers, decided per file from its name:
| File | Sent |
|---|---|
app-4f2a91bc.js | max-age=31536000, immutable — a year in the visitor's browser. |
index.html | max-age=0, must-revalidate in the browser, cached at the edge. The browser asks on every view and gets a 304. |
A file is frozen for a year only when its name carries a content hash: a run of at least eight alphanumerics including a digit, before the extension. app-4f2a91bc.js and index.a1b2c3d4.css qualify; logo-2024.png is too short and about-something.html has no digit. If your assets are not being frozen, that is the naming to check in your build.
There is no purge step. Each build is cached under its own key, so a deploy is live immediately and a rollback lands on a build the edge still holds.
The ETag is the file's content hash, so a revalidation costs no read.
Running locally
The same CLI serves a directory the way this service serves a deployment:
altengine dev --static ./dist
# → http://127.0.0.1:9191 API + console
# → http://127.0.0.1:9192 the siteThere is no local deploy step: the directory is read on every request, so a rebuild shows up on the next reload. The site listens on its own port, which keeps root-absolute URLs (/assets/app.js) working and puts the site and the API on different origins — the same split as production, so a fetch that CORS will refuse locally will refuse deployed.
Resolution, content types and ETags match this page. Two things differ: every response is no-cache rather than a year, and the trailing-slash redirect is a 302. The startup banner counts how many of your assets will be cached for a year once deployed.
--spa is detected from the directory when you do not pass it — one page and no 404.html reads as a client-routed build — and the banner says what it decided. Pass --spa or --spa=false to set it yourself.
The API
The CLI is the intended interface, and the console deploys a dropped folder. For your own pipeline, a deploy is three calls — send a manifest, upload what is missing, activate:
| Endpoint | Does |
|---|---|
POST /v1/static/{instance}/deployments | Send {files: {"/index.html": {hash, size}, …}}. Replies with the deployment id and signed upload links for only the files not already stored. |
GET /v1/static/{instance}/deployments/{id}/uploads | The next page of those links, when the first reply carried a cursor. |
POST /v1/static/{instance}/deployments/{id}/activate | Publish it. Verifies every file arrived first. |
GET /v1/static/{instance}/deployments | History, newest first, with a cursor. |
GET /v1/static/{instance}/deployments/{id}/files | What is in one build. |
GET /v1/static/{instance}/site | The URL, the live deployment, and totals. |
Hashes are SHA-256 of the file's bytes, hex. Creating, uploading and activating need a write grant; reading history needs read.
Limits and settings
| Setting | What it does |
|---|---|
| SPA mode | Serve index.html with a 200 for unmatched paths. Off by default. |
| Clean URLs | Resolve /about to /about.html. On by default. |
| 404 page | The path served for a miss when SPA mode is off. /404.html by default. |
| Maximum file size | The largest single file a build may contain. 25 MB by default, up to 100 MB. Anything larger belongs in Blob and should be linked to. |
| Files per deployment | 5,000, and not a setting. |
| Deployments kept | The most recent 10, plus whichever is live. Older ones are pruned, and the files no remaining build references go with them. |
| Rate limit | Bounds deploys and API calls. It does not apply to serving the site — a limit on page views would throttle you for succeeding. |
What it costs
Requests and stored bytes. Bandwidth is not metered.
Storage is deduplicated across builds — ten deployments of a 40 MB site that changed one page store about 40 MB — sampled hourly and averaged over the month. Every served request is billed, cache hit included: a million page views is about a dollar. See pricing for the numbers.