{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/kaminari-ad/json-schema/kaminari-ad-bulk-scan-request-schema.json",
  "title": "BulkScanRequest",
  "description": "Request body to create one scan (or repeat group) per country.",
  "x-generated": "2026-11-13",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/kaminari-ad-openapi.json#/components/schemas/BulkScanRequest",
  "properties": {
    "url": {
      "anyOf": [
        {
          "type": "string",
          "minLength": 1,
          "format": "uri"
        },
        {
          "type": "null"
        }
      ],
      "title": "Url"
    },
    "ad_tag": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Ad Tag",
      "description": "Ad tag to check: an HTML snippet OR an http(s) URL of a page with the creative already rendered. Mutually exclusive with url/vast_tag."
    },
    "vast_tag": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Vast Tag",
      "description": "VAST video tag to check: an http(s) URL of a VAST endpoint OR raw VAST XML. Mutually exclusive with url/ad_tag."
    },
    "referrer": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 2048,
          "minLength": 1,
          "format": "uri"
        },
        {
          "type": "null"
        }
      ],
      "title": "Referrer",
      "description": "Optional http(s) page URL the check is performed from. With ad_tag or vast_tag it is the page the tag is embedded in: the browser commits the harness document on this URL without fetching the publisher, so the creative is embedded exactly as it would be on that page. With url or ad_discovery it is where the visitor came from and travels as the Referer of the page request. Cross-origin subrequests receive the origin only (https://publisher.example/, no path) under Chromium's default strict-origin-when-cross-origin policy — same as on a real publisher. Must be a publicly routable host, carry no credentials, and stay within 2048 characters once normalised."
    },
    "ignore_first_n_domains": {
      "type": "integer",
      "maximum": 5.0,
      "minimum": 0.0,
      "title": "Ignore First N Domains",
      "description": "Skips detection and tagging on the first N unique domains of the redirect chain, counted from the entry point. Use it to exclude your own click/tracking domains so they don't trigger tags or alerts. Domains fold to their registrable form, so www.example.com and example.com count as one. Any other request to one of those domains is excluded too, wherever in the page it was made from. Default 0 (nothing skipped). Maximum 5.",
      "default": 0
    },
    "emulator_id": {
      "type": "string",
      "maxLength": 100,
      "minLength": 1,
      "title": "Emulator Id"
    },
    "proxy": {
      "$ref": "#/$defs/ProxyTargetRequest"
    },
    "labels": {
      "additionalProperties": {
        "type": "string"
      },
      "type": "object",
      "title": "Labels"
    },
    "campaign_id": {
      "anyOf": [
        {
          "type": "string",
          "format": "uuid"
        },
        {
          "type": "null"
        }
      ],
      "title": "Campaign Id",
      "description": "Campaign to attribute the scan to. Alerts are raised only for scans that carry a campaign whose policy set has rules."
    },
    "run_id": {
      "anyOf": [
        {
          "type": "string",
          "format": "uuid"
        },
        {
          "type": "null"
        }
      ],
      "title": "Run Id"
    },
    "ad_discovery": {
      "type": "boolean",
      "title": "Ad Discovery",
      "description": "Publisher ad discovery: detect ad blocks on the page and spawn one child scan per detected ad. Only valid with url (not ad_tag/vast_tag).",
      "default": false
    },
    "max_discovered_ads": {
      "anyOf": [
        {
          "type": "integer",
          "maximum": 25.0,
          "minimum": 1.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Max Discovered Ads",
      "description": "How many ad blocks to look for on the publisher page before stopping. Each detected ad becomes its own scan with its own report, and is billed as a separate check. Only valid together with ad_discovery. Omit to use the platform default. Maximum 25."
    },
    "ad_formats": {
      "anyOf": [
        {
          "items": {
            "$ref": "#/$defs/AdKind"
          },
          "type": "array",
          "maxItems": 3,
          "minItems": 1
        },
        {
          "type": "null"
        }
      ],
      "title": "Ad Formats",
      "description": "Which ad formats the discovery run captures: banner, video and/or pop (pop covers both pop-unders and tab-unders), each at most once. A format left out is neither captured nor billed — e.g. ['pop'] checks the page's pops without screenshotting or clicking its banners; a tab-under the page forces on its own is still reported as a pop. Only valid together with ad_discovery. Omit to capture every format."
    },
    "repeat_count": {
      "type": "integer",
      "minimum": 1.0,
      "title": "Repeat Count",
      "description": "How many times to scan this combination. Each repeat is a full scan with its own report and its own billing.",
      "default": 1
    },
    "repeat_mode": {
      "$ref": "#/$defs/RepeatModeType",
      "description": "isolated: every repeat gets a fresh browser and a new IP. shared: all repeats run in one browser behind one IP, carrying cookies and localStorage over. Not available with ad_discovery.",
      "default": "isolated"
    },
    "retry_max_attempts": {
      "type": "integer",
      "minimum": 0.0,
      "title": "Retry Max Attempts",
      "description": "Extra crawl attempts after a technical failure (dead proxy, timeout, browser crash). The same scan is reused, so a retry is never billed twice.",
      "default": 0
    },
    "country_codes": {
      "items": {
        "type": "string",
        "maxLength": 2,
        "minLength": 2
      },
      "type": "array",
      "minItems": 1,
      "title": "Country Codes",
      "description": "ISO 3166-1 alpha-2 country codes, case-insensitive; stored uppercase."
    }
  },
  "type": "object",
  "required": [
    "emulator_id",
    "country_codes"
  ],
  "$defs": {
    "AdKind": {
      "type": "string",
      "enum": [
        "banner",
        "pop",
        "video"
      ],
      "title": "AdKind",
      "description": "Closed vocabulary for a discovered ad block.\n\n``banner`` and ``video`` carry a creative (screenshot/HTML) plus an\noptional click-through landing — ``video`` is a VAST creative playing\nin a video element (in-player or outstream); ``pop`` is a behaviour\n(pop-under or tab-under) with a landing but no creative."
    },
    "ProxyTargetRequest": {
      "properties": {
        "proxy_type": {
          "type": "string",
          "title": "Proxy Type",
          "default": "residential"
        },
        "region": {
          "type": "string",
          "title": "Region",
          "default": ""
        },
        "city": {
          "type": "string",
          "title": "City",
          "default": ""
        },
        "isp": {
          "type": "string",
          "title": "Isp",
          "default": ""
        }
      },
      "type": "object",
      "title": "ProxyTargetRequest",
      "description": "Proxy targeting parameters for a scan."
    },
    "RepeatModeType": {
      "type": "string",
      "enum": [
        "isolated",
        "shared"
      ],
      "title": "RepeatModeType",
      "description": "Browser-session isolation mode for a group of repeated scans.\n\n``ISOLATED`` is the historical (and only) crawler behaviour: every\nscan gets its own Chromium, its own context and its own proxy session,\nso every repeat sees a new IP and an empty cookie jar.\n\n``SHARED`` makes the crawler run the whole group inside one browser\nsession — one proxy session (one IP) and ``storage_state`` carried from\none repeat to the next. Only groups in this mode carry a\n``Scan.repeat_session_id``."
    }
  }
}