Create releases — POST /v1/projects/{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.

POST /v1/projects/{slug}/releases

Addresshttps://api.hanzo.ai/v1/projects/{slug}/releases
MethodPOST
Operationpost_projects_by_slug_releases
AuthAuthorization: Bearer $HANZO_API_KEY

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. Creating a release is free. A retained release is stored bytes, so it counts toward the org's storage until retention reclaims it; the storage gate runs before any copy, refusing (402) an org past the free tier that cannot pay a month of what it holds.

Scope: a validated principal is required (401 without one) and the site is resolved within that principal's org, so another tenant's slug is a 404.

Request

3 fields, body application/json (required).

FieldInTypeRequiredDescription
slugpathstringyesSlug is the site to publish, from the path.
slugbodystring—Slug is the site to publish, from the path.
sourcebodystring—Source is the build output to promote, as a path RELATIVE to your org's own storage space — never a URL and never a bucket.

Response

StatusBodyMeaning
201project.projectsReleasecreated
defaultproblem-detailsrefused

201 body — 8 fields.

FieldInTypeAlwaysDescription
activebodyboolean—Active is whether this is the release the site is SERVING right now.
bytesbodyinteger (int64)—Bytes is their total size in bytes.
createdAtbodyinteger (int64)—CreatedAt is when the release was cut, as Unix seconds — not when it was last activated.
objectsbodyinteger (int64)—Objects is how many files the release holds.
releaseIdbodystring—ReleaseID is derived from a DIGEST of the release's own manifest, so identical content is the same release and a release can never be confused with another one.
slugbodystring—Slug is the site this release belongs to.
sourcebodystring—Source is what the release was cut from — the build output or upload it was promoted out of.
urlbodystring—URL is where the site serves.

Failure carries the platform error shape — see Errors.

Examples

hanzo projects releases create <slug>
import { Configuration, ProjectApi } from 'hanzoai';

const api = new ProjectApi(new Configuration({ accessToken: process.env.HANZO_API_KEY }));
const { data } = await api.postProjectsBySlugReleases({ slug: 'slug', slug: "<slug>", source: "<source>" });
from hanzoai.cloud import ApiClient, Configuration
from hanzoai.cloud.api import ProjectApi

client = ApiClient(Configuration(access_token=os.environ["HANZO_API_KEY"]))
result = ProjectApi(client).post_projects_by_slug_releases(slug='slug', slug="<slug>", source="<source>")
cfg := hanzoai.NewConfiguration()
cfg.AddDefaultHeader("Authorization", "Bearer "+os.Getenv("HANZO_API_KEY"))
client := hanzoai.NewAPIClient(cfg)

resp, _, err := client.ProjectAPI.PostProjectsBySlugReleases(context.Background()).Execute()
if err != nil {
	return err
}
use hanzo_client::apis::{configuration::Configuration, project_api};

let mut cfg = Configuration::new();
cfg.bearer_access_token = std::env::var("HANZO_API_KEY").ok();

let result = project_api::post_projects_by_slug_releases(&cfg, Default::default()).await?;
import ai.hanzo.cloud.ApiClient;
import ai.hanzo.cloud.api.ProjectApi;

ApiClient client = new ApiClient();
client.setBearerToken(System.getenv("HANZO_API_KEY"));

var result = new ProjectApi(client).postProjectsBySlugReleases();

The method above is the one at the current release of the document. hanzoai@8.5.914 (npm) and hanzoai@8.5.914 (PyPI) were generated from an earlier release, where this operation carried a different id, so it spells the method differently — regenerating the clients is what makes the two agree. SDKs →

curl -X POST https://api.hanzo.ai/v1/projects/<slug>/releases \
  -H "Authorization: Bearer $HANZO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "slug": "<slug>",
       "source": "<source>"
     }'

Tool project, op post_projects_by_slug_releases — POST the JSON-RPC envelope to https://api.hanzo.ai/v1/mcp.

curl -X POST https://api.hanzo.ai/v1/mcp \
  -H "Authorization: Bearer $HANZO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "jsonrpc": "2.0",
       "id": 1,
       "method": "tools/call",
       "params": {
         "name": "project",
         "arguments": {
           "op": "post_projects_by_slug_releases",
           "input": {
             "slug": "<slug>",
             "source": "<source>"
           }
         }
       }
     }'

Project API · All Hanzo APIs · Interactive reference

Was this page useful?