# Deploy projects

URL: https://docs.hanzo.ai/docs/openapi/project/post-projects-by-slug-deploy

> Takes a built site live at `https://<slug>.hanzo.app` in one call.

`POST /v1/projects/{slug}/deploy`

> [Project →](https://docs.hanzo.ai/docs/openapi/project) · [All API references →](https://docs.hanzo.ai/docs/openapi) · [Get started →](https://docs.hanzo.ai/docs/guides)

|               |                                                  |
| ------------- | ------------------------------------------------ |
| **Address**   | `https://api.hanzo.ai/v1/projects/{slug}/deploy` |
| **Method**    | `POST`                                           |
| **Operation** | `post_projects_by_slug_deploy`                   |
| **Auth**      | `Authorization: Bearer $HANZO_API_KEY`           |

Takes a built site live at `https://<slug>.hanzo.app` in one call. The body is the site itself — a `zip` or `tar.gz` holding `index.html` at its root (or a single wrapper directory that does), sent raw or as a multipart file part. It is unpacked to the site's own storage prefix and served immediately, answering the finished deployment.

It is bounded by the edge body limit (16 MiB by default), and that bound is the whole reason the other path exists: an oversized POST is refused by the server BEFORE any handler runs and surfaces as an opaque `400 Error when parsing request` that reads like a malformed payload rather than a size cap. A site too large for one archive opens a deployment with `POST /v1/project/{slug}/deployments` instead and writes its files straight to storage against the scoped grant that answers with — no body limit, and no bytes through this API at all.

Publishing is free: no deploy is charged, and an org with no balance can publish and republish. A site is billed for the storage its org holds past the free tier, metered hourly; an org already past the tier must be able to pay a month of that storage, or the upload is refused 402 (503 when commerce is unreachable) BEFORE anything is parsed or written. A failed upload never flips the live site, and a redeploy replaces the site's files and answers the SAME URL, because slug and apex are stable.

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. Object storage must be configured (503); an archive that does not walk is a 400 and one over the size cap is a 413.

## Request

2 fields, body `application/octet-stream`.

| Field    | In   | Type            | Required | Description |
| -------- | ---- | --------------- | -------- | ----------- |
| `slug`   | path | string          | yes      |             |
| `(body)` | body | string (binary) | yes      |             |

## Response

| Status | Body               | Meaning |
| ------ | ------------------ | ------- |
| `2XX`  | projectsDeployment | Success |

`2XX` body — 22 fields.

| Field              | In   | Type                | Always | Description |
| ------------------ | ---- | ------------------- | ------ | ----------- |
| `bucket`           | body | string              | —      |             |
| `bytes`            | body | integer             | —      |             |
| `commit`           | body | string              | —      |             |
| `createdAt`        | body | integer             | —      |             |
| `files`            | body | integer             | —      |             |
| `id`               | body | string              | —      |             |
| `liveUrl`          | body | string              | —      |             |
| `message`          | body | string              | —      |             |
| `prefix`           | body | string              | —      |             |
| `projectId`        | body | string              | —      |             |
| `source`           | body | string              | —      |             |
| `status`           | body | string              | —      |             |
| `updatedAt`        | body | integer             | —      |             |
| `upload`           | body | projectsUploadGrant | —      |             |
| `upload.copy`      | body | boolean             | —      |             |
| `upload.expiresAt` | body | integer             | —      |             |
| `upload.fields`    | body | object              | —      |             |
| `upload.fields.*`  | body | string              | —      |             |
| `upload.maxBytes`  | body | integer             | —      |             |
| `upload.prefix`    | body | string              | —      |             |
| `upload.url`       | body | string              | —      |             |
| `version`          | body | integer             | —      |             |

Failure carries the platform error shape — see [Errors](https://docs.hanzo.ai/docs/errors).

## Examples

**CLI**

```bash
hanzo projects deploy <slug>
```

**SDK**

**.ts**

```ts
import { Configuration, ProjectApi } from 'hanzoai';

const api = new ProjectApi(new Configuration({ accessToken: process.env.HANZO_API_KEY }));
const { data } = await api.postProjectsBySlugDeploy({ slug: 'slug' });
```

**.py**

```python
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_deploy(slug='slug')
```

**.go**

```go
cfg := hanzoai.NewConfiguration()
cfg.AddDefaultHeader("Authorization", "Bearer "+os.Getenv("HANZO_API_KEY"))
client := hanzoai.NewAPIClient(cfg)

resp, _, err := client.ProjectAPI.PostProjectsBySlugDeploy(context.Background()).Execute()
if err != nil {
	return err
}
```

**.rs**

```rust
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_deploy(&cfg, Default::default()).await?;
```

**.java**

```java
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).postProjectsBySlugDeploy();
```

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 →](https://docs.hanzo.ai/docs/sdks)

**HTTP**

```bash
curl -X POST https://api.hanzo.ai/v1/projects/<slug>/deploy \
  -H "Authorization: Bearer $HANZO_API_KEY"
```

**MCP**

MCP reaches **project** through the `project` tool, which names its 52 operations with its own verbs — this one among them, under a name only MCP declares. `describe` explains any of them:

```bash
curl -X POST https://api.hanzo.ai/v1/mcp \
  -H "Content-Type: application/json" \
  -d '{
       "jsonrpc": "2.0",
       "id": 1,
       "method": "tools/call",
       "params": {
         "name": "describe",
         "arguments": {
           "op": "get_project"
         }
       }
     }'
```

---

[Project API](https://docs.hanzo.ai/docs/openapi/project) · [All Hanzo APIs](https://docs.hanzo.ai/docs/openapi) · [Interactive reference](https://docs.hanzo.ai/docs/openapi/reference)
