Hanzo AI

Projects

Where your sites live: create one, deploy a build, roll back to any release.

Also for this capability: API · CLI · SDKs

Where your sites live: create one, deploy a build, roll back to any release.

Base URLhttps://api.hanzo.ai
Operations29
AuthAuthorization: Bearer $HANZO_API_KEY

Specification

HIP-1231 · Projects — The Site Store — Draft · read the specification →

projects is where an org's sites live: create a project, deploy a build, promote a release, roll back to any earlier one. It is the one org-scoped store of buildable, deployable sites, shared by every surface that shows a user's projects — the builder and the console render the same rows because both call this one surface (apps/projects/projects.go:1-13). It is implemented in hanzoai/cloud at apps/projects. This HIP states the target surface — one address, /v1/projects — and carries the browser tag door, formerly HIP-1068, which is a projects address because the project store is.

Motivation

The capability answered at five top-level addresses for one store, including a fourteen-path mirror of the sites surface under another capability's name. A reader met SitesApi, ProjectsApi, EdgeApi, TagsApi and half a PlatformApi for one subsystem, and two spellings of every release operation. One store is one capability (HIP-0139 §7.1); the mirror is an alias, and there is no alias.

Specification

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as in RFC 2119.

One store

The capability owns one store: the system-namespace projects SQLite database (apps/projects/store.go:211) — project rows, deployment rows, release state, and the global site_hosts table (apps/projects/store.go:267), which binds a globally-unique public hostname to exactly one (org, slug), first-come, with status separating holding a name from serving it. Site bytes live in our S3 under <org>/<slug>/; the store holds the pointer that makes one release live.

The addresses

Every route is under /v1/projects:

  • the collection and per-slug lifecycle — create, list, get, update, delete, deploy, deployments, purge, domains;
  • the site collection ops at /v1/projects/sites — generate from a brief, deploy a raw file manifest, fork, list the org's live sites;
  • releases per slug — publish, promote, list, activate (the rollback);
  • /v1/projects/edge — the CDN state door: provider, reach, cache policy for the org's published sites (apps/projects/edge.go);
  • /v1/projects/tags — the browser tag door below.

The router today still serves four stray roots — /v1/sites, /v1/edge, /v1/tags, and the /v1/platform/sites mirror; each pair is a line in cloud's openapi/misfiled.txt. The first three fold here; the mirror is deleted, not folded — an alias is not one of the ways a misfiled pair closes (HIP-0139 §7). Per-slug operations that already carry the /v1/projects spelling keep it; the second spelling goes.

Operations are typed except the two archive uploads, and the reason is the wire, not effort: their request body is bytes — a zip or tar, raw or as a multipart part — and a typed body is JSON-decoded before the handler runs, so typing them would answer a real archive with 400. Each declares its byte request through the registry instead (apps/projects/projects.go:424-449).

The tag door

GET /v1/projects/tags tells the hosted analytics tag which client-side pixels a site has connected, so it can inject them first-party. It is one public read, resolved per site — from the publishable key when it names a project, otherwise from the request host — and it MUST return non-secret identifiers only: the pixel ids a page would carry in its own markup anyway. Without a resolvable site it answers an empty set at 200, never an error — a page's tag configuration is fetched during load, and a broken page is worse than an unmeasured visit. It MUST be served by the process that holds the project store: served anywhere else it reads nothing and answers empty with a 200, the answer that carries no signal (apps/projects/tagdoor.go).

Tenancy

The org is minted by the gateway from the validated IAM JWT (HIP-0026) and is not an input to any handler; a deploy debit and every cap are keyed on the resolved caller org, hardened against a masquerading admin, with the validated project sub-scope threaded so a forged X-Project-Id can neither hard-stop nor evade a cap (apps/projects/billing.go:30-45). The tag door is deliberately public and can address no other org's project: a key that names no project falls back to the host, and a host that names no site yields the empty set rather than a default.

Metered

The capability is metered (plugin/projects/main.go:22): one flat per-deploy hosting debit — gate, then work, then meter once on success — at CLOUD_HOSTING_FEE_CENTS ($1.00 default; 0 makes deploys free and therefore un-gated), attributed to provider hosting, kind deploy, through the shared cloud.ResourceMeter (apps/projects/billing.go:22-27). A failed deploy is never billed; a redeploy is a distinct billable event that returns the same URL.

Events, observability, stage

The capability publishes no events on the bus, so a customer's webhooks receive nothing from it. It emits build.started, deploy.live and deploy.failed on the in-process lifecycle stream (apps/projects/deploy.go:311,179,162), best-effort and detached, so the chat notifiers can post about a site going live. Beyond the request span it emits structured log lines only. Its stage is ga: the manifest row carries no stage field, and absent means ga (HIP-0139 §8).

Upstreams

