{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/layercall-com/json-schema/layercall-com-ip-score-schema.json",
  "title": "IpScore",
  "x-generated": "2026-10-31",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/layercall-com-openapi.json#/components/schemas/IpScore",
  "type": "object",
  "required": [
    "ip",
    "risk_score",
    "verdict",
    "signals",
    "geo",
    "cached",
    "request_id"
  ],
  "properties": {
    "ip": {
      "type": "string"
    },
    "risk_score": {
      "$ref": "#/$defs/RiskScore"
    },
    "verdict": {
      "$ref": "#/$defs/Verdict"
    },
    "signals": {
      "type": "object",
      "properties": {
        "is_vpn": {
          "type": "boolean"
        },
        "is_proxy": {
          "type": "boolean"
        },
        "is_datacenter": {
          "type": "boolean"
        },
        "is_tor": {
          "type": "boolean"
        },
        "recent_abuse": {
          "type": "boolean"
        },
        "is_private_or_reserved": {
          "type": "boolean",
          "description": "RFC1918 / loopback / reserved. Cannot be meaningfully scored."
        }
      }
    },
    "geo": {
      "type": "object",
      "properties": {
        "country": {
          "type": [
            "string",
            "null"
          ],
          "description": "ISO 3166-1 alpha-2."
        },
        "city": {
          "type": [
            "string",
            "null"
          ]
        },
        "asn": {
          "type": [
            "string",
            "null"
          ]
        },
        "isp": {
          "type": [
            "string",
            "null"
          ]
        }
      }
    },
    "first_seen": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time",
      "description": "First time this value was seen across the LayerCall network."
    },
    "times_seen": {
      "type": "integer"
    },
    "abuse_reports": {
      "type": "integer",
      "description": "Confirmed-fraud reports from distinct accounts."
    },
    "cached": {
      "type": "boolean",
      "description": "Served from cache. Cached lookups are never billed."
    },
    "request_id": {
      "type": "string"
    },
    "warning": {
      "type": "string",
      "description": "Present only when the input needs attention."
    },
    "vpn_provider": {
      "type": [
        "string",
        "null"
      ],
      "description": "Name of the VPN operating this exit node (e.g. \"NordVPN\", \"Mullvad\", \"Surfshark\"), taken from the operator's own published server list. null when is_vpn was inferred from an ASN or third-party feed rather than confirmed by the operator — absence is not evidence the IP is not a VPN."
    },
    "signals_unavailable": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Signals that could not be measured on this request — e.g. [\"tor\", \"vpn_providers\"]. Absent entirely when every feed answered, so the happy-path shape is unchanged. Present means the score was built on incomplete data: a \"clean\" answer and a \"we could not check\" answer are otherwise identical."
    },
    "hijacked_source": {
      "nullable": true
    },
    "processing_time_sec": {
      "type": "number"
    },
    "billable_lookups": {
      "type": "integer"
    }
  },
  "$defs": {
    "RiskScore": {
      "type": "integer",
      "minimum": 0,
      "maximum": 100,
      "description": "0 = clean, 100 = certain fraud."
    },
    "Verdict": {
      "type": "string",
      "enum": [
        "allow",
        "review",
        "block"
      ],
      "description": "Recommended action. Thresholds move with `strictness`."
    }
  }
}