{
  "openapi": "3.1.0",
  "info": {
    "title": "PhishDestroy Public API",
    "version": "3.3.0",
    "summary": "Free public phishing and crypto-scam domain intelligence.",
    "description": "Public REST API for the PhishDestroy blocklists (DestroyList). No API key is required and CORS is open (`*`).\n\nLists: `primary` (curated, confirmed by PhishDestroy), `community` (aggregated community reports), `primary_active` and `community_active` (subsets whose DNS currently resolves).\n\nData is synchronized every 15 minutes. Static feeds and statistics are regenerated by the same job. An unlisted domain is not a guarantee of safety; do not convert errors or timeouts into clean verdicts.\n\nRate limit: 60 requests per minute per IP (burst 30). Exceeding it returns HTTP 429 with `Retry-After`. HEAD is supported on all GET routes.",
    "termsOfService": "https://phishdestroy.io/terms",
    "contact": {
      "name": "PhishDestroy",
      "url": "https://phishdestroy.io/",
      "email": "contact@phishdestroy.io"
    },
    "license": {
      "name": "Data: see DestroyList repository",
      "url": "https://gitlab.com/phishdestroy/destroylist"
    }
  },
  "servers": [
    {
      "url": "https://api.destroy.tools",
      "description": "Production"
    }
  ],
  "security": [],
  "externalDocs": {
    "url": "https://api.destroy.tools/index.md",
    "description": "Human-readable API reference"
  },
  "tags": [
    {
      "name": "Check",
      "description": "Domain verdicts"
    },
    {
      "name": "Search",
      "description": "Substring search over listed domains"
    },
    {
      "name": "Feeds",
      "description": "Full downloadable lists and incremental deltas"
    },
    {
      "name": "Meta",
      "description": "Statistics and service health"
    },
    {
      "name": "Intelligence",
      "description": "Per-domain enrichment and the bulk RAG corpus. Read-only; absence of intelligence is not evidence of safety; no WHOIS emails / personal data are exposed."
    }
  ],
  "paths": {
    "/v1/check": {
      "get": {
        "tags": [
          "Check"
        ],
        "operationId": "checkDomain",
        "summary": "Check a domain or URL",
        "description": "Input may be a bare domain or a full URL. It is normalized before lookup: scheme, userinfo, port, path, query, fragment and trailing dots are removed, internationalized names are converted to punycode, and a leading `www.` is stripped (the `www.` variant is still matched). Subdomains inherit a parent listing, but parent matching stops at shared-hosting suffixes from the Public Suffix List private section (for example `appspot.com`, `pages.dev`).",
        "parameters": [
          {
            "$ref": "#/components/parameters/DomainQuery"
          },
          {
            "$ref": "#/components/parameters/DomainAliasD"
          },
          {
            "$ref": "#/components/parameters/DomainAliasUrl"
          }
        ],
        "responses": {
          "200": {
            "description": "Verdict. `threat` is the authoritative field; the shape depends on the outcome.",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ListedResult"
                    },
                    {
                      "$ref": "#/components/schemas/WhitelistedResult"
                    },
                    {
                      "$ref": "#/components/schemas/HeuristicResult"
                    },
                    {
                      "$ref": "#/components/schemas/CleanResult"
                    }
                  ]
                },
                "examples": {
                  "listed": {
                    "summary": "Listed domain",
                    "value": {
                      "domain": "example-wallet-claim.xyz",
                      "threat": true,
                      "severity": "critical",
                      "risk_score": 100,
                      "active": true,
                      "listed_in": "confirmed",
                      "source_label": "PhishDestroy confirmed DB",
                      "lists": {
                        "primary": true,
                        "primary_active": true
                      },
                      "flags": [
                        "curated_blocklist",
                        "dns_active",
                        "suspicious_keywords",
                        "risky_tld"
                      ],
                      "matched_keywords": [
                        "wallet",
                        "claim"
                      ],
                      "matched_domain": "example-wallet-claim.xyz",
                      "listed_since": "2026-10-09T07:15:02.000Z",
                      "checked_at": "2026-10-09T09:00:00.000Z"
                    }
                  },
                  "whitelisted": {
                    "summary": "Reviewed false positive",
                    "value": {
                      "domain": "trade.instaswap.com",
                      "threat": false,
                      "whitelisted": true,
                      "message": "Reviewed and cleared as a false positive",
                      "appeal_url": "https://phishdestroy.io/appeals/",
                      "checked_at": "2026-10-09T09:00:00.000Z"
                    }
                  },
                  "suspicious": {
                    "summary": "Unlisted with heuristic signals",
                    "value": {
                      "domain": "metamask-wallet-verify.xyz",
                      "threat": false,
                      "suspicious": true,
                      "heuristic_score": 30,
                      "heuristic_flags": [
                        "suspicious_keywords",
                        "risky_tld"
                      ],
                      "matched_keywords": [
                        "wallet",
                        "verify",
                        "metamask"
                      ],
                      "message": "Not in blocklist — suspicious patterns detected",
                      "note": "Domain not confirmed as phishing but shows suspicious characteristics. Submit at https://phishdestroy.io to report.",
                      "checked_at": "2026-10-09T09:00:00.000Z"
                    }
                  },
                  "clean": {
                    "summary": "Not listed",
                    "value": {
                      "domain": "example.com",
                      "threat": false,
                      "message": "Not found in any blocklist",
                      "note": "Not listed does not guarantee safety",
                      "checked_at": "2026-10-09T09:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid domain.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Missing ?domain= parameter"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/check/bulk": {
      "post": {
        "tags": [
          "Check"
        ],
        "operationId": "bulkCheckDomains",
        "summary": "Check up to 500 domains",
        "description": "Each entry is normalized like `/v1/check`. Results are keyed by the normalized domain; duplicates collapse. Non-string entries are skipped and counted in `summary.invalid`. Maximum request body 256 KB.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              },
              "example": {
                "domains": [
                  "example.com",
                  "example-wallet-claim.xyz"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-domain results.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkResponse"
                },
                "example": {
                  "results": {
                    "example.com": {
                      "threat": false
                    },
                    "example-wallet-claim.xyz": {
                      "threat": true,
                      "severity": "critical",
                      "risk_score": 100,
                      "active": true,
                      "listed_in": "confirmed",
                      "source_label": "PhishDestroy confirmed DB",
                      "lists": {
                        "primary": true
                      },
                      "flags": [
                        "curated_blocklist"
                      ],
                      "matched_domain": "example-wallet-claim.xyz",
                      "listed_since": null
                    }
                  },
                  "summary": {
                    "total": 2,
                    "threats": 1,
                    "clean": 1,
                    "invalid": 0
                  },
                  "checked_at": "2026-10-09T09:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON, missing `domains`, or more than 500 entries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Max 500 per request"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "413": {
            "description": "Request body larger than 256 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Request body too large"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/search": {
      "get": {
        "tags": [
          "Search"
        ],
        "operationId": "searchDomains",
        "summary": "Find listed domains containing a substring",
        "description": "Literal, case-insensitive substring match (`%`, `_` and `\\` are matched literally). Returned names are threat indicators, not links to visit.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Substring, at least 3 characters.",
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 200
            },
            "example": "metamask"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum results.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching domains.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                },
                "example": {
                  "query": "metamask",
                  "count": 2,
                  "limit": 10,
                  "domains": [
                    "metamask-claim.xyz",
                    "secure-metamask.io"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Query shorter than 3 characters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "?q= must be at least 3 chars"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/stats": {
      "get": {
        "tags": [
          "Meta"
        ],
        "operationId": "getStatistics",
        "summary": "Dataset statistics",
        "description": "Precomputed after each synchronization (every 15 minutes). Counts describe the dataset, not takedowns.",
        "responses": {
          "200": {
            "description": "Statistics.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Stats"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "tags": [
          "Meta"
        ],
        "operationId": "getHealth",
        "summary": "Service health",
        "description": "Database reachability and synchronization freshness. Never cached. Returns 503 when the database is unreachable or the last synchronization is older than 2 hours.",
        "responses": {
          "200": {
            "description": "Healthy or degraded (degraded = last sync status is not `ok` but data is recent).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                },
                "example": {
                  "status": "ok",
                  "db": "ok",
                  "last_sync": "2026-10-09T08:45:08.000Z",
                  "last_sync_status": "ok",
                  "sync_age_seconds": 512,
                  "checked_at": "2026-10-09T08:53:40.000Z"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "description": "Down.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                },
                "example": {
                  "status": "down",
                  "db": "error",
                  "last_sync": null,
                  "last_sync_status": null,
                  "sync_age_seconds": null,
                  "checked_at": "2026-10-09T08:53:40.000Z"
                }
              }
            }
          }
        }
      }
    },
    "/v1/feeds": {
      "get": {
        "tags": [
          "Feeds"
        ],
        "operationId": "listFeeds",
        "summary": "Feed catalog",
        "description": "Available lists, their sizes, last update times and download URLs with byte sizes and SHA-256 checksums for each format.",
        "responses": {
          "200": {
            "description": "Feed catalog.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FeedCatalog"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/feed/{list}": {
      "get": {
        "tags": [
          "Feeds"
        ],
        "operationId": "getDomainFeed",
        "summary": "Full feed (JSON)",
        "description": "Same as `/v1/feed/{list}.json`. Static file regenerated only when the list changes. Supports `ETag`/`Last-Modified` conditional requests (304) and gzip. `Cache-Control: public, max-age=300`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ListPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Full list.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Last-Modified": {
                "$ref": "#/components/headers/LastModified"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Feed"
                }
              }
            }
          },
          "304": {
            "description": "Not modified (conditional request)."
          },
          "404": {
            "description": "Unknown list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unknown list. Available: primary, community, primary_active, community_active"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/feed/{list}.{format}": {
      "get": {
        "tags": [
          "Feeds"
        ],
        "operationId": "downloadDomainFeed",
        "summary": "Full feed in a specific format",
        "description": "- `txt` — one domain per line.\n- `json` — `{list, count, updated_at, domains[]}`.\n- `hosts` — `0.0.0.0 <domain>` lines with a short `#` header (Pi-hole, hosts files).\n- `adblock` — `||<domain>^` lines with `! Title/Updated/Homepage` header (AdGuard, uBlock Origin).\n\nSupports `ETag`/`Last-Modified` (304) and gzip. `Cache-Control: public, max-age=300`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ListPath"
          },
          {
            "name": "format",
            "in": "path",
            "required": true,
            "description": "File format.",
            "schema": {
              "type": "string",
              "enum": [
                "txt",
                "json",
                "hosts",
                "adblock"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Feed file.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Last-Modified": {
                "$ref": "#/components/headers/LastModified"
              }
            },
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "example-wallet-claim.xyz\nsecure-metamask.io\n"
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Feed"
                }
              }
            }
          },
          "304": {
            "description": "Not modified (conditional request)."
          },
          "404": {
            "description": "Unknown list or format.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unknown list. Available: primary, community, primary_active, community_active"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/feed/{list}/delta": {
      "get": {
        "tags": [
          "Feeds"
        ],
        "operationId": "getFeedDelta",
        "summary": "Incremental changes since a timestamp",
        "description": "Domains added to and removed from a list after `since`. Typical use: download the full `.txt` once, then poll this endpoint with `since` set to the previous response `until`. If `truncated` is true, request again with `since = next_since` (an opaque cursor; paging is lossless and keeps the same `until`). Removals are retained for 30 days.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ListPath"
          },
          {
            "name": "since",
            "in": "query",
            "required": true,
            "description": "ISO-8601 timestamp (UTC recommended), or the `next_since` cursor from a truncated response.",
            "schema": {
              "type": "string"
            },
            "example": "2026-10-09T08:00:00Z"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum entries per page (added and removed combined).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100000,
              "default": 50000
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Changes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FeedDelta"
                },
                "example": {
                  "list": "primary",
                  "since": "2026-10-09T08:00:00.000Z",
                  "until": "2026-10-09T09:00:00.000Z",
                  "added": [
                    "new-drainer.xyz"
                  ],
                  "removed": [
                    "trade.instaswap.com"
                  ],
                  "truncated": false,
                  "next_since": null
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid `since`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "?since= must be an ISO-8601 timestamp"
                }
              }
            }
          },
          "404": {
            "description": "Unknown list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unknown list. Available: primary, community, primary_active, community_active"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "410": {
            "description": "`since` is older than the retention window.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SinceTooOld"
                },
                "example": {
                  "error": "since_too_old",
                  "retention_days": 30,
                  "full_feed": "/v1/feed/primary.txt"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/intel": {
      "get": {
        "tags": [
          "Intelligence"
        ],
        "operationId": "getDomainIntel",
        "summary": "Per-domain intelligence",
        "description": "Structured intelligence for a single confirmed domain: classification, reputation, hosting, lifecycle, fingerprint, evidence and an AI-written report. Returns `found: false` for a domain not in the dataset. WHOIS emails / personal data are never included. The AI report is machine-generated analysis, not a legal determination.",
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "required": true,
            "description": "A listed domain or URL. Normalized before lookup; the `www.` variant is also tried.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 2048
            },
            "example": "example.com"
          }
        ],
        "responses": {
          "200": {
            "description": "Intelligence record (or `found:false`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IntelResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid `domain`."
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/similar": {
      "get": {
        "tags": [
          "Intelligence"
        ],
        "operationId": "getSimilarDomains",
        "summary": "Similar domains (actor clustering)",
        "description": "Domains likely run by the same actor as the seed domain, clustered by shared `favicon_hash`, `ip_address` or `kit_name`. A signal is used only when it is specific to an actor; generic shared infrastructure (blank favicon, shared CDN IP, broad kit) is dropped to avoid over-clustering. Returns `clustered_by` and a per-row `matched_by`.",
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "required": true,
            "description": "A listed domain or URL. Normalized before lookup; the `www.` variant is also tried.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 2048
            },
            "example": "example.com"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum rows.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Actor cluster.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SimilarResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid `domain`."
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/campaign": {
      "get": {
        "tags": [
          "Intelligence"
        ],
        "operationId": "getCampaign",
        "summary": "Brand / kit campaign",
        "description": "With `brand` or `kit`, returns the matching campaign with `total`, `alive`, `distinct_ips` and a page of `domains`. With no parameter, returns `type: \"brand_index\"` and the top 50 brands by tracked-domain count. Counts describe tracked impersonation domains, not takedowns.",
        "parameters": [
          {
            "name": "brand",
            "in": "query",
            "required": false,
            "description": "Impersonated brand (case-insensitive). Mutually exclusive with `kit`. Omit both for the brand index.",
            "schema": {
              "type": "string"
            },
            "example": "Ledger"
          },
          {
            "name": "kit",
            "in": "query",
            "required": false,
            "description": "Phishing kit name (case-insensitive). Mutually exclusive with `brand`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum rows.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Row offset for paging.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A campaign page, or the brand index.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/Campaign"
                    },
                    {
                      "$ref": "#/components/schemas/BrandIndex"
                    }
                  ]
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/investigate": {
      "get": {
        "tags": [
          "Intelligence"
        ],
        "operationId": "investigateDomain",
        "summary": "Investigate a domain or URL",
        "description": "One call that answers \"what do we know about this link?\". `verdict` is the authoritative blocklist result. `assessment` is a prediction from PhishDestroy data (brand impersonation, lookalikes of known phishing, shared naming patterns, phishing-infrastructure IP, risky TLD) with a risk level and the signals behind it; established high-traffic domains (Tranco top 1M) are capped. `context` is a plain-text summary with numbered sources for language models. A `none` risk is not evidence of safety, and a prediction is not a confirmed listing.",
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "required": true,
            "description": "Any domain or URL, listed or not.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 2048
            },
            "example": "example.com"
          }
        ],
        "responses": {
          "200": {
            "description": "Investigation result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Investigation"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "400": {
            "description": "Missing or invalid `domain`."
          }
        }
      }
    },
    "/v1/intel/search": {
      "get": {
        "tags": [
          "Intelligence"
        ],
        "operationId": "searchThreatIntel",
        "summary": "Search threat knowledge",
        "description": "Search confirmed phishing records by words: brands, kits, drainers, scam types, registrars. Returns the best matches (at most 10, no paging) with a citable `context`; full records are at `/v1/intel`.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Words to search for; at least 3 letters or digits. Quoted phrases and `or` are supported.",
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 200
            },
            "example": "angel drainer metamask"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum results.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10,
              "default": 5
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching cases.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KnowledgeSearch"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "400": {
            "description": "Missing or too short `q`."
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "tags": [
          "Intelligence"
        ],
        "operationId": "mcp",
        "summary": "MCP server (Streamable HTTP)",
        "description": "Model Context Protocol endpoint for AI clients: JSON-RPC 2.0 over HTTP POST, plain JSON responses, no session and no authentication. Supports initialize, ping, tools/list and tools/call with the tools investigate_domain, check_domain, bulk_check_domains, search_threat_intel and get_campaign. Notifications are answered with HTTP 202. GET returns 405 (no server-sent event stream).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": [
                  "object",
                  "array"
                ]
              },
              "example": {
                "jsonrpc": "2.0",
                "id": 1,
                "method": "tools/call",
                "params": {
                  "name": "investigate_domain",
                  "arguments": {
                    "domain": "example.com"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response.",
            "content": {
              "application/json": {
                "schema": {
                  "type": [
                    "object",
                    "array"
                  ]
                }
              }
            }
          },
          "202": {
            "description": "Notification accepted."
          },
          "400": {
            "description": "Parse error or invalid request."
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "DomainQuery": {
        "name": "domain",
        "in": "query",
        "required": false,
        "description": "Domain or URL to check. One of `domain`, `d` or `url` is required.",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 2048
        },
        "example": "example.com"
      },
      "DomainAliasD": {
        "name": "d",
        "in": "query",
        "required": false,
        "description": "Alias of `domain`.",
        "schema": {
          "type": "string"
        }
      },
      "DomainAliasUrl": {
        "name": "url",
        "in": "query",
        "required": false,
        "description": "Alias of `domain`; full URLs are accepted.",
        "schema": {
          "type": "string"
        }
      },
      "ListPath": {
        "name": "list",
        "in": "path",
        "required": true,
        "description": "List name.",
        "schema": {
          "type": "string",
          "enum": [
            "primary",
            "community",
            "primary_active",
            "community_active"
          ]
        }
      }
    },
    "headers": {
      "ETag": {
        "description": "Entity tag for conditional requests (If-None-Match).",
        "schema": {
          "type": "string"
        }
      },
      "LastModified": {
        "description": "Last regeneration time (If-Modified-Since).",
        "schema": {
          "type": "string"
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying.",
        "schema": {
          "type": "integer",
          "example": 7
        }
      },
      "Allow": {
        "description": "Allowed methods for this route.",
        "schema": {
          "type": "string",
          "example": "GET, HEAD"
        }
      }
    },
    "responses": {
      "MethodNotAllowed": {
        "description": "Method not allowed for this route.",
        "headers": {
          "Allow": {
            "$ref": "#/components/headers/Allow"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Method not allowed"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Quota exceeded. Two quotas per IP: data 300 units/min, intel 30 units/min + 1,000/day; 10x for Claude/ChatGPT connector IPs. Retry-After is the exact wait.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/RateLimitError"
            },
            "example": {
              "error": "rate_limited",
              "retry_after": 7,
              "bucket": "intel",
              "cost": 3,
              "tier": "anonymous"
            }
          }
        }
      },
      "InternalError": {
        "description": "Unexpected server error. Not a clean verdict.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Internal error"
            }
          }
        }
      }
    },
    "schemas": {
      "ListName": {
        "type": "string",
        "enum": [
          "primary",
          "community",
          "primary_active",
          "community_active"
        ]
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "RateLimitError": {
        "type": "object",
        "required": [
          "error",
          "retry_after"
        ],
        "properties": {
          "error": {
            "const": "rate_limited"
          },
          "retry_after": {
            "type": "integer"
          },
          "bucket": {
            "type": "string",
            "enum": [
              "data",
              "intel",
              "intel-day"
            ]
          },
          "cost": {
            "type": "integer"
          },
          "tier": {
            "type": "string",
            "enum": [
              "anonymous",
              "platform"
            ]
          },
          "message": {
            "type": "string"
          },
          "hint": {
            "type": "string"
          },
          "docs": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "SinceTooOld": {
        "type": "object",
        "required": [
          "error",
          "retention_days",
          "full_feed"
        ],
        "properties": {
          "error": {
            "const": "since_too_old"
          },
          "retention_days": {
            "type": "integer",
            "example": 30
          },
          "full_feed": {
            "type": "string",
            "example": "/v1/feed/primary.txt"
          }
        }
      },
      "Lists": {
        "type": "object",
        "description": "Lists containing the matched domain. Only true values are present.",
        "properties": {
          "primary": {
            "type": "boolean",
            "const": true
          },
          "community": {
            "type": "boolean",
            "const": true
          },
          "primary_active": {
            "type": "boolean",
            "const": true
          },
          "community_active": {
            "type": "boolean",
            "const": true
          }
        },
        "additionalProperties": false
      },
      "ListedResult": {
        "type": "object",
        "required": [
          "domain",
          "threat",
          "severity",
          "risk_score",
          "listed_in",
          "lists",
          "flags",
          "matched_domain",
          "checked_at"
        ],
        "properties": {
          "domain": {
            "type": "string",
            "description": "Normalized input."
          },
          "threat": {
            "const": true
          },
          "severity": {
            "type": "string",
            "enum": [
              "critical",
              "high",
              "medium",
              "low"
            ]
          },
          "risk_score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "active": {
            "type": "boolean",
            "description": "Present in an *_active list (DNS resolves)."
          },
          "listed_in": {
            "type": "string",
            "enum": [
              "confirmed_and_community",
              "confirmed",
              "community",
              "heuristic"
            ]
          },
          "source_label": {
            "type": "string"
          },
          "lists": {
            "$ref": "#/components/schemas/Lists"
          },
          "flags": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "curated_blocklist",
                "community_reported",
                "dns_active",
                "multi_source",
                "suspicious_keywords",
                "risky_tld"
              ]
            }
          },
          "matched_keywords": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "matched_domain": {
            "type": "string",
            "description": "The listed name that matched (input, its www. variant, or a parent domain)."
          },
          "input_was_subdomain": {
            "type": "boolean",
            "description": "True when a parent domain matched."
          },
          "listed_since": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the matched domain was first listed. Confirmed (primary) entries carry the original PhishDestroy detection time; other entries carry the first time the sync saw them (tracking began 2026-10-09). null = unknown."
          },
          "checked_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WhitelistedResult": {
        "type": "object",
        "required": [
          "domain",
          "threat",
          "whitelisted",
          "checked_at"
        ],
        "properties": {
          "domain": {
            "type": "string"
          },
          "threat": {
            "const": false
          },
          "whitelisted": {
            "const": true,
            "description": "Domain was reviewed and removed as a false positive."
          },
          "message": {
            "type": "string",
            "example": "Reviewed and cleared as a false positive"
          },
          "appeal_url": {
            "type": "string",
            "format": "uri",
            "example": "https://phishdestroy.io/appeals/"
          },
          "checked_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "HeuristicResult": {
        "type": "object",
        "required": [
          "domain",
          "threat",
          "suspicious",
          "heuristic_score",
          "checked_at"
        ],
        "properties": {
          "domain": {
            "type": "string"
          },
          "threat": {
            "const": false
          },
          "suspicious": {
            "type": "boolean",
            "description": "True when heuristic_score ≥ 20."
          },
          "heuristic_score": {
            "type": "integer",
            "minimum": 0
          },
          "heuristic_flags": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "suspicious_keywords",
                "risky_tld"
              ]
            }
          },
          "matched_keywords": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "message": {
            "type": "string"
          },
          "note": {
            "type": "string"
          },
          "checked_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CleanResult": {
        "type": "object",
        "required": [
          "domain",
          "threat",
          "checked_at"
        ],
        "properties": {
          "domain": {
            "type": "string"
          },
          "threat": {
            "const": false
          },
          "message": {
            "type": "string",
            "example": "Not found in any blocklist"
          },
          "note": {
            "type": "string",
            "example": "Not listed does not guarantee safety"
          },
          "checked_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "BulkRequest": {
        "type": "object",
        "required": [
          "domains"
        ],
        "properties": {
          "domains": {
            "type": "array",
            "minItems": 1,
            "maxItems": 500,
            "items": {
              "type": "string",
              "minLength": 1
            }
          }
        },
        "additionalProperties": false
      },
      "BulkItem": {
        "anyOf": [
          {
            "$ref": "#/components/schemas/BulkListedItem"
          },
          {
            "type": "object",
            "required": [
              "threat",
              "whitelisted"
            ],
            "properties": {
              "threat": {
                "const": false
              },
              "whitelisted": {
                "const": true
              }
            }
          },
          {
            "type": "object",
            "required": [
              "threat"
            ],
            "properties": {
              "threat": {
                "const": false
              }
            }
          }
        ]
      },
      "BulkResponse": {
        "type": "object",
        "required": [
          "results",
          "summary",
          "checked_at"
        ],
        "properties": {
          "results": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/BulkItem"
            },
            "description": "Keyed by normalized domain."
          },
          "summary": {
            "type": "object",
            "required": [
              "total",
              "threats",
              "clean"
            ],
            "properties": {
              "total": {
                "type": "integer"
              },
              "threats": {
                "type": "integer"
              },
              "clean": {
                "type": "integer"
              },
              "invalid": {
                "type": "integer",
                "description": "Skipped non-string or unparsable entries."
              }
            }
          },
          "checked_at": {
            "type": "string",
            "format": "date-time"
          },
          "invalid_inputs": {
            "type": "array",
            "items": {},
            "maxItems": 50,
            "description": "First 50 inputs that could not be parsed as a domain (present only when there are any)."
          }
        }
      },
      "SearchResponse": {
        "type": "object",
        "required": [
          "query",
          "count",
          "limit",
          "domains"
        ],
        "properties": {
          "query": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "domains": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Stats": {
        "type": "object",
        "required": [
          "counts",
          "api_version"
        ],
        "properties": {
          "counts": {
            "type": "object",
            "properties": {
              "primary": {
                "type": "integer"
              },
              "community": {
                "type": "integer"
              },
              "primary_active": {
                "type": "integer"
              },
              "community_active": {
                "type": "integer"
              },
              "total_unique": {
                "type": "integer",
                "description": "Distinct domains across all lists."
              },
              "total_active": {
                "type": "integer",
                "description": "Distinct domains in *_active lists."
              },
              "active_memberships": {
                "type": "integer"
              }
            }
          },
          "lastSync": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "sync": {
            "type": "object",
            "properties": {
              "strategy": {
                "type": "string"
              },
              "status": {
                "type": "string",
                "example": "ok"
              },
              "by_list": {
                "type": "object",
                "additionalProperties": {
                  "type": "object",
                  "properties": {
                    "completed_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "progress": {
                      "type": "null"
                    }
                  }
                }
              }
            }
          },
          "api_version": {
            "type": "string",
            "example": "3.2"
          },
          "endpoints": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "project": {
            "type": "string"
          },
          "repo": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Health": {
        "type": "object",
        "required": [
          "status",
          "db",
          "checked_at"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "degraded",
              "down"
            ]
          },
          "db": {
            "type": "string",
            "enum": [
              "ok",
              "error"
            ]
          },
          "last_sync": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_sync_status": {
            "type": [
              "string",
              "null"
            ]
          },
          "sync_age_seconds": {
            "type": [
              "integer",
              "null"
            ]
          },
          "checked_at": {
            "type": "string",
            "format": "date-time"
          },
          "stale_lists": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ListName"
            },
            "description": "Lists not refreshed in the last 2 hours; any entry makes status `degraded`."
          },
          "lists": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "last_sync": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                },
                "age_seconds": {
                  "type": [
                    "integer",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "FeedFile": {
        "type": "object",
        "required": [
          "url",
          "bytes",
          "sha256"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "bytes": {
            "type": "integer"
          },
          "sha256": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$"
          }
        }
      },
      "FeedCatalog": {
        "type": "object",
        "required": [
          "updated_at",
          "lists"
        ],
        "properties": {
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "lists": {
            "type": "object",
            "propertyNames": {
              "$ref": "#/components/schemas/ListName"
            },
            "additionalProperties": {
              "type": "object",
              "required": [
                "count",
                "updated_at",
                "files"
              ],
              "properties": {
                "count": {
                  "type": "integer"
                },
                "updated_at": {
                  "type": "string",
                  "format": "date-time"
                },
                "files": {
                  "type": "object",
                  "properties": {
                    "txt": {
                      "$ref": "#/components/schemas/FeedFile"
                    },
                    "json": {
                      "$ref": "#/components/schemas/FeedFile"
                    },
                    "hosts": {
                      "$ref": "#/components/schemas/FeedFile"
                    },
                    "adblock": {
                      "$ref": "#/components/schemas/FeedFile"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "Feed": {
        "type": "object",
        "required": [
          "list",
          "count",
          "domains"
        ],
        "properties": {
          "list": {
            "$ref": "#/components/schemas/ListName"
          },
          "count": {
            "type": "integer"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "domains": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "FeedDelta": {
        "type": "object",
        "required": [
          "list",
          "since",
          "until",
          "added",
          "removed",
          "truncated",
          "next_since"
        ],
        "properties": {
          "list": {
            "$ref": "#/components/schemas/ListName"
          },
          "since": {
            "type": "string",
            "format": "date-time"
          },
          "until": {
            "type": "string",
            "format": "date-time",
            "description": "Snapshot upper bound (time of the last completed sync). Use as the next `since` after the final page."
          },
          "added": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "removed": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "truncated": {
            "type": "boolean"
          },
          "next_since": {
            "type": [
              "string",
              "null"
            ],
            "description": "Opaque cursor, set when truncated; pass it back as `since` to continue."
          }
        }
      },
      "BulkListedItem": {
        "type": "object",
        "description": "Listed result inside a bulk response (no `domain`/`checked_at`; the key is the domain).",
        "required": [
          "threat",
          "severity",
          "risk_score",
          "listed_in",
          "lists",
          "flags",
          "matched_domain"
        ],
        "properties": {
          "threat": {
            "const": true
          },
          "severity": {
            "type": "string",
            "enum": [
              "critical",
              "high",
              "medium",
              "low"
            ]
          },
          "risk_score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "active": {
            "type": "boolean",
            "description": "Present in an *_active list (DNS resolves)."
          },
          "listed_in": {
            "type": "string",
            "enum": [
              "confirmed_and_community",
              "confirmed",
              "community",
              "heuristic"
            ]
          },
          "source_label": {
            "type": "string"
          },
          "lists": {
            "$ref": "#/components/schemas/Lists"
          },
          "flags": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "curated_blocklist",
                "community_reported",
                "dns_active",
                "multi_source",
                "suspicious_keywords",
                "risky_tld"
              ]
            }
          },
          "matched_keywords": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "matched_domain": {
            "type": "string",
            "description": "The listed name that matched (input, its www. variant, or a parent domain)."
          },
          "input_was_subdomain": {
            "type": "boolean",
            "description": "True when a parent domain matched."
          },
          "listed_since": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the matched domain was first listed. Confirmed (primary) entries carry the original PhishDestroy detection time; other entries carry the first time the sync saw them (tracking began 2026-10-09). null = unknown."
          }
        }
      },
      "IntelResponse": {
        "type": "object",
        "properties": {
          "domain": {
            "type": "string"
          },
          "found": {
            "type": "boolean",
            "description": "Whether the domain is in the dataset."
          },
          "intel": {
            "type": "object",
            "nullable": true,
            "description": "Present when `found` is true.",
            "properties": {
              "domain": {
                "type": "string"
              },
              "classification": {
                "type": "object",
                "properties": {
                  "target_brand": {
                    "type": "string",
                    "nullable": true
                  },
                  "drainer_type": {
                    "type": "string",
                    "nullable": true
                  },
                  "kit_name": {
                    "type": "string",
                    "nullable": true
                  },
                  "scam_type": {
                    "type": "string",
                    "nullable": true
                  }
                }
              },
              "reputation": {
                "type": "object",
                "properties": {
                  "vt_detections": {
                    "type": "integer",
                    "nullable": true
                  },
                  "vt_vendors": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "nullable": true
                  },
                  "gsb_flagged": {
                    "type": "boolean",
                    "nullable": true
                  },
                  "blocklist_count": {
                    "type": "integer",
                    "nullable": true
                  },
                  "blocklist_hits": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "nullable": true
                  },
                  "gs_trust_score": {
                    "type": "integer",
                    "nullable": true
                  }
                }
              },
              "hosting": {
                "type": "object",
                "properties": {
                  "ip_address": {
                    "type": "string",
                    "nullable": true
                  },
                  "registrar": {
                    "type": "string",
                    "nullable": true
                  },
                  "registrar_country": {
                    "type": "string",
                    "nullable": true
                  },
                  "nameservers": {
                    "nullable": true
                  }
                }
              },
              "lifecycle": {
                "type": "object",
                "properties": {
                  "detected_at": {
                    "type": "string",
                    "nullable": true
                  },
                  "creation_date": {
                    "type": "string",
                    "nullable": true
                  },
                  "expiration_date": {
                    "type": "string",
                    "nullable": true
                  },
                  "site_status": {
                    "type": "string",
                    "nullable": true
                  },
                  "http_status": {
                    "type": "integer",
                    "nullable": true
                  },
                  "last_alive": {
                    "type": "string",
                    "nullable": true
                  },
                  "killed_at": {
                    "type": "string",
                    "nullable": true
                  }
                }
              },
              "fingerprint": {
                "type": "object",
                "properties": {
                  "favicon_hash": {
                    "type": "string",
                    "nullable": true
                  },
                  "technologies": {
                    "nullable": true
                  },
                  "page_title": {
                    "type": "string",
                    "nullable": true
                  }
                }
              },
              "evidence": {
                "type": "object",
                "properties": {
                  "screenshot_url": {
                    "type": "string",
                    "nullable": true
                  },
                  "wayback_url": {
                    "type": "string",
                    "nullable": true
                  },
                  "urlscan_uuid": {
                    "type": "string",
                    "nullable": true
                  },
                  "cloudflare_radar_url": {
                    "type": "string",
                    "nullable": true
                  },
                  "case_id": {
                    "type": "string",
                    "nullable": true
                  }
                }
              },
              "ai": {
                "type": "object",
                "description": "Machine-generated analyst report.",
                "properties": {
                  "title": {
                    "type": "string",
                    "nullable": true
                  },
                  "description": {
                    "type": "string",
                    "nullable": true
                  },
                  "summary": {
                    "type": "string",
                    "nullable": true
                  },
                  "generated_at": {
                    "type": "string",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "required": [
          "domain",
          "found"
        ]
      },
      "SimilarResponse": {
        "type": "object",
        "properties": {
          "domain": {
            "type": "string"
          },
          "found": {
            "type": "boolean"
          },
          "seed": {
            "type": "object",
            "properties": {
              "target_brand": {
                "type": "string",
                "nullable": true
              },
              "kit_name": {
                "type": "string",
                "nullable": true
              },
              "ip_address": {
                "type": "string",
                "nullable": true
              },
              "favicon_hash": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "clustered_by": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "favicon",
                "ip",
                "kit"
              ]
            }
          },
          "count": {
            "type": "integer"
          },
          "similar": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "domain": {
                  "type": "string"
                },
                "target_brand": {
                  "type": "string",
                  "nullable": true
                },
                "kit_name": {
                  "type": "string",
                  "nullable": true
                },
                "drainer_type": {
                  "type": "string",
                  "nullable": true
                },
                "ip_address": {
                  "type": "string",
                  "nullable": true
                },
                "site_status": {
                  "type": "string",
                  "nullable": true
                },
                "detected_at": {
                  "type": "string",
                  "nullable": true
                },
                "matched_by": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "checked_at": {
            "type": "string"
          }
        }
      },
      "Campaign": {
        "type": "object",
        "properties": {
          "campaign": {
            "type": "string"
          },
          "by": {
            "type": "string",
            "enum": [
              "brand",
              "kit"
            ]
          },
          "total": {
            "type": "integer"
          },
          "alive": {
            "type": "integer"
          },
          "distinct_ips": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "domains": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "domain": {
                  "type": "string"
                },
                "target_brand": {
                  "type": "string",
                  "nullable": true
                },
                "kit_name": {
                  "type": "string",
                  "nullable": true
                },
                "drainer_type": {
                  "type": "string",
                  "nullable": true
                },
                "ip_address": {
                  "type": "string",
                  "nullable": true
                },
                "site_status": {
                  "type": "string",
                  "nullable": true
                },
                "detected_at": {
                  "type": "string",
                  "nullable": true
                },
                "killed_at": {
                  "type": "string",
                  "nullable": true
                },
                "screenshot_url": {
                  "type": "string",
                  "nullable": true
                }
              }
            }
          }
        }
      },
      "BrandIndex": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "brand_index"
            ]
          },
          "campaigns": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "domains": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "Investigation": {
        "type": "object",
        "required": [
          "domain",
          "verdict",
          "context"
        ],
        "properties": {
          "domain": {
            "type": "string"
          },
          "verdict": {
            "type": "object",
            "description": "Authoritative blocklist result.",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "listed",
                  "whitelisted",
                  "not_listed"
                ]
              },
              "threat": {
                "type": "boolean"
              },
              "severity": {
                "type": "string"
              },
              "risk_score": {
                "type": "integer"
              },
              "listed_since": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "matched_domain": {
                "type": "string"
              },
              "appeal_url": {
                "type": "string"
              },
              "note": {
                "type": "string"
              }
            }
          },
          "assessment": {
            "type": [
              "object",
              "null"
            ],
            "description": "Prediction from PhishDestroy patterns; null for reviewed false positives.",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "prediction"
                ]
              },
              "risk": {
                "type": "string",
                "enum": [
                  "high",
                  "elevated",
                  "low",
                  "none"
                ]
              },
              "score": {
                "type": "integer"
              },
              "max_score": {
                "type": "integer"
              },
              "signals": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "signal": {
                      "type": "string",
                      "enum": [
                        "brand_in_name",
                        "brand_with_scam_terms",
                        "scam_terms",
                        "lookalike_of_known_phishing",
                        "shared_name_pattern",
                        "phishing_infrastructure_ip",
                        "high_risk_tld",
                        "brand_homoglyph",
                        "newly_registered"
                      ]
                    },
                    "weight": {
                      "type": "integer"
                    }
                  },
                  "additionalProperties": true
                }
              },
              "established_domain": {
                "type": "object",
                "properties": {
                  "domain": {
                    "type": "string"
                  },
                  "tranco_rank": {
                    "type": "integer"
                  }
                }
              },
              "long_registered": {
                "type": "object",
                "description": "Registered for three years or more; name signals are capped.",
                "properties": {
                  "registered": {
                    "type": "string",
                    "format": "date"
                  },
                  "years": {
                    "type": "integer"
                  }
                }
              }
            }
          },
          "display_name": {
            "type": "string",
            "description": "How an internationalized (xn--) name renders, e.g. metamаsk.com. Present only for such names."
          },
          "infrastructure": {
            "type": "object",
            "properties": {
              "resolved_ips": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "source": {
                "type": "string",
                "enum": [
                  "intel_record",
                  "live_dns"
                ]
              },
              "cloudflare": {
                "type": "boolean"
              },
              "ip": {
                "type": "string"
              },
              "known_phishing_on_ip": {
                "type": "integer"
              },
              "shared_platform": {
                "type": "boolean"
              }
            }
          },
          "registration": {
            "type": [
              "object",
              "null"
            ],
            "description": "Creation date and registrar from the registry's RDAP service (or the intel record). Null when the registry has no RDAP service or for shared platform subdomains.",
            "properties": {
              "created": {
                "type": "string",
                "format": "date"
              },
              "registrar": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "source": {
                "type": "string",
                "enum": [
                  "rdap",
                  "intel_record"
                ]
              },
              "age_days": {
                "type": "integer"
              }
            }
          },
          "profile": {
            "type": [
              "object",
              "null"
            ],
            "description": "Present for domains with an intelligence record.",
            "additionalProperties": true
          },
          "actor": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "clustered_by": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "domains": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "campaign": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "brand": {
                "type": "string"
              },
              "total": {
                "type": "integer"
              },
              "alive": {
                "type": "integer"
              }
            }
          },
          "related": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "domain": {
                  "type": "string"
                },
                "target_brand": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "site_status": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "detected_at": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "relation": {
                  "type": "string",
                  "enum": [
                    "lookalike",
                    "same_name_pattern",
                    "imitates_this_name"
                  ]
                },
                "similarity": {
                  "type": "number"
                }
              }
            }
          },
          "analyst_report_excerpt": {
            "type": [
              "string",
              "null"
            ]
          },
          "context": {
            "type": "string",
            "description": "Plain-text summary with numbered sources, ready for a language model."
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "guidance": {
            "type": "string"
          },
          "checked_at": {
            "type": "string"
          }
        }
      },
      "KnowledgeSearch": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string"
          },
          "matches": {
            "type": "integer"
          },
          "matches_capped": {
            "type": "boolean"
          },
          "count": {
            "type": "integer"
          },
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "domain": {
                  "type": "string"
                },
                "brand": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "category": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "kit": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "drainer": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "site_status": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "detected_at": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "killed_at": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "vt_detections": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "summary": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "matched_in": {
                  "type": "string",
                  "enum": [
                    "name_brand_title",
                    "analyst_report"
                  ]
                },
                "intel_url": {
                  "type": "string"
                }
              }
            }
          },
          "context": {
            "type": "string"
          },
          "guidance": {
            "type": "string"
          },
          "checked_at": {
            "type": "string"
          }
        }
      }
    }
  }
}
