{
  "openapi": "3.0.3",
  "info": {
    "title": "SpoofLens API",
    "version": "1.0.0",
    "description": "Visitor risk score for sign-ups, checkouts and promo codes: a 0 to 100 score, a verdict, plain reasons, the network (VPN, data center, Tor) and a per-site device ID that shows how many accounts used one device. Browser signals come from https://spooflens.com/v1/sl.js. Docs: https://spooflens.com/developers",
    "contact": { "email": "hello@spooflens.com", "url": "https://spooflens.com/developers" },
    "termsOfService": "https://spooflens.com/developers#terms"
  },
  "servers": [{ "url": "https://spooflens.com" }],
  "security": [{ "bearer": [] }, { "apiKey": [] }],
  "paths": {
    "/v1/assess": {
      "post": {
        "summary": "Assess a visitor from the sl.js blob",
        "operationId": "assess",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AssessRequest" } } }
        },
        "responses": {
          "200": { "description": "Assessment", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Assessment" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/check": {
      "post": {
        "summary": "Assess a visitor from raw spooflens-collector signals",
        "operationId": "check",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CheckRequest" } } }
        },
        "responses": {
          "200": { "description": "Assessment", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Assessment" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/ip/{ip}": {
      "get": {
        "summary": "Network facts about one IP address",
        "operationId": "ip",
        "parameters": [{ "name": "ip", "in": "path", "required": true, "schema": { "type": "string" }, "example": "8.8.8.8" }],
        "responses": {
          "200": { "description": "IP facts", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/IpInfo" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/Error" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": { "type": "http", "scheme": "bearer", "description": "Key from https://spooflens.com/en/app/api (sl_live_...)" },
      "apiKey": { "type": "apiKey", "in": "header", "name": "X-API-Key" }
    },
    "responses": {
      "Error": {
        "description": "Error",
        "content": { "application/json": { "schema": {
          "type": "object",
          "properties": {
            "ok": { "type": "boolean", "example": false },
            "error": { "type": "string", "enum": ["unauthorized", "rate_limited", "quota_exceeded", "bad_blob", "bad_signals", "bad_ip", "bad_json", "method", "not_found", "server"] }
          }
        } } }
      }
    },
    "schemas": {
      "Headers": {
        "type": "object",
        "description": "The visitor's request headers as your server received them",
        "properties": {
          "user_agent": { "type": "string" },
          "accept_language": { "type": "string" },
          "sec_ch_ua": { "type": "string" },
          "sec_ch_ua_platform": { "type": "string" },
          "sec_ch_ua_mobile": { "type": "string" },
          "accept_encoding": { "type": "string" },
          "dnt": { "type": "string" },
          "sec_gpc": { "type": "string" }
        }
      },
      "AssessRequest": {
        "type": "object",
        "required": ["blob", "ip"],
        "properties": {
          "blob": { "type": "string", "description": "Value produced by sl.js (data-field or SpoofLens.collect())" },
          "ip": { "type": "string", "description": "The visitor's IP address", "example": "203.0.113.42" },
          "headers": { "$ref": "#/components/schemas/Headers" },
          "user_ref": { "type": "string", "maxLength": 200, "description": "Optional: your account ID; enables seen.accounts_on_device_30d (only a hash is stored)" },
          "proxy_headers": { "type": "array", "items": { "type": "string" }, "description": "Names of forwarding headers your server received" }
        }
      },
      "CheckRequest": {
        "type": "object",
        "required": ["signals", "ip"],
        "properties": {
          "signals": { "type": "object", "description": "Raw object from spooflens-collector (v: 1)" },
          "ip": { "type": "string" },
          "headers": { "$ref": "#/components/schemas/Headers" },
          "user_ref": { "type": "string", "maxLength": 200 },
          "proxy_headers": { "type": "array", "items": { "type": "string" } }
        }
      },
      "Reason": {
        "type": "object",
        "properties": {
          "code": { "type": "string", "example": "tls_ua" },
          "severity": { "type": "string", "enum": ["high", "medium", "low", "privacy", "info"] },
          "title": { "type": "string" },
          "text": { "type": "string" }
        }
      },
      "Assessment": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "score": { "type": "integer", "minimum": 0, "maximum": 100 },
          "verdict": { "type": "string", "enum": ["honest", "contradictions", "spoofed", "automation"] },
          "reasons": { "type": "array", "items": { "$ref": "#/components/schemas/Reason" } },
          "device": {
            "type": "object",
            "properties": {
              "id": { "type": "string", "description": "Stable per browser profile, keyed per client site" },
              "browser": { "type": "string" },
              "os": { "type": "string" },
              "tls": { "type": "string", "enum": ["chromium", "firefox", "webkit", "library", ""] },
              "ja4": { "type": "string" }
            }
          },
          "network": {
            "type": "object",
            "properties": {
              "ip": { "type": "string" },
              "asn": { "type": "integer", "nullable": true },
              "org": { "type": "string" },
              "country": { "type": "string" },
              "type": { "type": "string", "enum": ["regular", "hosting", "tor"] },
              "tor": { "type": "boolean" },
              "dns_leak": { "type": "boolean" },
              "webrtc_ip_mismatch": { "type": "boolean" }
            }
          },
          "seen": {
            "type": "object",
            "description": "Present only when user_ref was sent",
            "properties": { "accounts_on_device_30d": { "type": "integer" } }
          },
          "edge": { "type": "boolean", "description": "Whether the TLS, DNS and WebRTC checks reached the visitor" },
          "request_id": { "type": "string" },
          "ts": { "type": "string", "format": "date-time" }
        }
      },
      "IpInfo": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "ip": { "type": "string" },
          "version": { "type": "integer" },
          "ptr": { "type": "string" },
          "asn": { "type": "integer", "nullable": true },
          "org": { "type": "string" },
          "country": { "type": "string" },
          "type": { "type": "string", "enum": ["regular", "hosting", "tor"] },
          "tor": { "type": "boolean" }
        }
      }
    }
  }
}