# Label

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

> The ground-truth plane: what actually turned out to be fraud, who said so, and when they could first have said it.

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

The ground-truth plane: what actually turned out to be fraud, who said so, and when they could first have said it.

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

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

## Specification

> **HIP-1261 · Label — Ground Truth** — Draft · [read the specification →](https://github.com/hanzoai/hips/blob/7ed737180339cd05b5f2935eafe7f7b83d2449a2/HIPs/hip-1261-label.md)

`/v1/label` is the ground-truth plane of the risk product: what actually turned
out to be fraud, who said so, and when they could first have said it. It closes
the loop the decision plane cannot close for itself — `/v1/risk` decides, and
the answer key arrives late, from several places, sometimes in disagreement:
a charge-off, an adjudicated dispute, a closed compliance case, a fraud-reason
refund, an analyst's review, a judged sample (`apps/label/label.go:1-13`). It is
implemented in `hanzoai/cloud` at `apps/label`. This HIP is where HIP-1046 §6's
subtree now lives; HIP-1046 keeps the invariants that hold across the planes.

### Motivation

A label that fed an adverse action is a compliance record. Its writers are
mostly not the decision plane — commerce adjudicates the dispute, the
compliance face closes the case, an analyst files the review — its readers are
the dataset materialiser and the evaluator, and its retention clock is its own:
the label's life is not the life of the decision that cited it
(`apps/label/label.go:55-63`). Own writers, own readers, own clock is why this
is its own capability rather than a subtree of `risk`.

### Specification

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

#### The store

The record is the tenant's own encrypted SQLite file, opened per organization
through `cloud.OrgStore` under the name `label` (`apps/label/label.go:158`).
`hanzo.risk_label` in the shared warehouse is a DERIVED mirror for joining at
training scale (`apps/label/mirror.go:85`, `:213`): the record is written
first, and the mirror's failure is reported rather than fatal. Nothing here
rides the best-effort event path.

Every writing operation ships the tenant's file to its durable object before it
answers, and an unacked ship fails the request (`apps/label/label.go:259`).
This is not optional: the deployment recreates the process on every rollout and
the successor hydrates the durable snapshot OVER the local file, so a write
acknowledged but not shipped would be overwritten by an older copy of the same
tenant's history on the ordinary path (`apps/label/label.go:48-54`).

#### The address

The capability answers under `/v1/label`: filing and listing assertions at the
root, `resolve`, `coverage`, `vocabulary`, `hold` and `dispose`
(`apps/label/typed.go:165-189`). Every operation is typed. The name is
singular by HIP-0139 §2.2 — there is no `/{id}` member route, so this is a
faculty; if a per-assertion read is ever proposed, the name question MUST be
settled before that route ships, because a later flip to `labels` is a second
wire break. Today's router still serves this surface under `/v1/risk/labels`;
that pair is carried by `hanzoai/cloud` `openapi/misfiled.txt` and closes by
fold.

#### The record's three properties

- **Latency.** Every assertion carries when the event happened, when the filer
  says it became knowable, and the DERIVED instant — the later of the filer's
  claim and the server clock at the write, never supplied
  (`apps/label/label.go:17-27`). Resolution takes an observation instant and
  shows only what was knowable then; the guard MUST read the derived instant,
  because a guard whose only input is a caller-chosen value is exactly as
  strong as the caller's honesty.
- **Conflict.** Assertions that disagree BOTH stay; a total order over
  adjudication weight picks the one in force and returns the losers beside it
  (`apps/label/resolve.go:146`). There is no UPDATE statement in the package.
- **Provenance.** Every assertion names its source, its evidence, and the
  identity that filed it, the last stamped server-side from the validated
  principal; a label with no evidence is refused at the door
  (`apps/label/label.go:35-41`).

`hold` places the record behind an answer under litigation hold by its content
digest (`apps/label/typed.go:189`, `apps/label/zipdoc_gen.go:151`); `dispose`
is the tenant's own retention decision. A held record MUST NOT be disposable
while the hold stands.

#### Tenant, meter, events, observability, stage

The organization comes from the validated principal
(`apps/label/label.go:196`) and a request without one is refused with 403
(`apps/label/label.go:194-198`); the per-org file makes the cross-tenant read
unspellable rather than merely forbidden. The capability is free, in those
words (`plugin/label/main.go:29`, `Price: cloud.Free`). It publishes no events
on the bus. Beyond the request span it registers nothing; the mirror's failure
is reported in the answer rather than counted in a private metric. Its stage
is `beta`.

#### Upstream

It derives from none: no OSS project is forked, embedded or mirrored.

### Rationale

The alternative is a `labels` table inside the decision plane's store. It is
one fewer process and it braids two lifetimes: retention policy written for
decisions would silently govern compliance records, and the decision plane's
single-writer store would gain a second class of writer. A separate per-tenant
file keeps one owner per file and lets the record outlive the decisions it
judges.

The mirror exists because training-scale joins over thousands of per-tenant
files are not a query; it is derived and second so that losing it loses
nothing.

### Security Considerations

This plane feeds adverse decisions about people, so its integrity is the
attack surface. A forged or back-dated label poisons every training set built
after it — which is why the knowable instant is server-derived and the filing
identity is stamped from the principal, never taken from the body. A deleted
contested label would resolve a dispute into silence — which is why conflicting
assertions both stay and there is no UPDATE. A cross-tenant read is one
organization's fraud history handed to another — which is why the tenant is a
file, not a predicate. And a record that vanished before litigation ended is
spoliation — which is why hold outranks dispose.

## Four surfaces

| Surface  | Reaches this capability as                    | Coverage     |
| -------- | --------------------------------------------- | ------------ |
| **REST** | `label` at its own prefix                     | 7 operations |
| **CLI**  | `hanzo label …`                               | 7 of 7       |
| **SDK**  | `LabelApi` in every published client          | 7 methods    |
| **MCP**  | tool `label` on `https://api.hanzo.ai/v1/mcp` | 7 operations |

## 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/label`, operation `riskLabels`:

**CLI**

```bash
hanzo label get
```

**SDK**

**.ts**

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

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

**.py**

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

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

**.go**

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

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

**.rs**

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

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

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

**.java**

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

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

var result = new LabelApi(client).riskLabels();
```

**HTTP**

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

**MCP**

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

Answers `200` with `object` — ok.

## Endpoints

| Endpoint                                                                                   | What it does                                                                |
| ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------- |
| [`GET /v1/label/coverage`](https://docs.hanzo.ai/docs/openapi/label/risklabelcoverage)     | How much of the window has matured, and how much of that is judged          |
| [`POST /v1/label/dispose`](https://docs.hanzo.ai/docs/openapi/label/riskdisposelabels)     | Dispose of this tenant's expired assertions, whole records only             |
| [`POST /v1/label/hold`](https://docs.hanzo.ai/docs/openapi/label/riskholdlabels)           | Place or release a litigation hold on named records                         |
| [`POST /v1/label/resolve`](https://docs.hanzo.ai/docs/openapi/label/riskresolvelabels)     | Resolve the label in force for named events, as of each event's own horizon |
| [`GET /v1/label/vocabulary`](https://docs.hanzo.ai/docs/openapi/label/risklabelvocabulary) | The closed vocabularies and the precedence rule that resolves a conflict    |
| [`GET /v1/label`](https://docs.hanzo.ai/docs/openapi/label/risklabels)                     | Read the assertions this tenant has recorded                                |
| [`POST /v1/label`](https://docs.hanzo.ai/docs/openapi/label/risklabel)                     | Assert ground truth about events                                            |

---

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