{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/bewary/json-schema/bewary-related-response-schema.json",
  "title": "RelatedResponse",
  "description": "GET /v1/domain/{host}/related (API-CONTRACT section 2.6).",
  "x-generated": "2026-11-02",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/bewary-openapi.json#/components/schemas/RelatedResponse",
  "properties": {
    "schema_version": {
      "type": "string",
      "title": "Schema Version"
    },
    "request_id": {
      "type": "string",
      "title": "Request Id"
    },
    "checked_at": {
      "type": "string",
      "title": "Checked At",
      "description": "RFC 3339 UTC."
    },
    "engine": {
      "type": "string",
      "title": "Engine"
    },
    "domain": {
      "$ref": "#/$defs/RelatedSelf"
    },
    "dns_observed": {
      "type": "string",
      "enum": [
        "stored",
        "live",
        "none"
      ],
      "title": "Dns Observed",
      "description": "stored: from Bewary's hourly re-check history; live: resolved for this call (not a domain Bewary tracks); none: it did not resolve in time."
    },
    "pivots": {
      "items": {
        "$ref": "#/$defs/Pivot"
      },
      "type": "array",
      "title": "Pivots",
      "description": "Every value of the domain's that was considered, used or not."
    },
    "related": {
      "items": {
        "$ref": "#/$defs/RelatedDomain"
      },
      "type": "array",
      "title": "Related",
      "description": "This page, strongest first."
    },
    "page": {
      "type": "integer",
      "title": "Page"
    },
    "per_page": {
      "type": "integer",
      "title": "Per Page"
    },
    "total": {
      "type": "integer",
      "title": "Total",
      "description": "Related domains over all pages (at most 500)."
    },
    "has_more": {
      "type": "boolean",
      "title": "Has More"
    },
    "truncated": {
      "type": "boolean",
      "title": "Truncated",
      "description": "More than 500 related domains: only the strongest 500 are paged."
    },
    "campaigns": {
      "items": {
        "$ref": "#/$defs/Campaign"
      },
      "type": "array",
      "title": "Campaigns",
      "description": "Kits this domain is part of (D4): one page template or one kit's Telegram bot shown by 2+ listed domains, listed being at least half of those showing it, never on a known, brand-owned or parked host."
    },
    "credits": {
      "type": "integer",
      "title": "Credits"
    }
  },
  "type": "object",
  "required": [
    "schema_version",
    "request_id",
    "checked_at",
    "engine",
    "domain",
    "dns_observed",
    "pivots",
    "related",
    "page",
    "per_page",
    "total",
    "has_more",
    "truncated",
    "campaigns",
    "credits"
  ],
  "$defs": {
    "Campaign": {
      "properties": {
        "id": {
          "type": "string",
          "title": "Id",
          "description": "cmp_ and 16 hex: stable, the same kit is the same id in every answer."
        },
        "kind": {
          "type": "string",
          "enum": [
            "page_template",
            "tracking_id"
          ],
          "title": "Kind"
        },
        "value": {
          "type": "string",
          "title": "Value",
          "description": "The shared template hash, or the kit's Telegram bot id (tgbot:<id>)."
        },
        "domains": {
          "type": "integer",
          "title": "Domains",
          "description": "Registrable domains whose pages showed it, the asked one included."
        },
        "listed": {
          "type": "integer",
          "title": "Listed",
          "description": "Of those, on Bewary's scam list: at least 2 and at least half."
        },
        "first_observed": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "First Observed",
          "description": "RFC 3339 UTC: the first page read that showed it."
        },
        "last_observed": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Last Observed",
          "description": "RFC 3339 UTC: the latest."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "id",
        "kind",
        "value",
        "domains",
        "listed",
        "first_observed",
        "last_observed"
      ],
      "title": "Campaign"
    },
    "Pivot": {
      "properties": {
        "kind": {
          "type": "string",
          "enum": [
            "ip",
            "ip_net",
            "asn",
            "name_servers",
            "certificate",
            "registration",
            "page_template",
            "tracking_id"
          ],
          "title": "Kind"
        },
        "value": {
          "type": "string",
          "title": "Value",
          "description": "ip: an IPv4 address; ip_net: its /24; asn: AS<number>; name_servers: the zone's name servers, sorted, comma-joined; certificate: the serial number in hex; registration: the registrar; page_template: the page's structure hash; tracking_id: <kind>:<id>, e.g. ga4:G-ABC123 or tgbot:123456789."
        },
        "domains": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "title": "Domains",
          "description": "Other registrable domains in Bewary's data that share it; null when it was excluded before they were counted (a CDN address, an ASN holding more than 300 of our addresses)."
        },
        "domains_capped": {
          "type": "boolean",
          "title": "Domains Capped",
          "description": "More share it than the cut for its kind: domains is that cut + 1."
        },
        "used": {
          "type": "boolean",
          "title": "Used",
          "description": "It relates the domain to at least one other domain below."
        },
        "excluded": {
          "anyOf": [
            {
              "type": "string",
              "enum": [
                "cdn",
                "shared",
                "dns_provider",
                "parking",
                "platform",
                "not_routable",
                "legitimate_overlap",
                "generic_page"
              ]
            },
            {
              "type": "null"
            }
          ],
          "title": "Excluded",
          "description": "Why it was not pivoted on: cdn (a CDN network), shared (more domains share it than the measured cut, or more ordinary sites than listed domains are behind it), dns_provider (name servers of a DNS provider), parking (a parking service's name servers or network), platform (the zone of a hosting platform the domain is a tenant of), not_routable, legitimate_overlap (also held by a known site, a brand's own domain or, for a page template or tracking id, a parked host), generic_page (also seen on an error page or a bot check)."
        },
        "first_observed": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "First Observed",
          "description": "RFC 3339 UTC. For a certificate, when we logged it; for a registration, the registration time."
        },
        "last_observed": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Last Observed",
          "description": "RFC 3339 UTC. For a certificate, when we logged it; for a registration, the registration time."
        },
        "current": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "null"
            }
          ],
          "title": "Current",
          "description": "DNS kinds: the domain's latest observation still holds it."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "kind",
        "value",
        "domains",
        "domains_capped",
        "used",
        "excluded",
        "first_observed",
        "last_observed",
        "current"
      ],
      "title": "Pivot"
    },
    "RelatedDomain": {
      "properties": {
        "domain": {
          "type": "string",
          "title": "Domain",
          "description": "The related registrable domain."
        },
        "hosts": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "title": "Hosts",
          "description": "Up to 5 of its hosts that carry the relations."
        },
        "verdict": {
          "type": "string",
          "enum": [
            "scam",
            "suspicious",
            "safe",
            "unknown"
          ],
          "title": "Verdict",
          "description": "Its own verdict, from what Bewary holds (as for the asked domain)."
        },
        "p_scam": {
          "type": "number",
          "maximum": 1.0,
          "minimum": 0.0,
          "title": "P Scam"
        },
        "reason_codes": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "title": "Reason Codes"
        },
        "listed": {
          "type": "boolean",
          "title": "Listed"
        },
        "known_site": {
          "type": "boolean",
          "title": "Known Site"
        },
        "strength": {
          "type": "string",
          "enum": [
            "strong",
            "medium",
            "weak"
          ],
          "title": "Strength",
          "description": "Its strongest relation."
        },
        "relations": {
          "items": {
            "$ref": "#/$defs/Relation"
          },
          "type": "array",
          "title": "Relations",
          "description": "Strongest first."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "domain",
        "hosts",
        "verdict",
        "p_scam",
        "reason_codes",
        "listed",
        "known_site",
        "strength",
        "relations"
      ],
      "title": "RelatedDomain"
    },
    "RelatedSelf": {
      "properties": {
        "value": {
          "type": "string",
          "title": "Value",
          "description": "The domain asked about, canonical."
        },
        "registrable_domain": {
          "type": "string",
          "title": "Registrable Domain",
          "description": "Its owner boundary (Public Suffix List with its private section)."
        },
        "verdict": {
          "type": "string",
          "enum": [
            "scam",
            "suspicious",
            "safe",
            "unknown"
          ],
          "title": "Verdict",
          "description": "From what Bewary holds without a new lookup: lists, the name rule, stored protective-DNS refusals. GET /v1/domain/{host} reads more."
        },
        "p_scam": {
          "type": "number",
          "maximum": 1.0,
          "minimum": 0.0,
          "title": "P Scam"
        },
        "reason_codes": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "title": "Reason Codes"
        },
        "listed": {
          "type": "boolean",
          "title": "Listed",
          "description": "On Bewary's scam list."
        },
        "known_site": {
          "type": "boolean",
          "title": "Known Site",
          "description": "A top-100K site or a business's official website."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "value",
        "registrable_domain",
        "verdict",
        "p_scam",
        "reason_codes",
        "listed",
        "known_site"
      ],
      "title": "RelatedSelf"
    },
    "Relation": {
      "properties": {
        "kind": {
          "type": "string",
          "enum": [
            "ip",
            "ip_net",
            "asn",
            "name_servers",
            "certificate",
            "registration",
            "page_template",
            "tracking_id"
          ],
          "title": "Kind"
        },
        "value": {
          "type": "string",
          "title": "Value",
          "description": "The shared value, as in pivots."
        },
        "strength": {
          "type": "string",
          "enum": [
            "strong",
            "medium",
            "weak"
          ],
          "title": "Strength",
          "description": "strong: few domains share it and the two held it at the same time; medium; weak: a /24, an ASN, a registration minute, or a value many share. A link, never a merge."
        },
        "domains": {
          "type": "integer",
          "title": "Domains",
          "description": "Other registrable domains sharing this value (the asked domain excluded)."
        },
        "first_observed": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "First Observed",
          "description": "When the related domain was first seen with it. RFC 3339 UTC. For a certificate, when we logged it; for a registration, the registration time."
        },
        "last_observed": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Last Observed",
          "description": "When it was last seen with it. RFC 3339 UTC. For a certificate, when we logged it; for a registration, the registration time."
        },
        "current": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "null"
            }
          ],
          "title": "Current",
          "description": "DNS kinds: the related domain still holds it."
        },
        "concurrent": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "null"
            }
          ],
          "title": "Concurrent",
          "description": "DNS kinds: the two domains held it at the same time."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "kind",
        "value",
        "strength",
        "domains",
        "first_observed",
        "last_observed",
        "current",
        "concurrent"
      ],
      "title": "Relation"
    }
  }
}