# Ingress

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

> Your edge: automatic TLS certificates and hostname routing to any backend, changed live.

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

Your edge: automatic TLS certificates and hostname routing to any backend, changed live.

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

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

## Specification

> **HIP-1133 · Ingress — The Embedded Edge** — Draft · [read the specification →](https://github.com/hanzoai/hips/blob/7ed737180339cd05b5f2935eafe7f7b83d2449a2/HIPs/hip-1133-ingress-embedded-edge.md)

`/v1/ingress` is the front door as a capability: automatic TLS certificates and
hostname routing to any backend, changed live over an API with no config file
and no restart. It is cloud's embedded, runtime-configurable edge, implemented
in `hanzoai/cloud` at `apps/ingress` (HIP-0106) — a control plane every
deployment mounts, and a data plane only the instance in edge role binds.

### Motivation

A single-binary deployment that needs TLS and host routing should not need a
second proxy process in front of it. The standalone cluster edge (HIP-0068)
remains the fleet's Kubernetes-native proxy; this capability makes the ONE
cloud binary able to BE the edge for deployments where a separate proxy is pure
overhead — point DNS at the instance, POST the routes, and the proxy pod is
gone (`apps/ingress/ingress.go:1-46`).

### Specification

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

#### §1 Two planes, one role flag

The CONTROL plane is `/v1/ingress/*` — routes, services, middlewares, TLS,
status — mounted in every role so config can be authored and inspected. The
DATA plane binds listeners only in edge role (`CLOUD_INGRESS_EDGE_ENABLED`):
:80 for ACME HTTP-01 and the HTTP router, :443 for SNI TLS termination. In app
role the listeners never bind and cloud stays a pure application
(`apps/ingress/ingress.go:12-24`). Every mutation hot-reloads the engine: the
compiled host table is an atomic snapshot, recompiled whole and swapped
pointer-for-pointer, so a change takes effect with no restart and no
per-request lock (`apps/ingress/engine.go:17-26`).

#### §2 The store, and the host as a global claim

One encrypted SQLite file — the deployment's own `ingress`
(`apps/ingress/store.go:46`) — holds every org's edge config as opaque JSON
documents keyed by (org, kind, id). CRUD tenancy is the org column on every
query; a route's HOST, however, is a globally unique DNS claim, enforced unique
ACROSS orgs by a partial unique index, so no org can hijack another's hostname.
The edge compile reads the union across orgs, unambiguous precisely because
hosts are unique (`apps/ingress/store.go:21-45`).

#### §3 The address

Eighteen operations under `/v1/ingress`, all typed; the three deletes answer no
body, which is their shape rather than a gap. Middleware is four orthogonal
edge transforms — scheme redirect, strip-prefix, add-prefix, headers — and an
unknown type is refused at compile (`apps/ingress/middleware.go:9-25`).

#### §4 Tenancy is SuperAdmin

The edge is platform infrastructure, so every operation requires SuperAdmin —
the same predicate the admin surfaces enforce — and storage is scoped to that
validated admin org. A non-admin, a forged principal, or a call arriving off
the HTTP path (a CLI local invoke carries no request) is refused 403, fail
closed, with no second gate to keep in sync
(`apps/ingress/ingress.go:214-238`).

#### §5 Money, events, observability, stage

Free (`cloud.Free`, `plugin/ingress/main.go`). It publishes nothing on the bus
and emits nothing beyond the request span every route gets. Stage `ga`: the
platform core's front door.

#### §6 Upstream

Two, both embedded as libraries: `github.com/vulcand/oxy/v2` v2.2.0
(Apache-2.0) — its `forward` and `roundrobin` survive as the proxy primitives
of the engine (`apps/ingress/engine.go:13-14`) — and
`golang.org/x/crypto/acme/autocert` (BSD-3-Clause), which is the whole ACME
lifecycle: cert issuance against Let's Encrypt, the cache directory, and the
HostPolicy fed from the engine's TLS host set (`apps/ingress/edge.go:10-30`).

### Rationale

The alternative is what HIP-0068 already provides: a separate proxy watching
cluster resources. That is right for the multi-service cluster and wrong for
the one-binary deployment, where it doubles the processes to run something the
binary can carry. Ingress and gateway stay orthogonal on purpose — ingress owns
routing and TLS, gateway owns auth and rate limit — so neither grows the
other's concern (`apps/ingress/ingress.go:48-53`).

### Security Considerations

The control plane is a routing authority: whoever writes it decides where every
served hostname's traffic goes, which is interception, not misconfiguration.
That is why the gate is SuperAdmin rather than org admin, why it fails closed
off the HTTP path, and why the host claim is globally unique — without that
index, tenant A posts tenant B's hostname and the edge compile happily routes
B's traffic to A's backend. The ACME account and cert cache are
deployment-level state; the wrong implementation that let a tenant name extra
TLS hosts would mint certificates for domains the deployment does not own the
routes to.

## Four surfaces

| Surface  | Reaches this capability as                      | Coverage                                                                   |
| -------- | ----------------------------------------------- | -------------------------------------------------------------------------- |
| **REST** | `ingress` at its own prefix                     | 18 operations                                                              |
| **CLI**  | `hanzo ingress …`                               | 18 of 18                                                                   |
| **SDK**  | `IngressApi` in every published client          | 18 methods                                                                 |
| **MCP**  | tool `ingress` on `https://api.hanzo.ai/v1/mcp` | 18 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/ingress/tls`, operation `get_ingress_tls`:

**CLI**

```bash
hanzo ingress tls get
```

**SDK**

**.ts**

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

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

**.py**

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

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

**.go**

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

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

**.rs**

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

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

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

**.java**

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

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

var result = new IngressApi(client).getIngressTls();
```

**HTTP**

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

**MCP**

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

Answers `200` with `object` — ok.

## Endpoints

| Endpoint                                                                                                             | What it does                                                                                                                                                                                                                                                                                                                                                                                                               |
| -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`GET /v1/ingress/middlewares/{id}`](https://docs.hanzo.ai/docs/openapi/ingress/get-ingress-middlewares-by-id)       | Returns one of the caller org's edge transforms by id.                                                                                                                                                                                                                                                                                                                                                                     |
| [`PUT /v1/ingress/middlewares/{id}`](https://docs.hanzo.ai/docs/openapi/ingress/put-ingress-middlewares-by-id)       | Creates or replaces one edge transform and hot-applies it.                                                                                                                                                                                                                                                                                                                                                                 |
| [`DELETE /v1/ingress/middlewares/{id}`](https://docs.hanzo.ai/docs/openapi/ingress/delete-ingress-middlewares-by-id) | Removes one of the caller org's edge transforms and hot-applies the change.                                                                                                                                                                                                                                                                                                                                                |
| [`GET /v1/ingress/middlewares`](https://docs.hanzo.ai/docs/openapi/ingress/get-ingress-middlewares)                  | Returns every edge transform the caller's org has configured, ordered by id.                                                                                                                                                                                                                                                                                                                                               |
| [`POST /v1/ingress/middlewares`](https://docs.hanzo.ai/docs/openapi/ingress/post-ingress-middlewares)                | Creates or replaces one edge transform and hot-applies it.                                                                                                                                                                                                                                                                                                                                                                 |
| [`GET /v1/ingress/routes/{id}`](https://docs.hanzo.ai/docs/openapi/ingress/get-ingress-routes-by-id)                 | Returns one of the caller org's routing rules by id.                                                                                                                                                                                                                                                                                                                                                                       |
| [`PUT /v1/ingress/routes/{id}`](https://docs.hanzo.ai/docs/openapi/ingress/put-ingress-routes-by-id)                 | Creates or replaces one routing rule and hot-applies the new table — there is no config file and no restart.                                                                                                                                                                                                                                                                                                               |
| [`DELETE /v1/ingress/routes/{id}`](https://docs.hanzo.ai/docs/openapi/ingress/delete-ingress-routes-by-id)           | Removes one of the caller org's routing rules and hot-applies the shrunken table, freeing its host for another claim.                                                                                                                                                                                                                                                                                                      |
| [`GET /v1/ingress/routes`](https://docs.hanzo.ai/docs/openapi/ingress/get-ingress-routes)                            | Returns every routing rule the caller's org has configured, ordered by id.                                                                                                                                                                                                                                                                                                                                                 |
| [`POST /v1/ingress/routes`](https://docs.hanzo.ai/docs/openapi/ingress/post-ingress-routes)                          | Creates or replaces one routing rule and hot-applies the new table — there is no config file and no restart.                                                                                                                                                                                                                                                                                                               |
| [`GET /v1/ingress/services/{id}`](https://docs.hanzo.ai/docs/openapi/ingress/get-ingress-services-by-id)             | Returns one of the caller org's backend pools by id.                                                                                                                                                                                                                                                                                                                                                                       |
| [`PUT /v1/ingress/services/{id}`](https://docs.hanzo.ai/docs/openapi/ingress/put-ingress-services-by-id)             | Creates or replaces one backend pool and hot-applies it.                                                                                                                                                                                                                                                                                                                                                                   |
| [`DELETE /v1/ingress/services/{id}`](https://docs.hanzo.ai/docs/openapi/ingress/delete-ingress-services-by-id)       | Removes one of the caller org's backend pools and hot-applies the change.                                                                                                                                                                                                                                                                                                                                                  |
| [`GET /v1/ingress/services`](https://docs.hanzo.ai/docs/openapi/ingress/get-ingress-services)                        | Returns every backend pool the caller's org has configured, ordered by id.                                                                                                                                                                                                                                                                                                                                                 |
| [`POST /v1/ingress/services`](https://docs.hanzo.ai/docs/openapi/ingress/post-ingress-services)                      | Creates or replaces one backend pool and hot-applies it.                                                                                                                                                                                                                                                                                                                                                                   |
| [`GET /v1/ingress/status`](https://docs.hanzo.ai/docs/openapi/ingress/get-ingress-status)                            | Reports the ingress edge's live posture: the role this instance runs in (app or edge), whether its listeners are bound and on which addresses, the ACME posture (staging flag and certificate cache directory), how many hosts the compiled route table currently serves, and how many the ACME HostPolicy will issue a certificate for.                                                                                   |
| [`GET /v1/ingress/tls`](https://docs.hanzo.ai/docs/openapi/ingress/get-ingress-tls)                                  | Returns the caller org's ACME intent together with the edge-wide TLS facts it lands in: which role this instance runs in, whether its listeners are bound, every host the ACME HostPolicy will issue a certificate for (the union across ALL orgs of TLS-marked routes and configured extraHosts, because one process holds one certificate cache), and the ACME directory and account email the process was started with. |
| [`PUT /v1/ingress/tls`](https://docs.hanzo.ai/docs/openapi/ingress/put-ingress-tls)                                  | Replaces the caller org's ACME intent and hot-applies what can be hot-applied.                                                                                                                                                                                                                                                                                                                                             |

---

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