# Reference

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

> The lookup data a risk decision needs but cannot derive: which email domains hand out throwaway inboxes, which addresses belong to a datacentre or a Tor exit, which card scheme an issuer prefix belongs to, which browsers the fleet sees everywhere, and how current the designation lists the screening engine holds actually are.

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

The lookup data a risk decision needs but cannot derive: which email domains hand out throwaway inboxes, which addresses belong to a datacentre or a Tor exit, which card scheme an issuer prefix belongs to, which browsers the fleet sees everywhere, and how current the designation lists the screening engine holds actually are.

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

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

## Specification

> **HIP-1262 · Reference — Lookup Sets** — Draft · [read the specification →](https://github.com/hanzoai/hips/blob/7ed737180339cd05b5f2935eafe7f7b83d2449a2/HIPs/hip-1262-reference.md)

`/v1/reference` is the lookup data a risk decision needs but cannot derive:
which email domains hand out throwaway inboxes, which addresses belong to a
datacentre or a Tor exit, which card scheme an issuer prefix belongs to, which
browsers the fleet sees everywhere, and how current the designation lists the
screening engine holds actually are (`apps/reference/reference.go:15-19`). It is
implemented in `hanzoai/cloud` at `apps/reference`. This HIP is where HIP-1046
§8's subtree now lives; HIP-1046 keeps the cross-plane invariants.

### Motivation

Every one of these facts could be hard-coded where it is consulted, and each
copy would go stale on its own schedule with nobody noticing — a designation
list that answers "not listed" because it was never loaded is indistinguishable
from a clean world. Making the lookup a capability puts freshness on the wire
and makes staleness a reported condition rather than a silent one.

### Specification

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

#### Two stores, one precedence

The Hanzo-maintained BASELINE lives in the shared warehouse —
`hanzo.reference_source` and `hanzo.reference_entry`, tables with NO tenant
column at all, so a cross-tenant write is unrepresentable rather than merely
forbidden (`apps/reference/store.go:47-49`). A tenant's OWN allow and deny
entries live in that organization's own SQLite file, opened per organization
through `cloud.OrgStore` under the name `reference`
(`apps/reference/override.go`, `apps/reference/reference.go:34-40`).
Resolution MUST be override first, then baseline; first hit wins.

Nothing derived from one organization's rows may enter the baseline, ever. The
only fleet-derived set is an aggregate published above a k-anonymity floor no
single organization can reach alone (`apps/reference/set.go:297-299`).

#### The address

The capability answers under `/v1/reference`: the set list at the root, one set
read, written and cleared at `/{set}` — a SET, which is why the name is singular
— `resolve`, and `refresh` (`apps/reference/reference.go:507-525`). Every
operation is typed. `refresh` writes the shared baseline every organization
reads and is therefore SuperAdmin only (`apps/reference/reference.go:1328-1329`);
it MUST stay one operation under this prefix, never an address family of its own. Today's router still serves
this surface under `/v1/risk/reference`; that pair is carried by
`hanzoai/cloud` `openapi/misfiled.txt` and closes by fold.

#### A version, a refusal, and freshness on the wire

Every set is a VERSION — the content digest of what was taken, so the same
data is the same version whoever fetched it, and a decision records one string
an auditor resolves back to a publisher, a licence and a date. A set that has
never loaded MUST REFUSE rather than answer "not listed"
(`apps/reference/resolve.go:157`), and a set whose source needs a licence we do
not hold is declared as a seam that refuses (`apps/reference/set.go:93-95`).

Freshness rides every answer: the version, when its oldest contributing
publisher was current — the oldest, because a set is exactly as fresh as its
weakest source (`apps/reference/resolve.go:71-73`) — how old that is, and
whether it is past bound. A stale set still answers, and says that it did.

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

Reads and override writes resolve the organization from the validated
principal (`apps/reference/reference.go:549`) and a request without one is
refused; the baseline is readable by every validated org and writable by none
of them. The capability is free, in those words
(`plugin/reference/main.go:21`, `Price: cloud.Free`). It publishes no events on
the bus. Beyond the request span it registers nothing; staleness and refusal —
the two ways this plane can be quietly wrong — are reported on the wire rather
than in a private metric. Its stage is whatever its manifest row declares —
HIP-0139 §8 keeps that in one place, and the `beta` this line used to assert had
already drifted from it.

#### Upstreams

It forks no code; the refusal discipline generalises what `luxfi/aml`
`pkg/reference` states (`apps/reference/reference.go:25-28`). What it MIRRORS
is published data, and the catalog declares each source's licence as a fact
beside its origin (`apps/reference/set.go`):

- `disposable-email-domains` — CC0-1.0 (`set.go:221-223`);
- `crawler-user-agents` — MIT (`set.go:255-257`);
- the Tor bulk exit list — CC BY 3.0 US (`set.go:242`);
- the IANA special-purpose address and AS-number registries — the registries of
  record (`set.go:243-244`, `:269-271`);
- cloud and CDN operators' self-published IP feeds — machine-readable by intent,
  no licence stated and none claimed (`set.go:234-241`);
- issuer prefixes — computed here from ISO/IEC 7812 structural facts; no issuer
  database is licensed or held (`set.go:283-285`);
- sanctions designations — receipt only; they stay with the engine that screens
  (`set.go:310-313`).

A new source MUST enter through this catalog with its licence basis declared, or
as a seam that refuses.

### Rationale

The alternative was to file these operations under the model-serving plane, and
an earlier cut did exactly that (`manifest/apps.go:231-238`): lookup data in a
live product with different customers, where its growth reads as that product's
growth. The other alternative — folding into `risk` — fails on the store rule:
this capability owns two stores of its own, and the baseline's no-tenant-column
shape is a property `risk`'s per-tenant planes cannot host.

### Security Considerations

The baseline is consulted by every organization's decisions, so poisoning it is
the high-value attack: `refresh` is held to the platform's own identity, a
version is a content digest so a tampered take is a different version by
construction, and nothing an organization sends can reach the baseline tables —
their shape has nowhere for it to go. The override plane is the tenant-side
exposure: a per-org file reached only through the validated principal, so one
organization's allow-list cannot bleed into another's decisions. The remaining
risk is absence — an unloaded, unlicensed or stale set silently passing for a
clean answer — closed by refusing the first two and reporting the third.

## Four surfaces

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

**CLI**

```bash
hanzo reference list
```

**SDK**

**.ts**

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

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

**.py**

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

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

**.go**

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

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

**.rs**

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

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

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

**.java**

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

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

var result = new ReferenceApi(client).riskReferenceSets();
```

**HTTP**

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

**MCP**

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

Answers `200` with `object` — ok.

## Endpoints

| Endpoint                                                                                          | What it does                                                                |
| ------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| [`GET /v1/reference/{set}`](https://docs.hanzo.ai/docs/openapi/reference/riskreference)           | Describes one set and lists your org's overrides in it.                     |
| [`PUT /v1/reference/{set}`](https://docs.hanzo.ai/docs/openapi/reference/risksetreference)        | Writes your organisation's own allow and deny entries over a set.           |
| [`DELETE /v1/reference/{set}`](https://docs.hanzo.ai/docs/openapi/reference/riskclearreference)   | Removes one of your organisation's overrides.                               |
| [`POST /v1/reference/resolve`](https://docs.hanzo.ai/docs/openapi/reference/riskresolvereference) | Looks keys up against the reference plane.                                  |
| [`GET /v1/reference`](https://docs.hanzo.ai/docs/openapi/reference/riskreferencesets)             | Lists every set this plane publishes, with its version and how fresh it is. |

---

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