The capability forks nothing. It links the Hanzo S3 client (hanzos3/go) to upload site bytes; serving is the static plugin's, and the edge door reports on it rather than implementing it.

Rationale

The alternative to the fold is the mirror: every release operation published twice, under two capabilities, with generated clients disagreeing about which one is real. The alternative to keeping sites inside projects is a split — but the site rows, the release pointer and the hostname table all live in projects' one store, and two apps on one store is the defect HIP-0106 names, so the split is refused by the store rule.

Security Considerations

The wrong implementation leaks through three doors. A tag door that returns anything beyond publishable identifiers turns a public unauthenticated read into a secret oracle for any site on the platform. A hostname binding without the first-come site_hosts table lets one org serve under another's name — the table is the authoritative binding the site server keys on, and status is what stops a claimed-but-unproven custom domain from serving. A deploy path that read the org from the request rather than the validated principal would deploy into, purge, or bill another tenant's site.

Four surfaces

SurfaceReaches this capability asCoverage
RESTprojects at its own prefix29 operations
CLIhanzo projects …29 of 29
SDKProjectsApi in every published client29 methods
MCPtool projects on https://api.hanzo.ai/v1/mcp48 operations, 0 under the document's own id — ask describe for the rest

Quickstart

export HANZO_API_KEY=sk-...   # console.hanzo.ai → API keys

Then the first call — a read that needs nothing but the key. GET /v1/projects, operation get_projects:

hanzo projects list

Answers 200 with projectsProject[] — ok.

Endpoints

EndpointWhat it does
POST /v1/projects/{slug}/deployUpload a built site as one archive and serve it
POST /v1/projects/{slug}/deployments/{id}/completeCompleteDeployment is the CI completion hook that flips a queued git deployment to live (or error) once CI has synced the built site to S3.
GET /v1/projects/{slug}/deployments/{id}Returns one deployment of a project by id.
GET /v1/projects/{slug}/deploymentsReturns a project's deploy history, newest version first.
POST /v1/projects/{slug}/deploymentsOpens a deployment and hands back a short-lived, prefix-scoped grant to write its bytes straight to object storage.
POST /v1/projects/{slug}/domains/{host}/verifyChecks the DNS challenge for a pending custom hostname and, when it passes, promotes the host so it begins routing at the edge.
DELETE /v1/projects/{slug}/domains/{host}Gives a custom hostname back, so the name is free to reuse.
GET /v1/projects/{slug}/domainsReturns every custom hostname this site holds: the live ones, plus any pending claim with the DNS records it still owes.
POST /v1/projects/{slug}/domainsAttaches one or more CUSTOM public hostnames to this org's site.
POST /v1/projects/{slug}/publishPromotes a build output into a new release AND goes live with it — create+activate in one call, which is the 99% path.
POST /v1/projects/{slug}/purgeFlushes the site's edge cache without redeploying anything.
POST /v1/projects/{slug}/releases/{release}/activatePoints the site at an existing release — the go-live, and equally the ROLLBACK.
GET /v1/projects/{slug}/releasesReturns a site's releases newest-first, marking the active one — the rollback menu.
POST /v1/projects/{slug}/releasesPromotes 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…
GET /v1/projects/{slug}/shotGet a PNG of the project's live site
PUT /v1/projects/{slug}/starBookmarks a project for the person calling, and answers whether it is starred afterwards.
DELETE /v1/projects/{slug}/starRemoves the caller's own bookmark from a project, and answers whether it is starred afterwards.
GET /v1/projects/{slug}Returns one project of yours by slug — its settings, its live URL and the deployment currently serving it.
PATCH /v1/projects/{slug}Changes a project's settings, and only the settings you send.
DELETE /v1/projects/{slug}Deletes a project and takes its site off the internet.
GET /v1/projects/edgehealth reports whether a publish reaches readers, rather than whether it was accepted.
POST /v1/projects/forkCreates a project seeded from a PUBLISHED EXAMPLE — either a starter-kit template from the ONE embedded gallery catalog, or any live project on the…
GET /v1/projects/sites/{slug}Returns one site — the same row ListSites carries, for one slug.
POST /v1/projects/sites/deployDeploys a caller-supplied file manifest — the deploy_site capability an agent calls — and answers with where it went live.
GET /v1/projects/sitesReturns the org's deployed sites at the pretty URLs they serve at.
POST /v1/projects/sitesGenerates a self-contained, mobile-responsive static site from a natural-language brief and deploys it live in one call.
GET /v1/projects/tagsThe site's browser tag set for the hosted tag — which pixels to inject, by publishable key
GET /v1/projectsReturns every project your org owns.
POST /v1/projectsCreates a project — the handle a site is deployed and served under — and answers 201 with it in draft.

All Hanzo APIs · Interactive reference

How is this guide?