# Platform

URL: https://docs.hanzo.ai/docs/openapi/platform

> Hanzo PaaS: deploy containers to your own tenant namespace — builds, releases, environments, logs, custom domains.

Also for this capability: **API** · [CLI](https://docs.hanzo.ai/docs/cli/platform) · [MCP](https://docs.hanzo.ai/docs/mcp-tools/platform) · [SDKs](https://docs.hanzo.ai/docs/sdks)

Hanzo PaaS: deploy containers to your own tenant namespace — builds, releases, environments, logs, custom domains.

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

|                |                                        |
| -------------- | -------------------------------------- |
| **Base URL**   | `https://api.hanzo.ai`                 |
| **Operations** | 39                                     |
| **Auth**       | `Authorization: Bearer $HANZO_API_KEY` |

## Specification

> **HIP-1230 · Platform — The Container Plane** — Draft · [read the specification →](https://github.com/hanzoai/hips/blob/7ed737180339cd05b5f2935eafe7f7b83d2449a2/HIPs/hip-1230-platform-the-container-plane.md)

`platform` is the per-org container platform: projects, applications, builds,
deploys, environments, releases, logs and verified custom domains, each
application reconciled as an operator Service CR into the caller's own
`tenant-<org>` Kubernetes namespace (`apps/platform/platform.go:1-27`). It is
implemented in `hanzoai/cloud` at `apps/platform`. This HIP states the target
surface — one address, `/v1/platform` — and carries two pieces previously
specified apart: the forge push door (formerly HIP-1070) and the pipelines
board (formerly HIP-1071), both platform addresses because their
implementation is this package.

### Motivation

The capability's routes grew at eight top-level addresses while its store,
its process and its name were one: a generated client offered a `BuildsApi`,
a `RunnerApi` and a `PlatformApi` for one subsystem, and one root carried a
name the grammar refuses outright (`git-webhook`). One store is one
capability however many nouns it answers for (HIP-0139 §7.1).

### Specification

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be
interpreted as in RFC 2119.

#### One store

The capability owns one store: the system-namespace `platform` SQLite
database (`apps/platform/store.go:131`), holding project, application, build,
deployment, environment and release rows. Tenancy is the `org` column on
every table, enforced in every WHERE clause; the store exposes no method that
reads a row without the org (`apps/platform/store.go:22-28`).

#### The addresses

Every route is under `/v1/platform`: `apps`, `builds`, `cd`, `ci`,
`environments`, `fleet`, `health`, `hook`, `pipelines`, `projects`,
`releases`, `run` and `runner`. `/v1/platform/ci` answers 501 naming what is
missing — an address the fleet publishes and routes nowhere is the defect the
manifest exists to prevent (`manifest/apps.go:140-145`). The router today
still serves seven of these families at the root (`/v1/builds`,
`/v1/environments`, `/v1/git-webhook`, `/v1/pipelines`, `/v1/releases`,
`/v1/run`, `/v1/runner`); each pair is a line in cloud's
`openapi/misfiled.txt` and folds here, with `git-webhook` — a hyphenated
compound — renamed to `hook` in the same move.

Operations are typed zip operations except the push door, which cannot be:
its authentication is an HMAC over the raw bytes checked before the payload
is parsed, and a typed operation decodes first (`apps/platform/hook.go:19-22`).

#### The push door

`POST /v1/platform/hook` is where the forge delivers a push. The receiver
MUST live in this process because the deploy trigger has exactly one
registrant, and it is platform's: the door once lived in git's process, where
that registrant is nil, so every delivery was signed, accepted, answered 204
and built nothing (`apps/platform/hook.go:1-17`).

The forge holds no platform session, so the address is public at the identity
layer and authenticated by the HMAC signature; the bytes verified MUST be the
bytes acted on, and the event kind is taken from the payload, never a header.
Four bounds keep an unauthenticated door from being a lever: an encoded body
is refused 415 before it is touched, a body cap bounds what is hashed, the
verifying secret refreshes on a bounded window from KMS (HIP-0027), and that
key-store read times out below the forge's delivery timeout. An unreadable
secret answers 503, not 401 — the fault is ours. Duplicates are remembered by
fact (namespace, repository, ref, commit) for a bounded window, and a failed
dispatch gives the memory back. The forge half is one forge-wide system
webhook signed with the value at the configured KMS reference; a repository
opts in by having an application that tracks it, not by owning a hook.

#### The pipelines board

`GET /v1/platform/pipelines` is a projection, not a record: one application's
build and deploy configuration joined with its latest run
(`apps/platform/console.go:227`). The surface MUST be read-only — a pipeline
comes into existence only through the one existing write path — and where an
application has a latest deployment, that deployment's status replaces the
configuration's. The same rule governs the sibling boards it sits beside:
an environment is a scope derived from the applications that target it, a
build row is a real record or an honest empty, a release is a deployment that
was actually applied.

#### Tenancy

The org is the gateway-minted, IAM-validated claim (HIP-0026), read as
`c.Org()`; the deploy namespace is derived from it as `tenant-<org>`, never
taken from the request, and cross-tenant identifiers are structurally not
inputs to any handler (`apps/platform/platform.go:20-26`). Console reads
additionally require the identity to carry a user, refusing the one forgeable
path — a caller reaching a pod directly with an asserted org and no bearer.
Two doors differ by audience: `/v1/platform/fleet` is the operator's drift
board, admitted only for a SuperAdmin or an org-confined OrgAdmin
(`apps/platform/fleet.go:31-34`, HIP-0135), and the push door authenticates
by signature as above. `POST /v1/platform/runner` — the privileged build
trigger `hanzo build` and the push hook call — is gated by a constant-time
shared token plus an image-ref allowlist confined to the registries we own
(`apps/platform/runner.go:1-13`).

#### Metered

The capability is metered (`plugin/platform/main.go:21`), and every debit
lands in integer cents through the shared `cloud.ResourceMeter` on the
caller's org ledger:

- **build minutes** — wall-clock from build creation to observed Job
  completion, once per completed build, at `CLOUD_BUILD_MINUTE_CENTS`
  (policy default $1.00/min; 0 makes builds free)
  (`apps/platform/buildmeter.go:1-20`);
- **running compute** — each tick charges every live app's org for the span
  since its compare-and-set watermark, at the app's SBOM compute rate, so a
  double-tick or restart never double-charges
  (`apps/platform/computemeter.go:1-30`);
- **the run fee** — a flat gate-then-meter-on-success debit per
  `/v1/platform/run` deploy at `CLOUD_PLATFORM_RUN_FEE_CENTS`
  (`apps/platform/run.go:36`).

#### Events, observability, stage

The capability publishes no events on the bus, so a customer's webhooks
receive nothing from it. It emits lifecycle facts — `push.landed`,
`build.started`, `deploy.live`, `deploy.failed` (`build.go:582-585`) — on the
in-process stream (`apps/platform/hook.go:628`, `apps/platform/deploy.go:290`,
`apps/platform/applylive.go:94`), fanned to registered reactors: the deploy
subscriber, mirror-out, and chat notification. Beyond the request span it
emits structured log lines only (`apps/platform/run.go:227`). Its stage is
`ga`: the manifest row carries no stage field, and absent means `ga`
(HIP-0139 §8).

#### Upstreams

The capability forks nothing. It links `go-git/go-git` v5 (Apache-2.0) to
read pushed repositories in memory, `k8s.io/apimachinery` (Apache-2.0) to
write operator CRs, and `Masterminds/sprig` v3 (MIT) for template functions.
Builds execute in an in-cluster BuildKit (Apache-2.0) Job launched by image;
nothing of BuildKit is linked into the binary.

### Rationale

The alternative to carrying the hook and the board in this HIP is two more
specification files for two things that are single routes of this package —
one capability sliced into three specs, the inversion of the defect HIP-0139
§6 names.

### Security Considerations

The wrong implementation hands an attacker the build plane. An unsigned or
parse-before-verify hook lets an unauthenticated caller mint builds in the
process that owns deploys; an encoded body accepted at that door buys
megabytes of allocation for kilobytes on the wire. A deploy namespace taken
from the request is a cross-tenant deploy; it is derived from the validated
org instead. A leaked runner token without the image-ref allowlist pushes to
an arbitrary registry; with it, only to ours. The fleet board without its
admin guard is a fleet-wide rollout lever exposed to every tenant.

## Four surfaces

| Surface  | Reaches this capability as                       | Coverage                                                                   |
| -------- | ------------------------------------------------ | -------------------------------------------------------------------------- |
| **REST** | `platform` at its own prefix                     | 39 operations                                                              |
| **CLI**  | `hanzo build …`                                  | 34 of 39 — the CLI pins the document on its own clock                      |
| **SDK**  | `PlatformApi` in every published client          | 39 methods                                                                 |
| **MCP**  | tool `platform` on `https://api.hanzo.ai/v1/mcp` | 38 operations, 4 under the document's own id — ask `describe` for the rest |

## Quickstart

```bash
export HANZO_API_KEY=sk-...   # console.hanzo.ai → API keys
```

Then the first call — a read that needs nothing but the key. `GET /v1/platform/cd`, operation `get_platform_cd`:

**CLI**

```bash
hanzo platform cd
```

**SDK**

**.ts**

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

const api = new PlatformApi(new Configuration({ accessToken: process.env.HANZO_API_KEY }));
const { data } = await api.getPlatformCd();
```

**.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).get_platform_cd()
```

**.go**

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

resp, _, err := client.PlatformAPI.GetPlatformCd(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::get_platform_cd(&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).getPlatformCd();
```

**HTTP**

```bash
curl https://api.hanzo.ai/v1/platform/cd \
  -H "Authorization: Bearer $HANZO_API_KEY"
```

**MCP**

Tool `platform`, op `get_platform_cd` — 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": "platform",
         "arguments": {
           "op": "get_platform_cd",
           "input": {}
         }
       }
     }'
```

Answers `200` with `object` — ok.

## Endpoints

| Endpoint                                                                                                                                                                                    | What it does                                                                                                                               |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| [`GET /v1/build/{id}`](https://docs.hanzo.ai/docs/openapi/platform/get-build-by-id)                                                                                                         | Answers one build: its status, and for a failed build the reason.                                                                          |
| [`POST /v1/build`](https://docs.hanzo.ai/docs/openapi/platform/post-build)                                                                                                                  | Triggers a native build — an image, or the binaries a repo declares.                                                                       |
| [`GET /v1/platform/apps/{app}/cd`](https://docs.hanzo.ai/docs/openapi/platform/get-platform-apps-by-app-cd)                                                                                 | Answers ONE app's reconciliation alone — the poll a deploy console makes while it waits, without re-reading the whole inventory each time. |
| [`PUT /v1/platform/apps/{app}/project`](https://docs.hanzo.ai/docs/openapi/platform/put-platform-apps-by-app-project)                                                                       | Moves an app to a project.                                                                                                                 |
| [`GET /v1/platform/apps/{app}`](https://docs.hanzo.ai/docs/openapi/platform/get-platform-apps-by-app)                                                                                       | Answers ONE declaration — what git says this app is, before the delivery plane has had any say in it.                                      |
| [`GET /v1/platform/apps`](https://docs.hanzo.ai/docs/openapi/platform/get-platform-apps)                                                                                                    | Answers what this organisation has declared, joined with what the delivery plane has done about it.                                        |
| [`POST /v1/platform/apps`](https://docs.hanzo.ai/docs/openapi/platform/post-platform-apps)                                                                                                  | Deploy an app through cd.hanzo.ai                                                                                                          |
| [`GET /v1/platform/builds`](https://docs.hanzo.ai/docs/openapi/platform/get-platform-builds)                                                                                                | Returns real build records for your org.                                                                                                   |
| [`GET /v1/platform/cd`](https://docs.hanzo.ai/docs/openapi/platform/get-platform-cd)                                                                                                        | Answers every Application the delivery plane holds.                                                                                        |
| [`GET /v1/platform/environments`](https://docs.hanzo.ai/docs/openapi/platform/get-platform-environments)                                                                                    | Returns your deploy targets, and what is running on each.                                                                                  |
| [`GET /v1/platform/fleet/{app}`](https://docs.hanzo.ai/docs/openapi/platform/get-platform-fleet-by-app)                                                                                     | Returns one platform service, resolved to production by default.                                                                           |
| [`GET /v1/platform/fleet`](https://docs.hanzo.ai/docs/openapi/platform/get-platform-fleet)                                                                                                  | Returns the platform's own service tier, and where it has drifted.                                                                         |
| [`GET /v1/platform/health`](https://docs.hanzo.ai/docs/openapi/platform/get-platform-health)                                                                                                | Reports whether this control plane can actually deploy anything.                                                                           |
| [`GET /v1/platform/pipelines`](https://docs.hanzo.ai/docs/openapi/platform/get-platform-pipelines)                                                                                          | Returns one build-and-deploy pipeline per app, with its latest run.                                                                        |
| [`POST /v1/platform/projects/{project}/apps/{app}/deploy`](https://docs.hanzo.ai/docs/openapi/platform/post-platform-projects-by-project-apps-by-app-deploy)                                | Deploys the app — building it first if it comes from git.                                                                                  |
| [`GET /v1/platform/projects/{project}/apps/{app}/deployments/{id}/logs`](https://docs.hanzo.ai/docs/openapi/platform/get-platform-projects-by-project-apps-by-app-deployments-by-id-logs)   | Returns real logs for a deployment — the build's, then the app's.                                                                          |
| [`GET /v1/platform/projects/{project}/apps/{app}/deployments/{id}`](https://docs.hanzo.ai/docs/openapi/platform/get-platform-projects-by-project-apps-by-app-deployments-by-id)             | Returns one deployment of one app.                                                                                                         |
| [`GET /v1/platform/projects/{project}/apps/{app}/deployments`](https://docs.hanzo.ai/docs/openapi/platform/get-platform-projects-by-project-apps-by-app-deployments)                        | Returns an app's deployment history.                                                                                                       |
| [`POST /v1/platform/projects/{project}/apps/{app}/domains/{host}/verify`](https://docs.hanzo.ai/docs/openapi/platform/post-platform-projects-by-project-apps-by-app-domains-by-host-verify) | Checks a custom domain's DNS and turns it on if it passes.                                                                                 |
| [`DELETE /v1/platform/projects/{project}/apps/{app}/domains/{host}`](https://docs.hanzo.ai/docs/openapi/platform/delete-platform-projects-by-project-apps-by-app-domains-by-host)           | Detaches a hostname and releases the claim.                                                                                                |
| [`GET /v1/platform/projects/{project}/apps/{app}/domains`](https://docs.hanzo.ai/docs/openapi/platform/get-platform-projects-by-project-apps-by-app-domains)                                | Returns every hostname this app answers on.                                                                                                |
| [`POST /v1/platform/projects/{project}/apps/{app}/domains`](https://docs.hanzo.ai/docs/openapi/platform/post-platform-projects-by-project-apps-by-app-domains)                              | Attaches a hostname — instantly if you already own it, otherwise with a DNS challenge.                                                     |
| [`PUT /v1/platform/projects/{project}/apps/{app}/env`](https://docs.hanzo.ai/docs/openapi/platform/put-platform-projects-by-project-apps-by-app-env)                                        | Replaces an app's environment variables.                                                                                                   |
| [`POST /v1/platform/projects/{project}/apps/{app}/preview`](https://docs.hanzo.ai/docs/openapi/platform/post-platform-projects-by-project-apps-by-app-preview)                              | Puts a branch on its own URL.                                                                                                              |
| [`POST /v1/platform/projects/{project}/apps/{app}/promote`](https://docs.hanzo.ai/docs/openapi/platform/post-platform-projects-by-project-apps-by-app-promote)                              | Promotes an already-built release to the app.                                                                                              |
| [`POST /v1/platform/projects/{project}/apps/{app}/rollback`](https://docs.hanzo.ai/docs/openapi/platform/post-platform-projects-by-project-apps-by-app-rollback)                            | Goes back to the previous release.                                                                                                         |
| [`POST /v1/platform/projects/{project}/apps/{app}/start`](https://docs.hanzo.ai/docs/openapi/platform/post-platform-projects-by-project-apps-by-app-start)                                  | Starts a stopped app back up.                                                                                                              |
| [`POST /v1/platform/projects/{project}/apps/{app}/stop`](https://docs.hanzo.ai/docs/openapi/platform/post-platform-projects-by-project-apps-by-app-stop)                                    | Stops an app without deleting it.                                                                                                          |
| [`GET /v1/platform/projects/{project}/apps/{app}`](https://docs.hanzo.ai/docs/openapi/platform/get-platform-projects-by-project-apps-by-app)                                                | Returns one application, with its live phase, health and secret sync.                                                                      |
| [`DELETE /v1/platform/projects/{project}/apps/{app}`](https://docs.hanzo.ai/docs/openapi/platform/delete-platform-projects-by-project-apps-by-app)                                          | Deletes an application and tears down what it runs.                                                                                        |
| [`GET /v1/platform/projects/{project}/apps`](https://docs.hanzo.ai/docs/openapi/platform/get-platform-projects-by-project-apps)                                                             | Returns the applications in one project, with what the cluster says about them.                                                            |
| [`POST /v1/platform/projects/{project}/apps`](https://docs.hanzo.ai/docs/openapi/platform/post-platform-projects-by-project-apps)                                                           | Creates an application from a git repo or a container image.                                                                               |
| [`GET /v1/platform/projects/{project}`](https://docs.hanzo.ai/docs/openapi/platform/get-platform-projects-by-project)                                                                       | Answers one project and every app in it.                                                                                                   |
| [`PUT /v1/platform/projects/{project}`](https://docs.hanzo.ai/docs/openapi/platform/put-platform-projects-by-project)                                                                       | Renames a project.                                                                                                                         |
| [`DELETE /v1/platform/projects/{project}`](https://docs.hanzo.ai/docs/openapi/platform/delete-platform-projects-by-project)                                                                 | Folds a project into another.                                                                                                              |
| [`GET /v1/platform/projects`](https://docs.hanzo.ai/docs/openapi/platform/get-platform-projects)                                                                                            | Answers every project the caller may see, with how its apps stand.                                                                         |
| [`POST /v1/platform/projects`](https://docs.hanzo.ai/docs/openapi/platform/post-platform-projects)                                                                                          | Creates a project from the apps it starts with.                                                                                            |
| [`GET /v1/platform/releases`](https://docs.hanzo.ai/docs/openapi/platform/get-platform-releases)                                                                                            | Returns the versions that actually reached the cluster.                                                                                    |
| [`POST /v1/platform/run`](https://docs.hanzo.ai/docs/openapi/platform/post-platform-run)                                                                                                    | Runs a container image and gives back a URL.                                                                                               |

---

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