# Create apps — POST /v1/platform/projects/{project}/apps

URL: https://docs.hanzo.ai/docs/openapi/platform/post-platform-projects-by-project-apps

> Creates an application from a git repo or a container image.

`POST /v1/platform/projects/{project}/apps`

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

|               |                                                            |
| ------------- | ---------------------------------------------------------- |
| **Address**   | `https://api.hanzo.ai/v1/platform/projects/{project}/apps` |
| **Method**    | `POST`                                                     |
| **Operation** | `post_platform_projects_by_project_apps`                   |
| **Auth**      | `Authorization: Bearer $HANZO_API_KEY`                     |

Creates an application from a git repo or a container image.

It registers a new application under one of the caller org's projects and
answers 201 with it. Creating does NOT deploy: the app lands in `draft` and
nothing reaches the cluster until /deploy.

`source` is `git` — which requires `repo.url` — or `image`, which requires
`image.repository`; anything else is 400. A git app builds with zero-config
`pack` by default and may opt into `dockerfile`; an image app never builds. The
repo URL and Dockerfile path are validated here against the SAME allowlist the
privileged build enforces, so an unsafe source is refused before it is ever
persisted.

The `slug` is the app's identity in the cluster: given or derived from `name`,
it must match `^[a-z0-9]([a-z0-9-]{0,38}[a-z0-9])?$`, and a slug already used in
this project is 409. `replicas` and `storageGb` are clamped to the deployment's
limits rather than refused.

Env keys must match `^[A-Za-z_][A-Za-z0-9_]*$`. A variable marked `secret: true`
is SEALED into KMS and its plaintext is never written to the database — and if
KMS is unavailable the create fails 503 rather than falling back to storing a
secret in the clear.

The app is seeded with its canonical default host, so it has a working HTTPS URL
the moment it deploys. A bare custom domain cannot be attached here — it has to
go through add-domain and DNS verification first. Requires a validated
principal; 401 without one, and every cluster object it will later create lands
in that org's own `tenant-<org>` namespace.

## Request

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

| Field              | In   | Type                   | Required | Description                                                                                                                                                              |
| ------------------ | ---- | ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `project`          | path | string                 | yes      | Project is the project to create the application under, from the path.                                                                                                   |
| `buildType`        | body | string                 | —        | BuildType is `pack` — the zero-config default that detects any project — or `dockerfile`, the explicit escape hatch.                                                     |
| `description`      | body | string                 | —        | Description is free text about what the application is.                                                                                                                  |
| `dockerfile`       | body | string                 | —        | Dockerfile is the path to build from, for buildType `dockerfile`.                                                                                                        |
| `domains`          | body | string\[]              | —        | Domains are extra ingress hosts.                                                                                                                                         |
| `env`              | body | platform.EnvVarJSON\[] | —        | Env is the application's environment.                                                                                                                                    |
| `env[].key`        | body | string                 | —        | Key is the variable's name in the container, which must match `^[A-Za-z_][A-Za-z0-9_]*$`.                                                                                |
| `env[].secret`     | body | boolean                | —        | Secret says the value lives in KMS and never in the database.                                                                                                            |
| `env[].value`      | body | string                 | —        | Value is the plaintext, and it is WRITE-ONLY once the entry is secret: a sealed value reads back as "", and sending "" again KEEPS what is sealed rather than wiping it. |
| `environment`      | body | string                 | —        | Environment is the deploy target this app names ("production" by default).                                                                                               |
| `image`            | body | platform.imageOrigin   | —        |                                                                                                                                                                          |
| `image.repository` | body | string                 | —        | Repository is the image repository.                                                                                                                                      |
| `image.tag`        | body | string                 | —        | Tag is the image tag to deploy; `latest` when omitted.                                                                                                                   |
| `name`             | body | string                 | —        | Name is the application's display name.                                                                                                                                  |
| `port`             | body | integer (int64)        | —        | Port is the container port the app listens on.                                                                                                                           |
| `project`          | body | string                 | —        | Project is the project to create the application under, from the path.                                                                                                   |
| `replicas`         | body | integer (int64)        | —        | Replicas is how many copies to run; clamped to the deployment's limit rather than refused.                                                                               |
| `repo`             | body | platform.gitOrigin     | —        |                                                                                                                                                                          |
| `repo.branch`      | body | string                 | —        | Branch is the branch to build; defaults to `main` for a git source.                                                                                                      |
| `repo.url`         | body | string                 | —        | URL is the repository clone URL.                                                                                                                                         |
| `slug`             | body | string                 | —        | Slug is the app's identity in the cluster — its CR name and part of its host.                                                                                            |
| `source`           | body | string                 | —        | Source is `git`, which requires repo.url, or `image`, which requires image.repository.                                                                                   |
| `storageGb`        | body | integer (int64)        | —        | StorageGB is the persistent volume size in GiB; absent means stateless.                                                                                                  |

