← Back to API docs

Docs

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. Put your own name in front of it with a custom domain.

Deploying

Install the CLI, then tell it where to deploy and with what. The key needs write on the static instance; the three variables are read by every altengine command, so this is once per shell (or once in CI):

alt install altlimit/altengine        # the CLI, if you do not have it

export ALTENGINE_URL=https://api.altengine.net
export ALTENGINE_KEY=ae_…              # an org API key with write on this instance
export ALTENGINE_INSTANCE=marketing    # the static instance's name

A deploy is then one command against your build output directory:

npm run build
altengine static deploy ./dist
142 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 minute

Files 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. Each upload link a deploy signs is billed as a write, alongside requests and stored bytes.

Deployments and rollback

A deploy creates a deployment and activates it. Activation is the only thing that changes what visitors see, and earlier builds are 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

--dry-run reports what would be uploaded and stops. --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 ready

To remove a build, delete it from the site's deployment list in the console, or with the static_delete_deployment MCP tool. The live build cannot be deleted; activate another first.

A URL for every build

Each deployment has a number, and every ready deployment is served at its own address, whichever build is live:

https://marketing--v7-web.altengine.app/   # deployment 7
  • Numbers are never reused. A deleted build's address stops serving within about a minute.
  • Versioned responses send x-robots-tag: noindex.
  • Versioned addresses work on the platform hostname only, not on a custom domain.
  • A subdomain may not contain --.

A 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:

Path resolution order
RequestServes
/assets/app.jsThat exact file.
/, /docs/index.html in that directory.
/docsA 301 to /docs/, when /docs/index.html exists.
/about/about.html — clean URLs, on by default.
anything unmatched/404.html with a 404. On a single-page app, a page request gets /index.html with a 200 instead.

/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

SPA mode serves /index.html with a 200 for an address that is not a file, so a deep link works on a cold load. It is turned on for you when a site's first deployment goes live and looks like a client-routed build: one page, at the root, and no 404.html. Set it yourself any time in the console — later deploys never change it back.

A missing script, stylesheet, image or fetch() still gets a 404. The shell goes only to requests the browser is loading as a page, so /edit/something.js typed into the address bar reaches your router, while a bundle that is not there fails as a missing file instead of arriving as a page of HTML.

Caching

Two headers, decided per file from its name:

Cache headers by file type
FileSent
app-4f2a91bc.jsmax-age=31536000, immutable — a year in the visitor's browser.
index.htmlmax-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 site

There 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, by the same rule your first deployment uses, 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:

Static API endpoints
EndpointDoes
POST /v1/static/{instance}/deploymentsSend {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}/uploadsThe next page of those links, when the first reply carried a cursor.
POST /v1/static/{instance}/deployments/{id}/activatePublish it. Verifies every file arrived first.
GET /v1/static/{instance}/deploymentsHistory, newest first, with a cursor. Each deployment carries its number and versioned url.
GET /v1/static/{instance}/deployments/{id}/filesWhat is in one build.
GET /v1/static/{instance}/siteThe URL, the live deployment, and totals.
GET /v1/static/marketing/deployments

{ "deployments": [
    { "id": "c09d3e71a2f4…", "number": 8, "url": null, "status": "pending", … },
    { "id": "8f21c4a90b6e…", "number": 7, "url": "https://marketing--v7-web.altengine.app/",
      "status": "ready", "file_count": 142, "total_bytes": 3251200, "message": null, … } ],
  "cursor": null,
  "active": "8f21c4a90b6e…",
  "url": "https://marketing-web.altengine.app/" }

url is null until a deployment is ready.

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

Static limits and per-instance settings
SettingWhat it does
SPA modeServe index.html with a 200 for unmatched page requests. Set from the shape of your first deployment.
Clean URLsResolve /about to /about.html. On by default.
404 pageThe path served for a miss when SPA mode is off. /404.html by default.
Maximum file sizeThe 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 deployment5,000, and not a setting.
Deployments to keepkeepDeployments: the newest ready deployments kept, 10 by default, 1–50. The live one is always kept, however old. Older ones are deleted after each deploy, with the files no remaining build references; lowering the setting deletes the extras when you save.
Rate limitBounds 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.