{ "openapi": "3.1.0", "info": { "title": "Hanzo Cloud API", "description": "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.", "version": "v1" }, "servers": [ { "url": "https://api.hanzo.ai" } ], "tags": [ { "name": "risk" } ], "paths": { "/v1/risk/reference": { "get": { "operationId": "riskReferenceSets", "summary": "Lists every set this plane publishes, with its version and how fresh it is.", "description": "Lists every set this plane publishes, with its version and how\nfresh it is.\n\nRead the Stale and Refused lists first: they are the two ways this plane can\nbe quietly wrong, and they are reported rather than inferred. A set in\nRefused answers nothing — it has never loaded, it is held by another\ncomponent, or it names a source we hold no licence for.", "tags": [ "risk" ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ReferenceSetsOut" } } }, "description": "ok" } } } }, "/v1/risk/reference/refresh": { "post": { "operationId": "riskRefreshReference", "summary": "Takes a new version of one set.", "description": "Takes a new version of one set. SuperAdmin only.\n\nIt is platform work, not tenant work: it writes the shared baseline every\norganisation reads, so it is gated to the platform's own identity. Nothing\nhere can write an organisation's overrides, and nothing an organisation sends\ncan reach this route.\n\nIdempotent. A version is the content digest of what was taken, so refreshing\nan unchanged publisher writes no rows and reports unchanged. Resumable: a run\nthat died half-way is continued from where it stopped rather than restarted.\n\nA set whose source needs a licence we do not hold is refused with the reason,\nrather than being quietly skipped.", "tags": [ "risk" ], "requestBody": { "content": { "application/json": { "example": { "set": "domain" }, "schema": { "$ref": "#/components/schemas/RefreshReferenceIn" } } }, "required": true }, "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RefreshReferenceOut" } } }, "description": "ok" } } } }, "/v1/risk/reference/resolve": { "post": { "operationId": "riskResolveReference", "summary": "Looks keys up against the reference plane.", "description": "Looks keys up against the reference plane.\n\nYour organisation's own overrides are consulted FIRST and win outright; the\nshared baseline answers everything they do not cover. Every answer names the\nversion that produced it, when that version was current and whether it is\nstale, so a decision can record exactly what it consulted.\n\nRead Refusal before reading Hit. A set that has never loaded, one held by the\ncomponent that screens against it, and one whose source needs a licence we do\nnot hold all answer with a refusal — and a miss on a refusing set means\nnothing is known, not that the key is clean.", "tags": [ "risk" ], "requestBody": { "content": { "application/json": { "example": { "keys": [ "user@tempbox.example", "3.5.140.1" ], "sets": [ "domain", "net" ] }, "schema": { "$ref": "#/components/schemas/ResolveReferenceIn" } } }, "required": true }, "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ResolveReferenceOut" } } }, "description": "ok" } } } }, "/v1/risk/reference/{set}": { "delete": { "operationId": "riskClearReference", "summary": "Removes one of your organisation's overrides.", "description": "Removes one of your organisation's overrides.\n\nIt removes an entry your organisation wrote, never a baseline member: the\npublished set is not writable from here, so a removal can only ever restore\nthe baseline's own answer.", "tags": [ "risk" ], "parameters": [ { "name": "set", "in": "path", "required": true, "schema": { "type": "string" }, "example": "domain" }, { "name": "key", "in": "query", "required": false, "schema": { "type": "string" }, "example": "partner.example" } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClearReferenceOut" } } }, "description": "ok" } } }, "get": { "operationId": "riskReference", "summary": "Reference describes one set and lists your org's overrides in it.", "description": "Reference describes one set and lists your org's overrides in it.\n\nThe set half is public data about a published list — its version, its\npublishers, their licences and how current each one is. The overrides half is\nyours alone: it is read from your organisation's own store, and no other\norganisation's entries can appear in it.", "tags": [ "risk" ], "parameters": [ { "name": "set", "in": "path", "required": true, "schema": { "type": "string" }, "example": "domain" }, { "name": "after", "in": "query", "required": false, "description": "After pages the override listing: the last key of the previous page.", "schema": { "type": "string" } }, { "name": "limit", "in": "query", "required": false, "description": "Limit caps the override listing: default 200, maximum 1000.", "schema": { "type": "integer" }, "example": 50 } ], "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ReferenceOut" } } }, "description": "ok" } } }, "put": { "operationId": "riskSetReference", "summary": "Writes your organisation's own allow and deny entries over a set.", "description": "Writes your organisation's own allow and deny entries over a set.\n\nIdempotent on the key: writing the same entry twice is one entry, and writing\nit again replaces the verdict and the note. The whole batch is one\ntransaction, so a batch that would cross the per-set bound writes nothing\nrather than half of itself — a half-applied deny list is worse than a refused\none, because nobody can tell which half applied.\n\nYour entries are held in your organisation's own store and are never visible\nto another organisation, and they never change what any other organisation\nsees. The shared baseline is not writable from here at all.", "tags": [ "risk" ], "parameters": [ { "name": "set", "in": "path", "required": true, "schema": { "type": "string" }, "example": "domain" } ], "requestBody": { "content": { "application/json": { "example": { "entries": [ { "key": "partner.example", "note": "our reseller", "verdict": "allow" } ], "set": "domain" }, "schema": { "$ref": "#/components/schemas/SetReferenceIn" } } }, "required": true }, "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SetReferenceOut" } } }, "description": "ok" } } } } }, "components": { "schemas": { "ClearReferenceOut": { "properties": { "cleared": { "description": "Cleared is false when your org held no such override — which is not an\nerror, it is the honest answer to a removal that had nothing to remove.", "type": "boolean" }, "key": { "description": "Key is the entry named.", "type": "string" }, "overrides": { "description": "Overrides is how many your org still holds in this set.", "type": "integer" }, "set": { "description": "Set is the set cleared in.", "type": "string" } }, "type": "object" }, "ReferenceAnswer": { "properties": { "age": { "description": "Age is how old that is, as a duration.", "type": "string" }, "asOf": { "description": "AsOf is when the oldest contributing publisher was current, RFC 3339.", "type": "string" }, "from": { "description": "From is override or baseline — which plane answered.", "type": "string" }, "hit": { "description": "Hit is whether the key is a member. It is meaningful ONLY when Refusal is\nempty: false with a refusal means the set could not be consulted, which is\nnot the same as the key being clean.", "type": "boolean" }, "key": { "description": "Key is the key as asked.", "type": "string" }, "matched": { "description": "Matched is the member that covered the key, which for a domain or a network\nis the enclosing entry rather than the key itself.", "type": "string" }, "refusal": { "description": "Refusal is why the set could not be consulted, when it could not: never\nloaded, held elsewhere, or a source we hold no licence for. Non-empty means\nHit must not be read as an answer.", "type": "string" }, "score": { "description": "Score is the published risk weight where the source expresses one.", "type": "number" }, "set": { "description": "Set is the set consulted.", "type": "string" }, "stale": { "description": "Stale is whether the set is past its freshness bound. A stale set still\nanswers — yesterday's list beats none — and this is how a decision knows it\nleaned on one.", "type": "boolean" }, "value": { "additionalProperties": { "type": "string" }, "description": "Value is what the publisher says about the member — class, operator,\nscheme, region.", "type": "object" }, "verdict": { "description": "Verdict is the tenant's own allow or deny, present only for an override.\nThe baseline never carries one: it states facts and leaves the decision to\nthe caller's policy.", "type": "string" }, "version": { "description": "Version is the exact baseline version consulted, composed of each\ncontributing publisher and its content digest. It is what makes a decision\nreproducible: an auditor takes this string and knows precisely what was\nconsulted.", "type": "string" } }, "type": "object" }, "ReferenceOut": { "properties": { "next": { "description": "Next is the key to page from, empty when this is the last page.", "type": "string" }, "overrides": { "description": "Overrides is YOUR org's entries over that baseline, in key order. They are\nheld in your organisation's own store and are not visible to any other.", "items": { "$ref": "#/components/schemas/ReferenceOverride" }, "type": "array" }, "set": { "$ref": "#/components/schemas/ReferenceSet", "description": "Set is the published set: its version, its freshness and its sources." } }, "type": "object" }, "ReferenceOverride": { "properties": { "at": { "description": "At is when it was written, RFC 3339.", "type": "string" }, "by": { "description": "By is who wrote it.", "type": "string" }, "key": { "description": "Key is the member this organisation is speaking about.", "type": "string" }, "note": { "description": "Note is why, in the operator's own words. Optional, and bounded.", "type": "string" }, "verdict": { "description": "Verdict is allow or deny.", "type": "string" } }, "type": "object" }, "ReferenceOverrideIn": { "properties": { "key": { "description": "Key is the member: a domain, a CIDR or address, an issuer prefix, a\ndevice digest. It is matched the same way the baseline is, so a deny on\ntempbox.example also covers mail.tempbox.example.", "type": "string" }, "note": { "description": "Note is why, in your own words. Optional, bounded to 512 bytes.", "type": "string" }, "verdict": { "description": "Verdict is allow or deny, and nothing else. An override is a decision —\nunlike a baseline entry, which states facts and leaves the decision to your\npolicy — because your organisation is the only party entitled to say \"for\nus, this one is fine\".", "type": "string" } }, "type": "object" }, "ReferenceReceipt": { "properties": { "asOf": { "description": "AsOf is when the load happened, RFC 3339. Absent is dated on arrival, which\ncan only make the list look older than it is.", "type": "string" }, "keys": { "description": "Keys is how many designations that load carried. Zero from a publisher who\ndesignates somebody is a failed load wearing a successful one's clothes,\nand belongs in Refusal instead.", "type": "integer" }, "refusal": { "description": "Refusal is why the load failed, when it did.", "type": "string" }, "source": { "description": "Source is the publisher this receipt is for.", "type": "string" }, "version": { "description": "Version is the digest of what that publisher supplied, so a refresh that\nchanged nothing can be told from a refresh that did not run.", "type": "string" } }, "type": "object" }, "ReferenceSet": { "properties": { "age": { "description": "Age is how long ago that was.", "type": "string" }, "asOf": { "description": "AsOf is when the OLDEST contributing publisher was current, RFC 3339. The\noldest and not the newest: a set is exactly as fresh as its weakest source.", "type": "string" }, "keys": { "description": "Keys is how many members the baseline carries.", "type": "integer" }, "kind": { "description": "Kind is how the baseline comes to exist: fetch (downloaded from a\npublisher), local (computed here), attest (held by the component that\nscreens against it, freshness reported), or seam (declared and NOT held,\nbecause the source needs a licence we do not have).", "type": "string" }, "match": { "description": "Match is how a key is tested: exact, domain, net, digits, pattern or range.", "type": "string" }, "maxAge": { "description": "MaxAge is how old this set may be before it is stale.", "type": "string" }, "overrides": { "description": "Overrides is how many entries YOUR org has laid over this baseline.", "type": "integer" }, "refusal": { "description": "Refusal names why the set cannot be relied on, when it cannot: never\nloaded, held elsewhere, or a licence we do not hold. Non-empty means a\nlookup against this set will not answer, rather than answering clean.", "type": "string" }, "set": { "description": "Set is the name this set is addressed by.", "type": "string" }, "sources": { "description": "Sources is each contributing publisher, its licence and its own freshness.", "items": { "$ref": "#/components/schemas/ReferenceSource" }, "type": "array" }, "stale": { "description": "Stale is whether it is past that bound. A stale set still answers and says\nso, because yesterday's list beats none.", "type": "boolean" }, "version": { "description": "Version is the exact baseline consulted — every contributing publisher and\nits content digest. A decision records this and an auditor resolves it back.", "type": "string" }, "what": { "description": "What the set holds, in one sentence.", "type": "string" } }, "type": "object" }, "ReferenceSetsOut": { "properties": { "refused": { "description": "Refused names the sets that cannot be consulted at all. A key checked\nagainst one of these is UNKNOWN, not clean.", "items": { "type": "string" }, "type": "array" }, "sets": { "description": "Sets is the whole catalog, in a stable order.", "items": { "$ref": "#/components/schemas/ReferenceSet" }, "type": "array" }, "stale": { "description": "Stale names the sets past their freshness bound — the list to alarm on.", "items": { "type": "string" }, "type": "array" } }, "type": "object" }, "ReferenceSource": { "properties": { "asOf": { "description": "AsOf is when this publisher was current, RFC 3339.", "type": "string" }, "basis": { "description": "Basis is the KIND of permission this publisher's data reaches you under:\nlicence (an explicit grant), registry (the registry of record publishing for\nanyone to consult), operator (an operator's own machine-readable statement\nabout its own network, published for third parties to filter by — not a\nlicence, and not claimed as one), own (computed here), or none (nothing\nreaches you: the membership is held by the component that screens against\nit). It is on the wire so the licence position is an audit you can run.", "type": "string" }, "keys": { "description": "Keys is how many members this publisher contributed.", "type": "integer" }, "origin": { "description": "Origin is exactly where it was taken from, so it can be taken again.", "type": "string" }, "refusal": { "description": "Refusal is why this publisher's last take failed, if it did. The set keeps\nits previous version of this source and ages out visibly rather than\nsilently shrinking.", "type": "string" }, "source": { "description": "Source is the publisher.", "type": "string" }, "terms": { "description": "Terms is the CITATION that basis points at — the licence identifier, the\nregistry, or the operator publication. A source with no stated terms is not\nin the catalog.", "type": "string" }, "version": { "description": "Version is the content digest of what this publisher last supplied. Two\nrefreshes that agree on it took the same data.", "type": "string" } }, "type": "object" }, "ReferenceTaken": { "properties": { "keys": { "description": "Keys is how many members it carries.", "type": "integer" }, "refusal": { "description": "Refusal is why this publisher contributed nothing, if it did not. The set\nkeeps its previous version of this source rather than shrinking.", "type": "string" }, "resumed": { "description": "Resumed is true when this run continued a version a previous run left\nhalf-landed.", "type": "boolean" }, "source": { "description": "Source is the publisher.", "type": "string" }, "unchanged": { "description": "Unchanged is true when the publisher's data was byte-for-byte the set we\nalready held.", "type": "boolean" }, "version": { "description": "Version is the content digest that landed.", "type": "string" }, "wrote": { "description": "Wrote is how many rows this run actually wrote. Zero with Unchanged means\nthe publisher served the same set again.", "type": "integer" } }, "type": "object" }, "ReferenceVersion": { "properties": { "asOf": { "description": "AsOf is when the oldest of them was current, RFC 3339.", "type": "string" }, "refusal": { "description": "Refusal is why it could not be consulted, when it could not.", "type": "string" }, "set": { "description": "Set is the set.", "type": "string" }, "stale": { "description": "Stale is whether it is past its freshness bound.", "type": "boolean" }, "version": { "description": "Version is every contributing publisher and its content digest.", "type": "string" } }, "type": "object" }, "RefreshReferenceIn": { "properties": { "force": { "description": "Force accepts a take whose size moved past the change bound. A publisher\nserving a tenth or ten times its previous list is refused by default and the\nprevious version is left standing; this is the operator saying the change is\nreal. It cannot make an empty, truncated or unparseable take land — those are\nerrors, not magnitudes.", "type": "boolean" }, "receipts": { "description": "Receipts are supplied by the component that holds the membership, for a set\nof kind attest. They are refused on any other kind, and a set of kind attest\nis refused without them: this plane never invents a freshness it did not\nobserve.", "items": { "$ref": "#/components/schemas/ReferenceReceipt" }, "type": "array" }, "set": { "description": "Set is the set to refresh.", "type": "string" } }, "type": "object" }, "RefreshReferenceOut": { "properties": { "set": { "description": "Set is the set refreshed.", "type": "string" }, "stale": { "description": "Stale is whether it is STILL past its freshness bound after the refresh,\nwhich is what a publisher that has stopped answering looks like.", "type": "boolean" }, "took": { "description": "Took is what each publisher contributed.", "items": { "$ref": "#/components/schemas/ReferenceTaken" }, "type": "array" }, "version": { "description": "Version is the set's new composed version.", "type": "string" } }, "type": "object" }, "ResolveReferenceIn": { "properties": { "keys": { "description": "Keys are the values to look up, at most 100 per call: email addresses or\ndomains, IP addresses, card prefixes, user-agent strings, autonomous system\nnumbers, device digests.", "items": { "type": "string" }, "type": "array" }, "sets": { "description": "Sets narrows which sets to consult. Empty consults every set whose matcher\ncan read the keys given.", "items": { "type": "string" }, "type": "array" } }, "type": "object" }, "ResolveReferenceOut": { "properties": { "answers": { "description": "Answers is one entry per (set, key) consulted.", "items": { "$ref": "#/components/schemas/ReferenceAnswer" }, "type": "array" }, "consulted": { "description": "Consulted names the version of every set that took part, so a decision can\nrecord precisely what it leaned on. Record this with the decision: it is\nwhat makes the decision reproducible a year later.", "items": { "$ref": "#/components/schemas/ReferenceVersion" }, "type": "array" }, "refused": { "description": "Refused names the consulted sets that could not answer at all. A key that\nmissed in one of these is UNKNOWN, not clean.", "items": { "type": "string" }, "type": "array" }, "stale": { "description": "Stale names the consulted sets past their freshness bound. Staleness is\nitself a risk signal — a decision taken against a three-week-old list is a\nweaker decision, and this is how it knows.", "items": { "type": "string" }, "type": "array" } }, "type": "object" }, "SetReferenceIn": { "properties": { "entries": { "description": "Entries are the overrides to write, up to 1000 per call.", "items": { "$ref": "#/components/schemas/ReferenceOverrideIn" }, "type": "array" } }, "type": "object" }, "SetReferenceOut": { "properties": { "overrides": { "description": "Overrides is how many your org now holds in this set.", "type": "integer" }, "set": { "description": "Set is the set written in.", "type": "string" }, "written": { "description": "Written is how many entries this call wrote.", "type": "integer" } }, "type": "object" } } } }