## Response

| Status    | Body            | Meaning |
| --------- | --------------- | ------- |
| `201`     | platform.appOut | created |
| `default` | problem-details | refused |

`201` body — 34 fields.

| Field                 | In   | Type                   | Always | Description                                                                                                                                                                                                                          |
| --------------------- | ---- | ---------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `buildType`           | body | string                 | —      | BuildType is how a git app builds: `pack`, the zero-config default that detects the project, or `dockerfile`.                                                                                                                        |
| `createdAt`           | body | integer (int64)        | —      | CreatedAt is when the app was created, unix seconds.                                                                                                                                                                                 |
| `currentDeploymentId` | body | string                 | —      | CurrentDeploymentID is the deployment that is live — the pointer a deploy advances monotonically by version, so it never regresses to an older one.                                                                                  |
| `description`         | body | string                 | —      | Description is free text about what the app is.                                                                                                                                                                                      |
| `dockerfile`          | body | string                 | —      | Dockerfile is the path inside the repo to build from, for buildType `dockerfile`.                                                                                                                                                    |
| `domains`             | body | string\[]              | —      | Domains are the ingress hosts rendered into the app's CR, its own `&lt;slug&gt;.&lt;org&gt;.&lt;sites host&gt;` first.                                                                                                               |
| `env`                 | body | platform.EnvVarJSON\[] | —      | Env is the app's environment variables, with every SECRET value masked to "" — the plaintext is in KMS and this surface never echoes it.                                                                                             |
| `env[].key`           | body | string                 | —      | Key is the variable's name in the container, which must match `^[A-Za-z_][A-Za-z0-9_]*$`.                                                                                                                                            |
| `env[].secret`        | body | boolean                | —      | Secret says the value lives in KMS and never in the database.                                                                                                                                                                        |
| `env[].value`         | body | string                 | —      | Value is the plaintext, and it is WRITE-ONLY once the entry is secret: a sealed value reads back as "", and sending "" again KEEPS what is sealed rather than wiping it.                                                             |
| `environment`         | body | string                 | —      | Environment is the deploy target this app names, `production` when none was given.                                                                                                                                                   |
| `health`              | body | string                 | —      | Health rolls ready-vs-desired replicas up to a colour: green (all ready), yellow (some ready, or deliberately scaled to zero), red (none), or "" when the cluster reports no replica counts at all — unknown, never a guessed green. |
| `id`                  | body | string                 | —      | ID is the server-minted application id (`app_…`).                                                                                                                                                                                    |
| `image`               | body | platform.imageView     | —      |                                                                                                                                                                                                                                      |
| `image.repository`    | body | string                 | —      | Repository is the image path without a tag (ghcr.io/acme/api).                                                                                                                                                                       |
| `image.tag`           | body | string                 | —      | Tag is the tag to run: what the create declared, then RE-STAMPED on every transition to live with the tag that actually went live.                                                                                                   |
| `name`                | body | string                 | —      | Name is the display name.                                                                                                                                                                                                            |
| `namespace`           | body | string                 | —      | Namespace is where the app's cluster objects live, `tenant-&lt;org&gt;`.                                                                                                                                                             |
| `org`                 | body | string                 | —      | Org is the tenant that owns the app.                                                                                                                                                                                                 |
| `phase`               | body | string                 | —      | Phase is the operator's own `status.phase` for the app's Service CR, read from the cluster on this request.                                                                                                                          |
| `port`                | body | integer (int64)        | —      | Port is the container port traffic is sent to.                                                                                                                                                                                       |
| `projectId`           | body | string                 | —      | ProjectID is the IAM project the app lives under, and it is that project's NAME — the (org,name) key IAM identifies it by, which is also what the `:project` path segment carries.                                                   |
| `replicas`            | body | integer (int64)        | —      | Replicas is how many copies the CR declares.                                                                                                                                                                                         |
| `repo`                | body | platform.gitSource     | —      |                                                                                                                                                                                                                                      |
| `repo.branch`         | body | string                 | —      | Branch is the branch a push-to-deploy build tracks, `main` when the create named none — a push to any other branch, and every tag push, builds nothing here.                                                                         |
| `repo.provider`       | body | string                 | —      | Provider is derived from the URL — github, gitlab, bitbucket, or `git` for anything else.                                                                                                                                            |
| `repo.url`            | body | string                 | —      | URL is the clone URL a git app builds from, stored as sent once the build path's allowlist accepted it (validateRepoURL).                                                                                                            |
| `secretSync`          | body | string                 | —      | SecretSync is how far the app's secret env has got into the cluster: ""\|pending\|syncing\|ready\|failed (secrets.go).                                                                                                               |
| `secretSyncDetail`    | body | string                 | —      | SecretSyncDetail is the honest reason when the sync is not ready — a missing CRD, an RBAC grant, a per-tenant credential.                                                                                                            |
| `slug`                | body | string                 | —      | Slug is the app's identity in the cluster: the operator CR's name, the first label of its default host, and the `:app` path segment.                                                                                                 |
| `source`              | body | string                 | —      | Source is what the app deploys FROM: `git`, which builds Repo, or `image`, which runs Image as it is.                                                                                                                                |
| `status`              | body | string                 | —      | Status is the lifecycle THIS store records: draft (created, nothing in the cluster yet), building, deploying, live, stopped or error.                                                                                                |
| `storageGb`           | body | integer (int64)        | —      | StorageGB is the persistent volume size in GiB.                                                                                                                                                                                      |
| `updatedAt`           | body | integer (int64)        | —      | UpdatedAt is when it last changed, unix seconds.                                                                                                                                                                                     |

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

