# Deploy

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

> Hanzo CD: see what each app is running, sync it, and roll back a bad release.

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

Hanzo CD: see what each app is running, sync it, and roll back a bad release.

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

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

## Specification

> **HIP-1122 · Deploy — The GitOps Plane** — Draft · [read the specification →](https://github.com/hanzoai/hips/blob/7ed737180339cd05b5f2935eafe7f7b83d2449a2/HIPs/hip-1122-deploy-gitops-plane.md)

`/v1/deploy` is Hanzo CD: see what each app in the fleet is running, sync it,
and roll back a bad release. It is implemented in `hanzoai/cloud` at
`apps/deploy`, observing the operator-managed fleet — applications, resource
tree, per-object health, live diff — and driving reconciliation. Each operator
`hanzo.ai/v1` App CR is a GitOps Application (`apps/deploy/deploy.go:8-11`).
This HIP states that the cluster is the store, which reads are tenant-scoped,
and why the writes are SuperAdmin-only.

### Motivation

The operator reconciles declared state into workloads whether or not anyone is
watching; what was missing was the watch. Without one plane that projects the
fleet — declared version, health, sync, the owned-resource tree — the answer to
"what is running" is a kubectl session, which neither the console nor a tenant
can be handed. This plane is that projection, at the addresses the CD dashboard
that consumes it already speaks (`apps/deploy/deploy.go:13-15`).

### Specification

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

#### §1 The cluster is the store

deploy owns no store. Its state is the cluster's: App CRs, their owned objects
(Deployment, Service, Ingress, ReplicaSet, Pod), and the static-plane site CRDs
projected as Application rows so the fleet list covers the whole delivery
surface (`apps/deploy/deploy.go:76-84`). Nothing this plane serves survives the
cluster it reads, and nothing needs to.

#### §2 The addresses

Every route is under `/v1/deploy`, enumerated as explicit prefixes in the
manifest row (`manifest/apps.go:175`). The fleet reads — applications, detail,
resource tree, revisions, clusters, projects, settings, gitops, version — are
typed. The raw routes are raw because their answers cannot be values:
`stream/applications` and the streamed resource tree are event streams;
`login`, `callback` and `logout` are the console's OIDC redirect dance against
IAM (`apps/deploy/login.go:128-140`); `sync`, `rollback` and `reconcile` are
imperatives answering acknowledgement, not a resource; `account/can-i/{...}` is
a wildcard permission probe the dashboard issues.

#### §3 Tenancy — two scopes, one predicate

Scope is derived from the validated identity in one place
(`resolveScope`, `apps/deploy/scope.go:66-84`): a SuperAdmin — decided by the
`c.IsAdmin()` predicate alone, which already implies a validated principal —
sees and mutates the whole fleet; a validated org member sees only apps carrying
its own `hanzo.ai/org` label, read-only. The writes (sync, rollback, reconcile)
MUST remain SuperAdmin-only: they change what runs. Secret objects are never
surfaced — no tree node, no manifest — so the projection cannot leak
materialized env (`apps/deploy/deploy.go:36-38`). Console sign-in resolves
SuperAdmins through the `admin-console` IAM application, whose organization is
the reserved admin org (`apps/deploy/login.go:101-106`), and validates tokens
with the same validator the identity boundary uses.

#### §4 Money, events, telemetry

deploy is free, in those words (`plugin/deploy/main.go:21`, `cloud.Free`; not
in `spend.go:275`). It publishes no events on the bus — the stream routes are
per-request server-sent events, not bus topics — and it emits nothing to
observability beyond the request span every route gets.

#### §5 Stage

deploy is `ga`: it is the platform core's delivery lens, part of the
self-service agentic OS rather than a vertical application.

#### §6 Upstream

deploy derives from no forked code. Two third-party facts stand: it reads the
cluster through `k8s.io/client-go` (Apache-2.0), and it serves the CD
dashboard's own address shapes so that console consumes it unchanged
(`apps/deploy/deploy.go:13-15`) — a wire dialect implemented, not code
inherited.

### Rationale

The alternative to reading the cluster is keeping a deployment database and
reconciling it against reality — a second copy of state whose one failure mode,
drift, is exactly what a CD plane exists to expose. Reading the CRs directly
means the plane can be wrong only by being stale, never by disagreeing.

### Security Considerations

The wrong implementation is a cluster console handed to tenants. Three
boundaries hold it: writes require the SuperAdmin predicate, org reads are
filtered by the org label resolved from the validated principal (never a
header), and Secrets are excluded from the tree at the GVR list, so no query
shape can reach them. The OIDC flow fails closed — a missing verifier or public
URL disables sign-in rather than weakening it (`apps/deploy/login.go:119-121`).

## Four surfaces

| Surface  | Reaches this capability as                     | Coverage                                                                   |
| -------- | ---------------------------------------------- | -------------------------------------------------------------------------- |
| **REST** | `deploy` at its own prefix                     | 14 operations                                                              |
| **CLI**  | `hanzo deploy …`                               | 14 of 14                                                                   |
| **SDK**  | `DeployApi` in every published client          | 12 of 14 — the clients are generated at their own release                  |
| **MCP**  | tool `deploy` on `https://api.hanzo.ai/v1/mcp` | 11 operations, 2 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/deploy/login`, operation `get_deploy_login`:

**CLI**

```bash
hanzo deploy login
```

**SDK**

**.ts**

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

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

**.py**

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

client = ApiClient(Configuration(access_token=os.environ["HANZO_API_KEY"]))
result = DeployApi(client).get_deploy_login()
```

**.go**

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

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

**.rs**

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

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

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

**.java**

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

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

var result = new DeployApi(client).getDeployLogin();
```

**HTTP**

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

**MCP**

MCP reaches **deploy** through the `deploy` tool, which names its 11 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": "list_deploy_applications"
         }
       }
     }'
```

## Endpoints

| Endpoint                                                                                                                                                                       | What it does                                                                                                                                                                                                                                                                                                                                               |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`GET /v1/deploy/applications/{name}/resource-tree`](https://docs.hanzo.ai/docs/openapi/deploy/get-deploy-applications-by-name-resource-tree)                                  | Returns one application's argocd ApplicationTree: the objects the operator reconciled from its App CR, reached by ownerRef — the Deployment and, under it, the ReplicaSet and Pods, plus the Service, Ingress, HorizontalPodAutoscaler, PodDisruptionBudget and ConfigMaps it owns — each node carrying its parent edges and its health.                   |
| [`GET /v1/deploy/applications/{name}/revisions/{revision}/metadata`](https://docs.hanzo.ai/docs/openapi/deploy/get-deploy-applications-by-name-revisions-by-revision-metadata) | Returns the argocd RevisionMetadata for one revision of one application — what the detail view shows beside a revision.                                                                                                                                                                                                                                    |
| [`POST /v1/deploy/applications/{name}/rollback`](https://docs.hanzo.ai/docs/openapi/deploy/post-deploy-applications-by-name-rollback)                                          | Serves the console's rollback control, and today it requests a reconcile and nothing more.                                                                                                                                                                                                                                                                 |
| [`GET /v1/deploy/applications/{name}/syncwindows`](https://docs.hanzo.ai/docs/openapi/deploy/get-deploy-applications-by-name-syncwindows)                                      | Returns one application's argocd ApplicationSyncWindowState — the answer to "is anything blocking a sync of this application right now?".                                                                                                                                                                                                                  |
| [`GET /v1/deploy/applications/{name}`](https://docs.hanzo.ai/docs/openapi/deploy/get-deploy-applications-by-name)                                                              | Returns ONE projected argocd Application by name, with status.resources filled in from its reconciled resource tree — which is what makes it the detail view rather than a row of the list.                                                                                                                                                                |
| [`GET /v1/deploy/applications`](https://docs.hanzo.ai/docs/openapi/deploy/get-deploy-applications)                                                                             | Returns the fleet as an argocd ApplicationList: one projected Application per operator App CR, carrying the image tag the CR DECLARES, the tag actually RUNNING in the cluster's Deployment, the reconciled health, and the sync verdict those two produce (declared == running ⇒ Synced, both known and different ⇒ OutOfSync, either unknown ⇒ Unknown). |
| [`GET /v1/deploy/clusters`](https://docs.hanzo.ai/docs/openapi/deploy/get-deploy-clusters)                                                                                     | Returns the argocd ClusterList of the destinations the caller's applications reconcile into: one entry per distinct destination server, carrying the count of applications reconciling into it.                                                                                                                                                            |
| [`GET /v1/deploy/health`](https://docs.hanzo.ai/docs/openapi/deploy/get-deploy-health)                                                                                         | Reports whether this deployment can observe the delivery plane.                                                                                                                                                                                                                                                                                            |
| [`GET /v1/deploy/login`](https://docs.hanzo.ai/docs/openapi/deploy/get-deploy-login)                                                                                           | Start the sign-in round trip for this console                                                                                                                                                                                                                                                                                                              |
| [`POST /v1/deploy/logout`](https://docs.hanzo.ai/docs/openapi/deploy/post-deploy-logout)                                                                                       | Ends the console session on this host.                                                                                                                                                                                                                                                                                                                     |
| [`GET /v1/deploy/projects`](https://docs.hanzo.ai/docs/openapi/deploy/get-deploy-projects)                                                                                     | Returns the argocd AppProjectList this console groups and filters applications by.                                                                                                                                                                                                                                                                         |
| [`GET /v1/deploy/session/userinfo`](https://docs.hanzo.ai/docs/openapi/deploy/get-deploy-session-userinfo)                                                                     | Answers "is this browser signed in, and if not where does it sign in?" — the dashboard SPA's bootstrap question, and the only route on this plane that answers for an anonymous caller.                                                                                                                                                                    |
| [`GET /v1/deploy/stream/applications/{name}/resource-tree`](https://docs.hanzo.ai/docs/openapi/deploy/get-deploy-stream-applications-by-name-resource-tree)                    | Live resource tree for one application, as Server-Sent Events                                                                                                                                                                                                                                                                                              |
| [`GET /v1/deploy/stream/applications`](https://docs.hanzo.ai/docs/openapi/deploy/get-deploy-stream-applications)                                                               | Live application fleet updates as Server-Sent Events                                                                                                                                                                                                                                                                                                       |

---

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