# Create deployments — POST /v1/projects/{slug}/deployments

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

> Opens a deployment and hands back a short-lived, prefix-scoped grant to write its bytes straight to object storage.

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

> [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}/deployments` |
| **Method**    | `POST`                                                |
| **Operation** | `post_projects_by_slug_deployments`                   |
| **Auth**      | `Authorization: Bearer $HANZO_API_KEY`                |

Opens a deployment and hands back a short-lived, prefix-scoped
grant to write its bytes straight to object storage. Answers 202.

This is the path for a site too large to send as one archive: a real export is
hundreds of megabytes against a 16 MiB body limit, so the bytes deliberately do
NOT pass through the API. The answer carries `bucket`, `prefix` and `upload` —
a presigned POST policy that S3 itself confines to this site's prefix
(starts-with `<org>/<slug>/`), expires in 30 minutes and bounds each object.
So a build writes its own files and holds no standing bucket credential; there
is nothing to rotate and nothing that leaks between tenants. Never guess the
prefix — it is server-derived, and a guessed one lands where nothing is served.

The deployment is `queued` until POST .../deployments/{id}/complete flips it
live (or error). That completion is also where DELETION happens: the grant
authorizes writes only, so a build cannot remove a file, and cloud reconciles
the prefix against the `keys` manifest the completion carries. A build that
dies before completing leaves the deployment queued rather than a half-live
site.

The grant is on the 202 and NOWHERE else — it is never stored and never
replayed on a later read, so it cannot outlive the build it was minted for. A
deployment whose grant could not be minted is still created and still
completable; it simply carries no `upload`, and a caller with no other way to
write should treat that as the failure it is.

Billing: the deployment is free. The one gate is storage, and it runs BEFORE
anything is created: an org past the free storage tier that cannot pay a month of
what it holds is 402, and 503 when commerce is unreachable. What the build uploads
is billed as storage from the hourly sweep that finds it, never as a deploy.

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

| Field    | In   | Type   | Required | Description                                                                                                                         |
| -------- | ---- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `slug`   | path | string | yes      | Slug is the site to deploy, from the path.                                                                                          |
| `commit` | body | string | —        | Commit is the git sha this build was produced from, recorded on the deployment so a released site can be traced back to its source. |
| `slug`   | body | string | —        | Slug is the site to deploy, from the path.                                                                                          |

## Response

| Status    | Body                       | Meaning  |
| --------- | -------------------------- | -------- |
| `202`     | project.projectsDeployment | accepted |
| `default` | problem-details            | refused  |

`202` body — 22 fields.

| Field              | In   | Type                        | Always | Description                                                                                                                                                                             |
| ------------------ | ---- | --------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bucket`           | body | string                      | —      | Bucket is the object-store bucket its files were written to.                                                                                                                            |
| `bytes`            | body | integer (int64)             | —      | Bytes is their total size in bytes.                                                                                                                                                     |
| `commit`           | body | string                      | —      | Commit is the revision that was built, for a deployment that came from a repository.                                                                                                    |
| `createdAt`        | body | integer (int64)             | —      | CreatedAt is when the deployment was queued, as Unix seconds.                                                                                                                           |
| `files`            | body | integer (int64)             | —      | Files is how many objects the deployment published.                                                                                                                                     |
| `id`               | body | string                      | —      | ID identifies this one deployment attempt, and is what CI quotes back to complete it.                                                                                                   |
| `liveUrl`          | body | string                      | —      | LiveURL is where this deployment serves, once it is live.                                                                                                                               |
| `message`          | body | string                      | —      | Message is what happened, in words — the build's own note, or on a failure why it failed.                                                                                               |
| `prefix`           | body | string                      | —      | Prefix is the key prefix within that bucket holding EXACTLY this deployment's objects — the unit an upload grant is scoped to, so a grant for one deployment cannot write over another. |
| `projectId`        | body | string                      | —      | ProjectID is the project this deployment belongs to.                                                                                                                                    |
| `source`           | body | string                      | —      | Source is what caused the deployment — a git push, an uploaded artifact, a generated site.                                                                                              |
| `status`           | body | string                      | —      | Status is where the attempt got to — queued, live, or failed.                                                                                                                           |
| `updatedAt`        | body | integer (int64)             | —      | UpdatedAt is when it last changed state, as Unix seconds — so the gap between the two is how long the build took.                                                                       |
| `upload`           | body | project.projectsUploadGrant | —      |                                                                                                                                                                                         |
| `upload.copy`      | body | boolean                     | —      | Copy is true when the completion of this deployment accepts copyFrom and copy.                                                                                                          |
| `upload.expiresAt` | body | integer (int64)             | —      | ExpiresAt is when the grant stops being accepted, as Unix seconds.                                                                                                                      |
| `upload.fields`    | body | object                      | —      | Fields are form values every POST must carry VERBATIM, alongside `key` and `file`.                                                                                                      |
| `upload.fields.*`  | body | string                      | —      |                                                                                                                                                                                         |
| `upload.maxBytes`  | body | integer (int64)             | —      | MaxBytes bounds ONE object, not the upload as a whole.                                                                                                                                  |
| `upload.prefix`    | body | string                      | —      | Prefix is the only place this grant can write: the deployment's own key prefix.                                                                                                         |
| `upload.url`       | body | string                      | —      | URL is the address to POST each object to.                                                                                                                                              |
| `version`          | body | integer (int64)             | —      | Version counts deployments of this project from 1, so the history reads as an ordered sequence rather than by timestamp.                                                                |

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

## Examples

**CLI**

```bash
hanzo projects deployments create <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.postProjectsBySlugDeployments({ slug: 'slug', commit: "<commit>", 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_deployments(slug='slug', commit="<commit>", slug="<slug>")
```

**.go**

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

resp, _, err := client.ProjectAPI.PostProjectsBySlugDeployments(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_deployments(&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).postProjectsBySlugDeployments();
```

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>/deployments \
  -H "Authorization: Bearer $HANZO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "commit": "<commit>",
       "slug": "<slug>"
     }'
```

**MCP**

Tool `project`, op `post_projects_by_slug_deployments` — 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_by_slug_deployments",
           "input": {
             "slug": "<slug>",
             "commit": "<commit>"
           }
         }
       }
     }'
```

---

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