# Deploy sites — POST /v1/projects/sites/deploy

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

> Deploys a caller-supplied file manifest — the deploy_site capability an agent calls — and answers with where it went live.

`POST /v1/projects/sites/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/sites/deploy` |
| **Method**    | `POST`                                          |
| **Operation** | `post_projects_sites_deploy`                    |
| **Auth**      | `Authorization: Bearer $HANZO_API_KEY`          |

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/project, ensuring a project
(framework `static`) for the resolved slug and recording a deployment. It is
free: the site is billed for the storage its org holds past the free tier, and
the storage gate runs before the upload, refusing (402) an org past the tier
that cannot pay a month of what it holds. Answers 503 when object storage is
unconfigured.

Scope: a validated principal is required (401 without one) and the site is
published into THAT principal's org.

## Request

5 fields, body `application/json` (required).

| Field             | In   | Type                    | Required | Description                                                                                               |
| ----------------- | ---- | ----------------------- | -------- | --------------------------------------------------------------------------------------------------------- |
| `files`           | body | project.projectsFile\[] | —        | Files is the whole site, inline — every file it consists of.                                              |
| `files[].content` | body | string                  | —        | Content is the file's whole text, inline.                                                                 |
| `files[].path`    | body | string                  | —        | Path is where the file lands in the site, RELATIVE to its root — so "index.html" is the page served at /. |
| `name`            | body | string                  | —        | Name is the site's display name.                                                                          |
| `slug`            | body | string                  | —        | Slug is the handle and public host label to publish under.                                                |

## Response

| Status    | Body                       | Meaning |
| --------- | -------------------------- | ------- |
| `200`     | project.projectsSiteDeploy | ok      |
| `default` | problem-details            | refused |

`200` body — 6 fields.

| Field          | In   | Type      | Always | Description                                                                                                                          |
| -------------- | ---- | --------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| `deploymentId` | body | string    | —      | DeploymentID is the deployment this publish recorded, for the history.                                                               |
| `files`        | body | string\[] | —      | Files are the site-relative paths that were uploaded, sorted.                                                                        |
| `name`         | body | string    | —      | Name is the project's display name.                                                                                                  |
| `slug`         | body | string    | —      | Slug is the project the site was published into, created on the fly when the slug was free.                                          |
| `status`       | body | string    | —      | Status is the deployment status, "live" on success.                                                                                  |
| `url`          | body | string    | —      | URL is the canonical live URL, https\://\<slug>.\<apex> — empty when the subdomain belongs to another tenant and this site has none. |

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

## Examples

**CLI**

```bash
hanzo projects sites deploy
```

**SDK**

**.ts**

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

const api = new ProjectApi(new Configuration({ accessToken: process.env.HANZO_API_KEY }));
const { data } = await api.postProjectsSitesDeploy({ files: [{"content":"<content>","path":"<path>"}], name: "<name>" });
```

**.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_sites_deploy(files=[{"content":"<content>","path":"<path>"}], name="<name>")
```

**.go**

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

resp, _, err := client.ProjectAPI.PostProjectsSitesDeploy(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_sites_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).postProjectsSitesDeploy();
```

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/sites/deploy \
  -H "Authorization: Bearer $HANZO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "files": [
         {
           "content": "<content>",
           "path": "<path>"
         }
       ],
       "name": "<name>"
     }'
```

**MCP**

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

```bash
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_sites_deploy",
           "input": {
             "files": [
               {
                 "content": "<content>",
                 "path": "<path>"
               }
             ],
             "name": "<name>"
           }
         }
       }
     }'
```

---

[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)
