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 · MCP · 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.

Base URLhttps://api.hanzo.ai
Operations5
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 prefix5 operations
CLIhanzo reference …5 of 5
SDKReferenceApi in every published client5 methods
MCPtool reference on https://api.hanzo.ai/v1/mcp5 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 reference list
import { Configuration, ReferenceApi } from 'hanzoai';

const api = new ReferenceApi(new Configuration({ accessToken: process.env.HANZO_API_KEY }));
const { data } = await api.riskReferenceSets();
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()
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
}
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?;
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();
curl https://api.hanzo.ai/v1/reference \
  -H "Authorization: Bearer $HANZO_API_KEY"

Tool reference, op riskReferenceSets — POST the JSON-RPC envelope to https://api.hanzo.ai/v1/mcp.

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

EndpointWhat it does
GET /v1/reference/{set}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/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

Was this page useful?