Hanzo

Reference

Package 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…

Package 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.

Base URLhttps://api.hanzo.ai
Operations6
AuthAuthorization: Bearer $HANZO_API_KEY

Specification

HIP-1262 · Reference — Lookup Sets — Draft · read the specification →

/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

SurfaceReaches this capability asCoverage
RESTreference at its own prefix6 operations
CLIno command reaches it yet — use HTTP or an SDK
SDKReferenceApi in every published client6 methods
MCPtool reference on https://api.hanzo.ai/v1/mcp6 operations

Quickstart

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:

hanzo has no subcommand for this operation — the CLI serves only what cloud's live route table confirms. Use HTTP or an SDK.

Answers 200 with object — ok.

Endpoints

EndpointWhat it does
GET /v1/reference/{set}Reference describes one set and lists your org's overrides in it.
PUT /v1/reference/{set}Writes your organisation's own allow and deny entries over a set.
DELETE /v1/reference/{set}Removes one of your organisation's overrides.
POST /v1/reference/refreshTakes a new version of one set.
POST /v1/reference/resolveLooks keys up against the reference plane.
GET /v1/referenceLists every set this plane publishes, with its version and how fresh it is.

All Hanzo APIs · Interactive reference

How is this guide?

On this page