## Examples

**CLI**

```bash
hanzo platform projects apps create <project>
```

**SDK**

**.ts**

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

const api = new PlatformApi(new Configuration({ accessToken: process.env.HANZO_API_KEY }));
const { data } = await api.postPlatformProjectsByProjectApps({ project: 'project', buildType: "<buildType>", description: "<description>" });
```

**.py**

```python
from hanzoai.cloud import ApiClient, Configuration
from hanzoai.cloud.api import PlatformApi

client = ApiClient(Configuration(access_token=os.environ["HANZO_API_KEY"]))
result = PlatformApi(client).post_platform_projects_by_project_apps(project='project', build_type="<buildType>", description="<description>")
```

**.go**

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

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

**.rs**

```rust
use hanzo_client::apis::{configuration::Configuration, platform_api};

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

let result = platform_api::post_platform_projects_by_project_apps(&cfg, Default::default()).await?;
```

**.java**

```java
import ai.hanzo.cloud.ApiClient;
import ai.hanzo.cloud.api.PlatformApi;

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

var result = new PlatformApi(client).postPlatformProjectsByProjectApps();
```

**HTTP**

```bash
curl -X POST https://api.hanzo.ai/v1/platform/projects/<project>/apps \
  -H "Authorization: Bearer $HANZO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "buildType": "<buildType>",
       "description": "<description>"
     }'
```

**MCP**

MCP reaches **platform** through the `platform` tool, which names its 38 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_build"
         }
       }
     }'
```

---

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