Sites
Package projects is where your sites live: create one, deploy a build, roll back to any release.
Package projects is where your sites live: create one, deploy a build, roll back to any release.
| Base URL | https://api.hanzo.ai |
| Operations | 7 |
| Auth | Authorization: Bearer $HANZO_API_KEY |
sites
POST /v1/sites/{slug}/publish
Promotes a build output into a new release AND goes live with it — create+activate in one call, which is the 99% path.
It is exactly the two halves in sequence with no extra semantics, so the
staged flow and the one-shot flow can never drift apart: source is promoted
under the same org-relative rule and the same guards CreateRelease applies,
then the site's pointer is flipped to it, the public host is claimed and the
edge is purged. Idempotent on unchanged bytes — same manifest, same release id,
no copy — and billed once, after the release exists.
Scope: a validated principal is required (403 without one) and the site is resolved within that principal's org, so another tenant's slug is a 404.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
slug | path | string | yes | Slug is the site to publish, from the path. |
Request body — application/json (required)
| Field | Type | Required | Description |
|---|---|---|---|
slug | string | — | Slug is the site to publish, from the path. |
source | string | — | Source is the build output to promote, as a path RELATIVE to your org's own storage space — never a URL and never a… |
POST /v1/sites/{slug}/releases/{release}/activate
Points the site at an existing release — the go-live, and equally the ROLLBACK.
Aim it at an older release and the site serves that one again: releases are immutable and retained to the retention depth, so nothing is rebuilt or re-copied and the flip is one atomic statement. Before the flip, two conditions run in the order that gives each its own honest answer — the ROW says whether this release exists for this tenant at all (404, with no signal about a foreign id), and only then do the BYTES say whether it can still serve (410 GONE when retention has reclaimed them; that rollback target is not coming back, so publish again). Going live also claims the public host and purges the edge, so the release is reachable and no cached predecessor is served. NOT billed: no new content is produced, only a pointer moved.
Scope: a validated principal is required (403 without one) and the site is resolved within that principal's org, so another tenant's slug is a 404.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
slug | path | string | yes | Slug is the site the release belongs to, from the path. |
release | path | string | yes | Release is the content-addressed release id ("rel_" + 32 hex chars), from the path. |
GET /v1/sites/{slug}/releases
Returns a site's releases newest-first, marking the active one — the rollback menu.
Each row carries the release id to activate, the source it was promoted from, its object and byte counts, and the URL if it is the one serving. Retention bounds the list, so it is the set that can actually still be rolled back to, not a full history.
Scope: a validated principal is required (403 without one) and the site is resolved within that principal's org, so another tenant's slug is a 404.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
slug | path | string | yes | Slug is the project to act on, from the path. |
POST /v1/sites/{slug}/releases
Promotes a build output into a new immutable release WITHOUT serving it — the staged half of publishing, for when you want to check a release before it goes live. Answers 201.
source is a path RELATIVE to your org's own storage space: the org segment
is prepended server-side from the validated principal and the bucket is never
in the request at all, so a server-side copy can only ever reach bytes your
org already owns. The prefix is listed, content-addressed (SHA-256 over the
sorted manifest of key/size/etag), and copied into an immutable
<org>/.releases/<slug>/<id>/ prefix; the row is written LAST, so a partial
copy is unreachable rather than merely unlikely. Re-publishing an unchanged
source is idempotent BY CONSTRUCTION — same bytes, same id, no copy at all.
The source must contain index.html at its root and stay under the same file and byte caps an artifact deploy does (413 past them); a source that changes mid-copy is a 409 and the release is abandoned. Each publish also reclaims releases past the retention depth, so a site's release space stays bounded. This is the billable half — the hosting gate runs before any copy, and the debit lands once the release exists.
Scope: a validated principal is required (403 without one) and the site is resolved within that principal's org, so another tenant's slug is a 404.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
slug | path | string | yes | Slug is the site to publish, from the path. |
Request body — application/json (required)
| Field | Type | Required | Description |
|---|---|---|---|
slug | string | — | Slug is the site to publish, from the path. |
source | string | — | Source is the build output to promote, as a path RELATIVE to your org's own storage space — never a URL and never a… |
POST /v1/sites/deploy
Deploys a caller-supplied file manifest — the deploy_site capability an agent calls — and answers with where it went live.
files is a list of {path, content} pairs, the same shape the brief build
emits, and it runs through the SAME guards: index.html required at the root,
absolute and traversal paths rejected, per-file and total size capped, and a
mobile viewport meta tag injected into every HTML document that lacks one — so
a hand-built site is exactly as safe and as responsive as a generated one.
slug and name are optional; a slug is derived from the name or minted.
It writes into the SAME org-scoped store as /v1/projects, ensuring a project
(framework static) for the resolved slug and recording a deployment. The
hosting gate runs before the upload and the debit lands once, after the site
is live — a failed upload is never billed. Answers 503 when object storage is
unconfigured.
Scope: a validated principal is required (403 without one) and the site is published into THAT principal's org.
Request body — application/json (required)
| Field | Type | Required | Description |
|---|---|---|---|
files | projectsFile[] | — | |
name | string | — | |
slug | string | — |
GET /v1/sites
Returns the org's deployed sites at the pretty URLs they serve at.
It reads the SAME org-scoped store as /v1/projects and keeps only the projects
that are actually live, so a draft or a failed build is not advertised as a
site.
Scope: a validated principal is required (403 without one) and the list is keyed by that principal's org.
POST /v1/sites
Generates a self-contained, mobile-responsive static site from a natural-language brief and deploys it live in one call.
One inference call turns brief (capped at 8 KiB) into a file manifest, which
then runs through the SAME validation, guards and viewport guarantee as a
hand-supplied manifest: index.html required at the root, absolute and
traversal paths rejected, per-file and total size capped, and a mobile
viewport meta tag injected into every HTML document that lacks one. The
generated site is fully inline — no CDNs, no remote fonts or images — so it is
CSP-safe. slug and name are optional: the model's own title is preferred,
and a slug is derived or minted when none is given.
It writes into the SAME org-scoped store as /v1/projects — it ensures a
project (framework static) for the resolved slug and records a deployment —
so this is a second door onto one publish pipeline, not a second copy of
project state. Ordering is the billing contract: the hosting gate runs BEFORE
any inference or upload, so a denied gate generates and uploads NOTHING, and
the debit lands once, only after the site is actually live. The tokens are
billed to the same ledger the hosting fee was reserved against.
Answers 503 when object storage or inference is unconfigured, and 400 when the model's manifest cannot be parsed or fails the guards.
Scope: a validated principal is required (403 without one) and the site is published into THAT principal's org.
Request body — application/json (required)
| Field | Type | Required | Description |
|---|---|---|---|
brief | string | — | |
model | string | — | |
name | string | — | |
slug | string | — |
How is this guide?